البرنامج التعليمي: إنشاء ونشر التطبيق بخدمة واجهة برمجة تطبيقات الويب الأساسية ASP.NET وخدمة نهاية الخلفية المناسبة

هذا البرنامج التعليمي هو الجزء الأول من سلسلة. سوف تتعلم كيفية إنشاء تطبيق Azure Service Fabric مع واجهة برمجة تطبيقات الويب الأساسية ASP.NET وخدمة الخلفية المناسبة لتخزين بياناتك. عند الانتهاء، يكون لديك تطبيق تصويت مع واجهة ASP.NET Core أمامية على الويب تحتفظ بنتائج التصويت في خدمة الخلفية المناسبة في نظام المجموعة. هذه البرنامج التعليمي يتطلب جهاز Windows مُطوّراً. إذا كنت لا تريد إنشاء تطبيق التصويت يدوياً، فيمكن تنزيل كود المصدر للتطبيق المكتمل الذهاب مباشرة إلى تفقد نموذج تطبيق التصويت. إذا كنت تفضل ذلك، يمكنك أيضاً مشاهدة فيديو التفقد في هذا البرنامج التعليمي.

AngularJS + ASP.NET واجهة برمجة التطبيقات الأمامية، الاتصال بخدمة واجهة الخلفية المناسبة على Service Fabric

في الجزء الأول من السلسلة، تعلم كيفية:

  • إنشاء خدمة API ويب الأساسية ASP.NET كخدمة موثوق بها ومناسبة
  • إنشاء خدمة تطبيق ويب أساسية ASP.NET كخدمة ويب عديمة الحالة
  • استخدام الوكيل العكسي للاتصال مع الخدمة المناسبة

ستتعلم في سلسلة البرامج التعليمية هذه كيفية:

المتطلبات الأساسية

قبل أن تبدأ هذا البرنامج التعليمي:

إنشاء خدمة ASP.NET Web API كخدمة موثوق بها

أولا، إنشاء واجهة الويب الأمامية لتطبيق التصويت باستخدام ASP.NET Core. ASP.NET Core هو إطار عمل تطوير للويب عبر المنصات خفيف بحيث يمكنك استخدامه لإنشاء واجهة مستخدم ويب حديثة وواجهات برمجة التطبيقات على الويب. لفهم كيفية دمج ASP.NET Core مع Service Fabric، نوصي بشدة بالقراءة في مقالة ASP.NET Core in Service Fabric Reliable Services. في الوقت الحالي، يمكنك متابعة هذا البرنامج التعليمي للبدء بسرعة. لمعرفة المزيد عن ASP.NET Core، اطلع على الوثائق الأساسية لــ ASP.NET.

  1. تشغيل Visual Studio كمسؤول.

  2. أنشئ مشروعًا باستخدام ملف->جديد->مشروع.

  3. في حوار المشروع الجديد، اختر تطبيق السحابية> تطبيق نسيج الخدمة .

  4. سمِّ التطبيق التصويت واختر موافق.

    حوار مشروع جديد في Visual Studio

  5. في صفحة خدمة Service Fabric الجديدة، اختر Stateless ASP.NET Core، وسمِّ خدمتك VotingWeb، ثم انقر فوق موافق.

    اختيار خدمة ويب ASP.NET في مربع حوار الخدمة الجديدة

  6. توفر الصفحة التالية مجموعة من قوالب المشروع الأساسية ASP.NET. لهذا البرنامج التعليمي، اختر تطبيق ويب (نموذج-عرض-المراقب المالي)، ثم انقر فوق موافق.

    اختيار مشروع من نوع ASP.NET

    يُنشئ Visual Studio تطبيقاً ومشروع خدمة ويعرضها في مستكشف الحلول.

    يتابع مستكشف الحلول إنشاء التطبيق مع خدمة ASP.NET core Web API الأساسية

تحديث ملف site.js

فتح wwwroot/js/site.js. استبدال محتوياته بـ JavaScript التالية المستخدمة من قِبَل طُرق العرض الرئيسية، ثم احفظ التغييرات.

var app = angular.module('VotingApp', ['ui.bootstrap']);
app.run(function () { });

app.controller('VotingAppController', ['$rootScope', '$scope', '$http', '$timeout', function ($rootScope, $scope, $http, $timeout) {

    $scope.refresh = function () {
        $http.get('api/Votes?c=' + new Date().getTime())
            .then(function (data, status) {
                $scope.votes = data;
            }, function (data, status) {
                $scope.votes = undefined;
            });
    };

    $scope.remove = function (item) {
        $http.delete('api/Votes/' + item)
            .then(function (data, status) {
                $scope.refresh();
            })
    };

    $scope.add = function (item) {
        var fd = new FormData();
        fd.append('item', item);
        $http.put('api/Votes/' + item, fd, {
            transformRequest: angular.identity,
            headers: { 'Content-Type': undefined }
        })
            .then(function (data, status) {
                $scope.refresh();
                $scope.item = undefined;
            })
    };
}]);

تحديث ملف Index.cshtml

فتح Views/Home/Index.cshtml، لعرض وحدة التحكم في الصفحة الرئيسية بشكل خاص. استبدال محتوياته بما يلي، ثم حفظ التغييرات.

@{
    ViewData["Title"] = "Service Fabric Voting Sample";
}

<div ng-controller="VotingAppController" ng-init="refresh()">
    <div class="container-fluid">
        <div class="row">
            <div class="col-xs-8 col-xs-offset-2 text-center">
                <h2>Service Fabric Voting Sample</h2>
            </div>
        </div>

        <div class="row">
            <div class="col-xs-8 col-xs-offset-2">
                <form class="col-xs-12 center-block">
                    <div class="col-xs-6 form-group">
                        <input id="txtAdd" type="text" class="form-control" placeholder="Add voting option" ng-model="item"/>
                    </div>
                    <button id="btnAdd" class="btn btn-default" ng-click="add(item)">
                        <span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
                        Add
                    </button>
                </form>
            </div>
        </div>

        <hr/>

        <div class="row">
            <div class="col-xs-8 col-xs-offset-2">
                <div class="row">
                    <div class="col-xs-4">
                        Click to vote
                    </div>
                </div>
                <div class="row top-buffer" ng-repeat="vote in votes.data">
                    <div class="col-xs-8">
                        <button class="btn btn-success text-left btn-block" ng-click="add(vote.Key)">
                            <span class="pull-left">
                                {{vote.key}}
                            </span>
                            <span class="badge pull-right">
                                {{vote.value}} Votes
                            </span>
                        </button>
                    </div>
                    <div class="col-xs-4">
                        <button class="btn btn-danger pull-right btn-block" ng-click="remove(vote.Key)">
                            <span class="glyphicon glyphicon-remove" aria-hidden="true"></span>
                            Remove
                        </button>
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>

تحديث الملف _Layout.cshtml

فتح طرق Views/Shared/_Layout.cshtml، التخطيط الافتراضي لتطبيق ASP.NET. استبدال محتوياته بما يلي، ثم حفظ التغييرات.

<!DOCTYPE html>
<html ng-app="VotingApp" xmlns:ng="https://angularjs.org">
<head>
    <meta charset="utf-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
    <title>@ViewData["Title"]</title>

    <link href="~/lib/bootstrap/dist/css/bootstrap.css" rel="stylesheet"/>
    <link href="~/css/site.css" rel="stylesheet"/>

</head>
<body>
<div class="container body-content">
    @RenderBody()
</div>

<script src="~/lib/jquery/dist/jquery.js"></script>
<script src="~/lib/bootstrap/dist/js/bootstrap.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.7.2/angular.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-bootstrap/2.5.0/ui-bootstrap-tpls.js"></script>
<script src="~/js/site.js"></script>

@RenderSection("Scripts", required: false)
</body>
</html>

تحديث ملف VotingWeb.cs

فتح ملف VotingWeb.cs، الذي ينشئ ASP.NET Core WebHost داخل الخدمة عديمة الحالة باستخدام خادم ويب WebListener.

إضافة using System.Net.Http; التوجيه إلى أعلى الملف.

استبدال وظيفة CreateServiceInstanceListeners() بالكود التالي، ثم احفظ التغييرات.

protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners()
{
    return new ServiceInstanceListener[]
    {
        new ServiceInstanceListener(
            serviceContext =>
                new KestrelCommunicationListener(
                    serviceContext,
                    "ServiceEndpoint",
                    (url, listener) =>
                    {
                        ServiceEventSource.Current.ServiceMessage(serviceContext, $"Starting Kestrel on {url}");

                        return new WebHostBuilder()
                            .UseKestrel()
                            .ConfigureServices(
                                services => services
                                    .AddSingleton<HttpClient>(new HttpClient())
                                    .AddSingleton<FabricClient>(new FabricClient())
                                    .AddSingleton<StatelessServiceContext>(serviceContext))
                            .UseContentRoot(Directory.GetCurrentDirectory())
                            .UseStartup<Startup>()
                            .UseServiceFabricIntegration(listener, ServiceFabricIntegrationOptions.None)
                            .UseUrls(url)
                            .Build();
                    }))
    };
}

إضافة الطريقة التالية GetVotingDataServiceName أدناه أيضاً، ثم CreateServiceInstanceListeners() حفظ التغييرات. GetVotingDataServiceName إرجاع اسم الخدمة عند الاستقصاء.

internal static Uri GetVotingDataServiceName(ServiceContext context)
{
    return new Uri($"{context.CodePackageActivationContext.ApplicationName}/VotingData");
}

إضافة ملف VotesController.cs

إضافة وحدة التحكم، التي تحدد إجراءات التصويت. انقر بزر الماوس الأيمن فوق المجلد وحدات التحكم، ثم حدد إضافة - >عنصر جديد - > عنصر مرئي C#->ASP.NET Core - >الفئة. قم بتسمية الملف VotesController.cs، ثم حدد إضافة.

استبدال محتويات الملف VotesController.cs بما يلي، ثم حفظ تغييراتك. لاحقاً، في تحديث ملف VotesController.cs، يُعدّل هذا الملف لقراءة وكتابة بيانات التصويت من الخدمة الخلفية. في الوقت الحالي، تقوم وحدة التحكم بإرجاع بيانات سلسلة ثابتة لعرضها.

namespace VotingWeb.Controllers
{
    using System;
    using System.Collections.Generic;
    using System.Fabric;
    using System.Fabric.Query;
    using System.Linq;
    using System.Net.Http;
    using System.Net.Http.Headers;
    using System.Text;
    using System.Threading.Tasks;
    using Microsoft.AspNetCore.Mvc;
    using Newtonsoft.Json;

    [Produces("application/json")]
    [Route("api/Votes")]
    public class VotesController : Controller
    {
        private readonly HttpClient httpClient;

        public VotesController(HttpClient httpClient)
        {
            this.httpClient = httpClient;
        }

        // GET: api/Votes
        [HttpGet]
        public async Task<IActionResult> Get()
        {
            List<KeyValuePair<string, int>> votes= new List<KeyValuePair<string, int>>();
            votes.Add(new KeyValuePair<string, int>("Pizza", 3));
            votes.Add(new KeyValuePair<string, int>("Ice cream", 4));

            return Json(votes);
        }
     }
}

تكوين منفذ الاستماع

عند إنشاء خدمة الواجهة الأمامية VotingWeb، يختار Visual Studio عشوائياً منفذ الخدمة للاستماع. تعمل خدمة VotingWeb كواجهة أمامية لهذا التطبيق وتقبل حركة المرور الخارجية، لذلك دعونا نربط هذه الخدمة بمنفذ ثابت ومعروف. يُعرّف بيان الخدمة نقاط انتهاء الخدمة.

في مستكشف الحلول، يرجى فتح VotingWeb/PackageRoot/ServiceManifest.xml. ابحث عن عنصر نقطة الانتهاء في قسم الموارد وغير قيمة المنفذ إلى 8080. لنشر التطبيق وتشغيله محلياً، يجب أن يكون منفذ استماع التطبيق مفتوحاً ومتاحاً على الكمبيوتر.

<Resources>
    <Endpoints>
      <!-- This endpoint is used by the communication listener to obtain the port on which to 
           listen. Please note that if your service is partitioned, this port is shared with 
           replicas of different partitions that are placed in your code. -->
      <Endpoint Protocol="http" Name="ServiceEndpoint" Type="Input" Port="8080" />
    </Endpoints>
  </Resources>

أيضاً تحديث قيمة خاصية URL التطبيق في مشروع التصويت بحيث يفتح مستعرض الويب المنفذ الصحيح عند تتبع أخطاء التطبيق. في مستكشف الحلول، حدد مشروع التصويت وحدّث خاصية URL التطبيق إلى 8080.

نشر وتشغيل تطبيق التصويت محلياً

يمكنك الآن المضي قدماً وتشغيل تطبيق التصويت لتصحيح الأخطاء. في Visual Studio، اضغط F5 لنشر التطبيق إلى نظام مجموعة "Service Fabric " المحلية في وضع التصحيح. سيفشل التطبيق إذا لم تفتح Visual Studio كمسؤولمسبقاً.

ملاحظة

في المرة الأولى التي تقوم بتشغيل ونشر التطبيق محلياً، ينشئ Visual Studio خدمة محلية "Service Fabric cluster" للتصحيح. قد يستغرق إنشاء نظام المجموعة بعض الوقت. يتم عرض حالة إنشاء نظام المجموعة في نافذة إخراج Visual Studio.

بعد نشر تطبيق التصويت على مجموعة خدمة "Fabric cluster" المحلية، سيتم فتح تطبيق الويب الخاص بك في علامة تبويب مستعرض تلقائياً ويجب أن يبدو هكذا:

ASP.NET الواجهة الأمامية الأساسية

لإيقاف تصحيح التطبيق، انتقل إلى Visual Studio واضغط على Shift+F5.

إضافة خدمة خلفية مناسبة إلى التطبيق

الآن بعد أن يتم تشغيل خدمة api ويب ASP.NET في التطبيق، عليك الانتقال وإضافة خدمة موثوق بها مناسبة لتخزين بعض البيانات في التطبيق.

تسمح لك خدمة Service Fabric بتخزين بياناتك باستمرار ومصداقية تماماً داخل خدمتك باستخدام مجموعات موثوق بها. المجموعات الموثوقة هي مجموعة من فئات الجمع المتوفرة والموثوقة بشكل كبير وهي أيضاً مألوفة لأي شخص يستخدم مجموعات # C.

في هذا البرنامج التعليمي، يمكنك إنشاء خدمة تقوم بتخزين قيمة مضادة في مجموعة موثوق بها.

  1. في مستكشف الحلول، حدد بزر الماوس الأيمن على الخدمات ضمن مشروع تطبيق التصويت واختر إضافة -> خدمة نسيج الخدمة الجديدة.....

  2. في الحوار في خدمة Fabric Service الجديدة، اختر Stateful ASP.NET Core، وسمِّ الخدمة VotingData، ثم اضغط موافق.

    بمجرد إنشاء مشروع خدمتك، يكون لديك خدمتان في تطبيقك. أثناء متابعة إنشاء التطبيق الخاص بك، يمكنك إضافة المزيد من الخدمات بنفس الطريقة. يمكن إصدار كل منها بشكل مستقل وترقيتها.

  3. توفر الصفحة التالية مجموعة من قوالب المشروع الأساسية ASP.NET. بالنسبة لهذا البرنامج التعليمي، اختر API.

    يُنشئ Visual Studio خدمة مشروع VotingData ويعرضه في مستكشف الحلول.

    مستكشف الحلول

إضافة ملف voteDataController.cs

في مشروع VotingData، انقر بزر الماوس الأيمن فوق المجلد Controllers، ثم حدد Add->New Item ->Class. سمِّ الملف VoteDataController.cs وحدد إضافة. استبدل محتويات الملف بما يلي، ثم احفظ التغييرات.

namespace VotingData.Controllers
{
    using System.Collections.Generic;
    using System.Threading;
    using System.Threading.Tasks;
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.ServiceFabric.Data;
    using Microsoft.ServiceFabric.Data.Collections;

    [Route("api/[controller]")]
    public class VoteDataController : Controller
    {
        private readonly IReliableStateManager stateManager;

        public VoteDataController(IReliableStateManager stateManager)
        {
            this.stateManager = stateManager;
        }

        // GET api/VoteData
        [HttpGet]
        public async Task<IActionResult> Get()
        {
            CancellationToken ct = new CancellationToken();

            IReliableDictionary<string, int> votesDictionary = await this.stateManager.GetOrAddAsync<IReliableDictionary<string, int>>("counts");

            using (ITransaction tx = this.stateManager.CreateTransaction())
            {
                Microsoft.ServiceFabric.Data.IAsyncEnumerable<KeyValuePair<string, int>> list = await votesDictionary.CreateEnumerableAsync(tx);

                Microsoft.ServiceFabric.Data.IAsyncEnumerator<KeyValuePair<string, int>> enumerator = list.GetAsyncEnumerator();

                List<KeyValuePair<string, int>> result = new List<KeyValuePair<string, int>>();

                while (await enumerator.MoveNextAsync(ct))
                {
                    result.Add(enumerator.Current);
                }

                return this.Json(result);
            }
        }

        // PUT api/VoteData/name
        [HttpPut("{name}")]
        public async Task<IActionResult> Put(string name)
        {
            IReliableDictionary<string, int> votesDictionary = await this.stateManager.GetOrAddAsync<IReliableDictionary<string, int>>("counts");

            using (ITransaction tx = this.stateManager.CreateTransaction())
            {
                await votesDictionary.AddOrUpdateAsync(tx, name, 1, (key, oldvalue) => oldvalue + 1);
                await tx.CommitAsync();
            }

            return new OkResult();
        }

        // DELETE api/VoteData/name
        [HttpDelete("{name}")]
        public async Task<IActionResult> Delete(string name)
        {
            IReliableDictionary<string, int> votesDictionary = await this.stateManager.GetOrAddAsync<IReliableDictionary<string, int>>("counts");

            using (ITransaction tx = this.stateManager.CreateTransaction())
            {
                if (await votesDictionary.ContainsKeyAsync(tx, name))
                {
                    await votesDictionary.TryRemoveAsync(tx, name);
                    await tx.CommitAsync();
                    return new OkResult();
                }
                else
                {
                    return new NotFoundResult();
                }
            }
        }
    }
}

اتصال الخدمات

في هذه الخطوة التالية، قم بتوصيل الخدمتين واجعل تطبيق الويب الأمامي يحصل على مجموعة معلومات التصويت من الخدمة الخلفية.

توفر Service Fabric مرونة كاملة في التواصل مع الخدمات الموثوقة. ضمن تطبيق واحد، قد تكون لديك خدمات يمكن الوصول إليها عبر TCP. يمكن الوصول إلى الخدمات الأخرى المتاح الوصول إليها عبر واجهة برمجة تطبيقات HTTP REST والخدمات الأخرى عبر مآخذ الويب. للحصول على معلومات أساسية حول الخيارات المتاحة والمفاضلة المعنية، راجع التواصل مع الخدمات.

يستخدم هذا البرنامج التعليمي ASP.NETASP.NET Core Web APIووخدمة وكيل Fabric العكسي حتى تتمكن خدمة ويب VotingWeb الأمامية من الاتصال بخدمة VotingData الخلفية. تتم تهيئة الوكيل العكسي بشكل افتراضي لاستخدام المنفذ 19081 ويجب أن يعمل لهذا البرنامج التعليمي. يتم تعيين منفذ الوكيل العكسي في قالبAzure Resource Manager المستخدم لإعداد الكتلة. للبحث عن المنفذ المستخدم، ابحث في قالب نظام المجموعة في مورد Microsoft.ServiceFabric/clusters:

"nodeTypes": [
          {
            ...
            "httpGatewayEndpointPort": "[variables('nt0fabricHttpGatewayPort')]",
            "isPrimary": true,
            "vmInstanceCount": "[parameters('nt0InstanceCount')]",
            "reverseProxyEndpointPort": "[parameters('SFReverseProxyPort')]"
          }
        ],

للبحث عن منفذ الوكيل العكسي المستخدم في كتلة التطوير المحلي، عرض عنصر HttpApplicationGatewayEndpoint في بيان كتلة "نسيج الخدمة" المحلي:

  1. فتح نافذة مستعرض والانتقال إلى http://localhost:19080 لفتح أداة مستكشف نسيج الخدمة.
  2. حدد مجموعة -> البيان.
  3. دون ملاحظة عن منفذ عنصر HttpApplicationGatewayEndpoint. يجب أن يكون بشكل افتراضي 19081. إذا لم يكن 19081، فستحتاج إلى تغيير المنفذ في أسلوب GetProxyAddress لكود VotesController.cs التالي.

تحديث ملف VotesController.cs

في مشروع VotingWeb فتح ملف Controllers/VotesController.cs. استبدال VotesController محتويات الملف بما يلي، ثم حفظ التغييرات. إذا لم يكن منفذ الوكيل العكسي الذي اكتشفته في الخطوة السابقة هو 19081، فغيّر المنفذ المستخدم في الأسلوب GetProxyAddress من 19081 إلى المنفذ الذي اكتشفته.

public class VotesController : Controller
{
    private readonly HttpClient httpClient;
    private readonly FabricClient fabricClient;
    private readonly StatelessServiceContext serviceContext;

    public VotesController(HttpClient httpClient, StatelessServiceContext context, FabricClient fabricClient)
    {
        this.fabricClient = fabricClient;
        this.httpClient = httpClient;
        this.serviceContext = context;
    }

    // GET: api/Votes
    [HttpGet("")]
    public async Task<IActionResult> Get()
    {
        Uri serviceName = VotingWeb.GetVotingDataServiceName(this.serviceContext);
        Uri proxyAddress = this.GetProxyAddress(serviceName);

        ServicePartitionList partitions = await this.fabricClient.QueryManager.GetPartitionListAsync(serviceName);

        List<KeyValuePair<string, int>> result = new List<KeyValuePair<string, int>>();

        foreach (Partition partition in partitions)
        {
            string proxyUrl =
                $"{proxyAddress}/api/VoteData?PartitionKey={((Int64RangePartitionInformation) partition.PartitionInformation).LowKey}&PartitionKind=Int64Range";

            using (HttpResponseMessage response = await this.httpClient.GetAsync(proxyUrl))
            {
                if (response.StatusCode != System.Net.HttpStatusCode.OK)
                {
                    continue;
                }

                result.AddRange(JsonConvert.DeserializeObject<List<KeyValuePair<string, int>>>(await response.Content.ReadAsStringAsync()));
            }
        }

        return this.Json(result);
    }

    // PUT: api/Votes/name
    [HttpPut("{name}")]
    public async Task<IActionResult> Put(string name)
    {
        Uri serviceName = VotingWeb.GetVotingDataServiceName(this.serviceContext);
        Uri proxyAddress = this.GetProxyAddress(serviceName);
        long partitionKey = this.GetPartitionKey(name);
        string proxyUrl = $"{proxyAddress}/api/VoteData/{name}?PartitionKey={partitionKey}&PartitionKind=Int64Range";

        StringContent putContent = new StringContent($"{{ 'name' : '{name}' }}", Encoding.UTF8, "application/json");
        putContent.Headers.ContentType = new MediaTypeHeaderValue("application/json");

        using (HttpResponseMessage response = await this.httpClient.PutAsync(proxyUrl, putContent))
        {
            return new ContentResult()
            {
                StatusCode = (int) response.StatusCode,
                Content = await response.Content.ReadAsStringAsync()
            };
        }
    }

    // DELETE: api/Votes/name
    [HttpDelete("{name}")]
    public async Task<IActionResult> Delete(string name)
    {
        Uri serviceName = VotingWeb.GetVotingDataServiceName(this.serviceContext);
        Uri proxyAddress = this.GetProxyAddress(serviceName);
        long partitionKey = this.GetPartitionKey(name);
        string proxyUrl = $"{proxyAddress}/api/VoteData/{name}?PartitionKey={partitionKey}&PartitionKind=Int64Range";

        using (HttpResponseMessage response = await this.httpClient.DeleteAsync(proxyUrl))
        {
            if (response.StatusCode != System.Net.HttpStatusCode.OK)
            {
                return this.StatusCode((int) response.StatusCode);
            }
        }

        return new OkResult();
    }


    /// <summary>
    /// Constructs a reverse proxy URL for a given service.
    /// Example: http://localhost:19081/VotingApplication/VotingData/
    /// </summary>
    /// <param name="serviceName"></param>
    /// <returns></returns>
    private Uri GetProxyAddress(Uri serviceName)
    {
        return new Uri($"http://localhost:19081{serviceName.AbsolutePath}");
    }

    /// <summary>
    /// Creates a partition key from the given name.
    /// Uses the zero-based numeric position in the alphabet of the first letter of the name (0-25).
    /// </summary>
    /// <param name="name"></param>
    /// <returns></returns>
    private long GetPartitionKey(string name)
    {
        return Char.ToUpper(name.First()) - 'A';
    }
}

تفقد نموذج تطبيق التصويت

يتكون تطبيق التصويت من خدمتين:

  • خدمة الويب الأمامية (VotingWeb) - خدمة ASP.NET Core للواجهة الأمامية للويب، والتي تخدم صفحة الويب وتعرض واجهات برمجة التطبيقات على الويب للتواصل مع خدمة الواجهة الخلفية.
  • خدمة الخلفية (VotingData) - خدمة ويب ASP.NET Core، والتي تعرض واجهة برمجة تطبيقات بقصد تخزين نتائج التصويت في قاموس موثوق على القرص.

مخطط التطبيق

عند التصويت في التطبيق يحدث ما يلي:

  1. يرسل JavaScript طلب التصويت إلى واجهة برمجة تطبيقات الويب في خدمة واجهة ويب الأمامية في شكل طلب HTTP PUT.

  2. تستخدم خدمة واجهة ويب الأمامية وكيلاً لتحديد موقع طلب HTTP PUT وإعادة توجيهه إلى الخدمة الخلفية.

  3. تأخذ الخدمة الخلفية الطلب الوارد، وتخزن النتيجة المُحدّثة في قاموس موثوق به، والذي ينسخ نسخاً متماثلاً إلى عقد متعددة داخل نظام المجموعة ويحتفظ به في القرص. يتم تخزين جميع بيانات التطبيق في نظام المجموعة، ولذا لا توجد حاجة إلى قاعدة بيانات.

تتبع الأخطاء في Visual Studio

عند تتبع أخطاء تطبيق في Visual Studio، يمكن استخدام خدمة نظام مجموعة Fabric development محلية. يمكنك ضبط تجربة تتبع الأخطاء في السيناريو. في هذا التطبيق، تُخزّن البيانات في خدمة النهاية الخلفية باستخدام قاموس موثوق بها. Visual Studio يزيل التطبيق افتراضياً عند إيقاف المصحح. تؤدي إزالة التطبيق، إلى حذف البيانات التي في الخدمة الخلفية أيضاً. للاحتفاظ بالبيانات بين جلسات تتبع الأخطاء، يمكنك تغيير وضع تتبع الأخطاء كخاصية في مشروع التصويت في Visual Studio.

للاطلاع على ما يحدث في التعليمات البرمجية، تابع الخطوات التالية:

  1. افتح ملف VotingWeb\VotesController.cs ثم قم بتعيين نقطة توقف في واجهة برمجة التطبيقات على الويب وضع الأسلوب (السطر 72).

  2. افتح ملف VotingWeb\VotesController.cs ثم قم بتعيين نقطة توقف في واجهة برمجة التطبيقات على الويب وضع الأسلوب (السطر 72).

  3. اضغط F5 لتشغيل التطبيق في وضع تتبع الأخطاء.

  4. العودة إلى المتصفح والتحديد على خيار التصويت أو إضافة خيار تصويت جديد. الضغط على نقطة التوقف الأولى في وحدة تحكم الواجهة الأمامية.

    1. هذا هو المكان الذي يرسل به الــ JavaScript في المستعرض طلباً إلى وحدة تحكم API التي على الويب في خدمة الواجهة الأمامية.

      إضافة خدمة التصويت الأمامية

    2. أولاً إنشاء URL في ReverseProxy لخدمة النهاية الخلفية (1).

    3. ثم إرسال طلب HTTP PUT إلى ReverseProxy (2) .

    4. أخيراً إرجاع الاستجابة من الخدمة الخلفية إلى العميل (3).

  5. اضغط F5 للمتابعة.

    1. أنت الآن في نقطة التوقف في الخدمة الخلفية.

      إضافة خدمة التصويت الخلفية

    2. في السطر الأول في الأسلوب (1) استخدم StateManager للحصول على أو إضافة قاموس موثوق يسمى counts.

    3. تتطلب جميع التفاعلات مع القيم في القاموس الموثوق به معاملة، وهذا باستخدام العبارة (2) بإنشاء تلك الحركة.

    4. في المعاملة، قم بتحديث قيمة المفتاح المتصل بخيار التصويت، وثبت العملية (3). بمجرد إرجاع الأسلوب التثبيت، يتم تحديث البيانات في القاموس ثم نسخها نسخاً متماثلاً إلى العقد الأخرى في نظام المجموعة. يتم الآن تخزين البيانات بأمان في نظام المجموعة، ويمكن أن تفشل الخدمة الخلفية إلى عقد أخرى، لا تزال لديها البيانات المتاحة.

  6. اضغط F5 للمتابعة.

لإيقاف جلسة عمل تتبع الأخطاء، اضغط على Shift +F5.

الخطوات التالية

في هذا الجزء من البرنامج التعليمي، تعلمت كيفية:

  • إنشاء خدمة API ويب الأساسية ASP.NET كخدمة موثوق بها ومناسبة
  • إنشاء خدمة تطبيق ويب أساسية ASP.NET كخدمة ويب عديمة الحالة
  • استخدام الوكيل العكسي للاتصال مع الخدمة المناسبة

التقدم إلى البرنامج التعليمي التالي: