您现在访问的是微软AZURE全球版技术文档网站,若需要访问由世纪互联运营的MICROSOFT AZURE中国区技术文档网站,请访问 https://docs.azure.cn.

教程:使用 ASP.NET Core Web API 前端服务和有状态后端服务创建并部署应用程序Tutorial: Create and deploy an application with an ASP.NET Core Web API front-end service and a stateful back-end service

本教程是一个系列中的第一部分。This tutorial is part one of a series. 其中介绍了如何使用 ASP.NET Core Web API 前端和有状态后端服务创建 Azure Service Fabric 应用程序以存储数据。You will learn how to create an Azure Service Fabric application with an ASP.NET Core Web API front end and a stateful back-end service to store your data. 完成后,将生成一个投票应用程序,其中包含 ASP.NET Core Web 前端,用于将投票结果保存到群集的有状态后端服务中。When you're finished, you have a voting application with an ASP.NET Core web front-end that saves voting results in a stateful back-end service in the cluster. 如果不想手动创建投票应用程序,可以下载已完成应用程序的源代码,跳到大致了解投票示例应用程序If you don't want to manually create the voting application, you can download the source code for the completed application and skip ahead to Walk through the voting sample application. 如果需要,还可以观看本教程的视频演练If you prefer, you can also watch a video walk-through of this tutorial.

连接到 Service Fabric 上的有状态后端服务的 AngularJS+ASP.NET API 前端

在该系列的第一部分中,你会学习如何:In part one of the series, you learn how to:

  • 将 ASP.NET Core Web API 服务作为有状态可靠服务创建Create an ASP.NET Core Web API service as a stateful reliable service
  • 将 ASP.NET Core Web 应用程序服务作为无状态 Web 服务创建Create an ASP.NET Core Web Application service as a stateless web service
  • 使用反向代理与有状态服务通信Use the reverse proxy to communicate with the stateful service

在此系列教程中,你会学习如何:In this tutorial series you learn how to:

先决条件Prerequisites

在开始学习本教程之前:Before you begin this tutorial:

将 ASP.NET Web API 服务作为 Reliable Services 创建Create an ASP.NET Web API service as a reliable service

首先,使用 ASP.NET Core 创建投票应用程序的 Web 前端。First, create the web front-end of the voting application using ASP.NET Core. ASP.NET Core 是轻量跨平台的 Web 开发框架,可用于创建现代 Web UI 和 Web API。ASP.NET Core is a lightweight, cross-platform web development framework that you can use to create modern web UI and web APIs. 若要全面了解 ASP.NET Core 如何与 Service Fabric 集成,强烈建议你通读 Service Fabric Reliable Services 中的 ASP.NET Core 一文。To get a complete understanding of how ASP.NET Core integrates with Service Fabric, we strongly recommend reading through the ASP.NET Core in Service Fabric Reliable Services article. 现可按照本指南快速入门。For now, you can follow this tutorial to get started quickly. 若要了解有关 ASP.NET Core 的详细信息,请参阅 ASP.NET Core 文档To learn more about ASP.NET Core, see the ASP.NET Core Documentation.

  1. 以管理员身份启动 Visual Studio。 Launch Visual Studio as an administrator.

  2. 通过单击“文件” -> “新建”-> “项目”创建项目。Create a project with File->New->Project.

  3. 在“新建项目”对话框中,选择“云”>“Service Fabric 应用程序”。 In the New Project dialog, choose Cloud > Service Fabric Application.

  4. 将应用程序命名为“Voting”,然后单击“确定” 。Name the application Voting and click OK.

    Visual Studio 中的新建项目对话框

  5. 在“新建 Service Fabric 服务”页中,选择“无状态 ASP.NET Core”,将服务命名为“VotingWeb”,然后单击“确定” 。On the New Service Fabric Service page, choose Stateless ASP.NET Core, name your service VotingWeb, then click OK.

    在新建服务对话框中选择 ASP.NET Web 服务

  6. 下一页将提供一组 ASP.NET Core 项目模板。The next page provides a set of ASP.NET Core project templates. 对于本教程,请选择“Web 应用程序(模型-视图-控制器)”,然后单击“确定” 。For this tutorial, choose Web Application (Model-View-Controller), then click OK.

    选择 ASP.NET 项目类型

    Visual Studio 会创建应用程序和服务项目,并在解决方案资源管理器中显示它们。Visual Studio creates an application and a service project and displays them in Solution Explorer.

    使用 ASP.NET Core Web API 服务创建应用程序之后的解决方案资源管理器

更新 site.js 文件Update the site.js file

打开 wwwroot/js/site.jsOpen wwwroot/js/site.js. 将其内容替换为“主页”视图所用的以下 JavaScript,然后保存更改。Replace its contents with the following JavaScript used by the Home views, then save your changes.

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 文件Update the Index.cshtml file

打开特定于主文件夹控制器的视图 Views/Home/Index.cshtmlOpen Views/Home/Index.cshtml, the view specific to the Home controller. 将其内容替换为以下内容,然后保存所做更改。Replace its contents with the following, then save your changes.

@{
    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 文件Update the _Layout.cshtml file

打开 ASP.NET 应用的默认布局 Views/Shared/_Layout.cshtmlOpen Views/Shared/_Layout.cshtml, the default layout for the ASP.NET app. 将其内容替换为以下内容,然后保存所做更改。Replace its contents with the following, then save your changes.

<!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 文件Update the VotingWeb.cs file

打开 VotingWeb.cs 文件,该文件会使用 WebListener Web 服务器在无状态服务内创建 ASP.NET Core WebHost。Open the VotingWeb.cs file, which creates the ASP.NET Core WebHost inside the stateless service using the WebListener web server.

在文件顶部,添加 using System.Net.Http; 指令。Add the using System.Net.Http; directive to the top of the file.

CreateServiceInstanceListeners() 函数替换为以下代码,然后保存所做更改。Replace the CreateServiceInstanceListeners() function with the following code, then save your changes.

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();
                    }))
    };
}

CreateServiceInstanceListeners() 的下面添加以下 GetVotingDataServiceName 方法,然后保存所做更改。Also add the following GetVotingDataServiceName method below CreateServiceInstanceListeners(), then save your changes. GetVotingDataServiceName 返回轮询的服务名称GetVotingDataServiceName returns the service name when polled.

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

添加 VotesController.cs 文件Add the VotesController.cs file

添加控制器用于定义投票操作。Add a controller, which defines voting actions. 右键单击“控制器”文件夹,然后选择“添加”->“新建项”->“Visual C#”->“ASP.NET Core”->“类” 。Right-click on the Controllers folder, then select Add->New item->Visual C#->ASP.NET Core->Class. 将文件命名为“VotesController.cs”,然后单击“添加” 。Name the file VotesController.cs, then click Add.

VotesController.cs 文件内容替换为以下内容,然后保存所做更改。Replace the VotesController.cs file contents with the following, then save your changes. 稍后在执行更新 VotesController.cs 文件时将会修改此文件,以读取和写入来自后端服务的投票数据。Later, in Update the VotesController.cs file, this file is modified to read and write voting data from the back-end service. 现在,控制器会将静态字符串数据返回到视图中。For now, the controller returns static string data to the view.

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);
        }
     }
}

配置侦听端口Configure the listening port

创建 VotingWeb 前端服务后,Visual Studio 会随机选择服务侦听的端口。When the VotingWeb front-end service is created, Visual Studio randomly selects a port for the service to listen on. VotingWeb 服务充当此应用程序的前端并接受外部流量,因此让我们将此服务绑定到已知的固定端口。The VotingWeb service acts as the front-end for this application and accepts external traffic, so let's bind that service to a fixed and well-know port. 服务清单声明服务终结点。The service manifest declares the service endpoints.

在解决方案资源管理器中,打开“VotingWeb/PackageRoot/ServiceManifest.xml” 。In Solution Explorer, open VotingWeb/PackageRoot/ServiceManifest.xml. 在“资源”部分中查找“终结点”元素,并将“端口”值更改为 80Find the Endpoint element in the Resources section and change the Port value to 8080. 若要在本地部署和运行应用程序,应用程序侦听端口必须为打开状态且在你的计算机上可用。To deploy and run the application locally, the application listening port must be open and available on your computer.

<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 属性值,使 Web 浏览器在调试应用程序时打开到正确的端口。Also update the Application URL property value in the Voting project so a web browser opens to the correct port when you debug your application. 在解决方案资源管理器中,选择“投票”项目并将“应用程序 URL”属性更新为 8080In Solution Explorer, select the Voting project and update the Application URL property to 8080.

在本地部署并运行“投票”应用程序Deploy and run the Voting application locally

现在可以继续运行“投票”应用程序进行调试。You can now go ahead and run the Voting application for debugging. 在 Visual Studio 中,按 F5 在调试模式下将应用程序部署到本地 Service Fabric 群集。In Visual Studio, press F5 to deploy the application to your local Service Fabric cluster in debug mode. 如果此前未以管理员身份打开 Visual Studio,则应用程序会失败。 The application will fail if you didn't previously open Visual Studio as administrator.

备注

首次在本地运行和部署应用程序时,Visual Studio 会创建用于调试的本地 Service Fabric 群集。The first time you run and deploy the application locally, Visual Studio creates a local Service Fabric cluster for debugging. 创建群集可能需要一段时间。Cluster creation may take some time. 群集创建状态显示在 Visual Studio 输出窗口中。The cluster creation status is displayed in the Visual Studio output window.

将“投票”应用程序部署到本地 Service Fabric 群集后,Web 应用会在浏览器选项卡中自动打开,如下所示:After the Voting application has been deployed to your local Service Fabric cluster, your web app will open in a browser tab automatically and should look like this:

ASP.NET Core 前端

若要停止调试应用程序,请返回到 Visual Studio 并按 Shift+F5 。To stop debugging the application, go back to Visual Studio and press Shift+F5.

向应用程序添加有状态后端服务Add a stateful back-end service to your application

在应用程序中运行 ASP.NET Web API 服务后,可以继续添加有状态可靠服务,以便在应用程序中存储一些数据。Now that an ASP.NET Web API service is running in the application, go ahead and add a stateful reliable service to store some data in the application.

Service Fabric 允许使用 Reliable Collections 直接在服务内以一致、可靠的方式存储数据。Service Fabric allows you to consistently and reliably store your data right inside your service by using reliable collections. Reliable Collections 是一组高度可用的可靠集合类,用过 C# 集合的用户都对它很熟悉。Reliable collections are a set of highly available and reliable collection classes that are familiar to anyone who has used C# collections.

在本教程中,创建一个服务,用于在 Reliable Collections 中存储计数器值。In this tutorial, you create a service which stores a counter value in a reliable collection.

  1. 在解决方案资源管理器中,右键单击“投票”应用程序项目中的“服务”,并选择“添加”->“新建 Service Fabric 服务...” 。In Solution Explorer, right-click Services within the Voting application project and choose Add -> New Service Fabric Service....

  2. 在“新建 Service Fabric 服务”对话框中,选择“有状态 ASP.NET Core”,将服务命名为“VotingData”,然后按“确定” 。In the New Service Fabric Service dialog, choose Stateful ASP.NET Core, name the service VotingData, then press OK.

    创建服务项目后,应用程序中会有两个服务。Once your service project is created, you have two services in your application. 随着继续生成应用程序,可采用相同的方式添加更多服务。As you continue to build your application, you can add more services in the same way. 每个服务都可以单独进行版本控制和升级。Each can be independently versioned and upgraded.

  3. 下一页将提供一组 ASP.NET Core 项目模板。The next page provides a set of ASP.NET Core project templates. 本教程中,选择“API” 。For this tutorial, choose API.

    Visual Studio 会创建 VotingData 服务项目,并在解决方案资源管理器中显示。Visual Studio creates the VotingData service project and displays it in Solution Explorer.

    解决方案资源管理器

添加 VoteDataController.cs 文件Add the VoteDataController.cs file

在“VotingData”项目中,右键单击“控制器”文件夹,然后选择“添加”->“新建项目”->“类” 。In the VotingData project, right-click on the Controllers folder, then select Add->New item->Class. 将文件命名为“VoteDataController.cs”,然后单击“添加” 。Name the file VoteDataController.cs and click Add. 将文件内容替换为以下内容,然后保存所做更改。Replace the file contents with the following, then save your changes.

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();
                }
            }
        }
    }
}

连接服务Connect the services

下一步是连接这两个服务,使前端 Web 应用程序获取并设置来自后端服务的投票信息。In this next step, connect the two services and make the front-end Web application get and set voting information from the back-end service.

在如何与 Reliable Services 通信方面,Service Fabric 提供十足的弹性。Service Fabric provides complete flexibility in how you communicate with reliable services. 在单个应用程序中,可能有能够通过 TCP 访问的服务。Within a single application, you might have services that are accessible via TCP. 其他服务也许可以通过 HTTP REST API 访问,并且仍可通过 Web 套接字访问。Other services that might be accessible via an HTTP REST API and still other services could be accessible via web sockets. 有关可用选项和相关权衡取舍的背景信息,请参阅与服务通信For background on the options available and the tradeoffs involved, see Communicating with services.

本教程使用 ASP.NET Core Web APIService Fabric 反向代理,以便 VotingWeb 前端 Web 服务能够与后端 VotingData 服务通信。This tutorial uses ASP.NET Core Web API and the Service Fabric reverse proxy so the VotingWeb front-end web service can communicate with the back-end VotingData service. 反向代理默认配置为使用端口 19081,应适用于本教程。The reverse proxy is configured by default to use port 19081 and should work for this tutorial. 反向代理端口是在用于设置群集的 Azure 资源管理器模板中设置的。The reverse proxy port is set in the Azure Resource Manager template used to set up the cluster. 若要确定使用哪个端口,请在 Microsoft.ServiceFabric/clusters 资源中搜索群集模板:To find which port is used, look in the cluster template in the Microsoft.ServiceFabric/clusters resource:

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

若要查找在本地开发群集中使用的反向代理端口,请查看本地 Service Fabric 群集清单中的 HttpApplicationGatewayEndpoint 元素:To find the reverse proxy port used in your local development cluster, view the HttpApplicationGatewayEndpoint element in the local Service Fabric cluster manifest:

  1. 打开一个浏览器窗口,并导航到 http://localhost:19080 以打开 Service Fabric Explorer 工具。Open a browser window and navigate to http://localhost:19080 to open the Service Fabric Explorer tool.
  2. 选择“群集”->“清单”。 Select Cluster -> Manifest.
  3. 记下 HttpApplicationGatewayEndpoint 元素端口。Make a note of the HttpApplicationGatewayEndpoint element port. 默认情况下,此端口应是 19081。By default this should be 19081. 如果不是 19081,则需要更改以下 VotesController.cs 代码的 GetProxyAddress 方法中的端口。If it is not 19081, you will need to change the port in the GetProxyAddress method of the following VotesController.cs code.

更新 VotesController.cs 文件Update the VotesController.cs file

在“VotingWeb”项目中,打开 Controllers/VotesController.cs 文件 。In the VotingWeb project, open the Controllers/VotesController.cs file. VotesController 类定义内容替换为以下内容,然后保存所做更改。Replace the VotesController class definition contents with the following, then save your changes. 如果在前面的步骤中发现反向代理端口不是 19081,更改发现的端口从 19081 GetProxyAddress 方法中使用的端口。If the reverse proxy port you discovered in the pervious step is not 19081, change the port used in the GetProxyAddress method from 19081 to the port that you discovered.

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';
    }
}

大致了解投票示例应用程序Walk through the voting sample application

投票应用程序由以下两个服务组成:The voting application consists of two services:

  • Web 前端服务 (VotingWeb) - ASP.NET Core Web 前端服务,可提供网页服务,并公开用于与后端服务进行通信的 Web API。Web front-end service (VotingWeb)- An ASP.NET Core web front-end service, which serves the web page and exposes web APIs to communicate with the backend service.
  • 后端服务 (VotingData) - ASP.NET Core Web 服务,可公开用于将投票结果存储到磁盘上保留的可靠字典中的 API。Back-end service (VotingData)- An ASP.NET Core web service, which exposes an API to store the vote results in a reliable dictionary persisted on disk.

应用程序关系图

在应用程序中投票时,将会发生以下事件:When you vote in the application the following events occur:

  1. JavaScript 将投票请求作为 HTTP PUT 请求发送给 Web 前端服务中的 Web API。A JavaScript sends the vote request to the web API in the web front-end service as an HTTP PUT request.

  2. Web 前端服务使用代理定位并将 HTTP PUT 请求转发给后端服务。The web front-end service uses a proxy to locate and forward an HTTP PUT request to the back-end service.

  3. 后端服务接收传入请求,并将更新后的结果存储在可靠字典中(结果复制到群集内的多个节点,并保留在磁盘上)。The back-end service takes the incoming request, and stores the updated result in a reliable dictionary, which gets replicated to multiple nodes within the cluster and persisted on disk. 应用程序的所有数据都存储在群集中,因此无需使用数据库。All the application's data is stored in the cluster, so no database is needed.

在 Visual Studio 中进行调试Debug in Visual Studio

在 Visual Studio 中调试应用程序时,使用的是本地 Service Fabric 开发群集。When debugging application in Visual Studio, you are using a local Service Fabric development cluster. 可以根据需要针对自己的方案调整调试体验。You have the option to adjust your debugging experience to your scenario. 在此应用程序中,我们使用可靠字典将数据存储到后端服务中。In this application, store data in the back-end service using a reliable dictionary. 停止调试程序时,Visual Studio 会默认删除应用程序。Visual Studio removes the application per default when you stop the debugger. 删除应用程序后,后端服务中的数据也会随之一起删除。Removing the application causes the data in the back-end service to also be removed. 若要跨调试会话保留数据,可以将“应用程序调试模式” 作为 Visual Studio 中“投票” 项目的属性进行更改。To persist the data between debugging sessions, you can change the Application Debug Mode as a property on the Voting project in Visual Studio.

若要查看代码,请完成以下步骤:To look at what happens in the code, complete the following steps:

  1. 打开 VotingWeb\VotesController.cs 文件,并在此 Web API 的 Put 方法(第 72 行)中设置断点。Open the VotingWeb\VotesController.cs file and set a breakpoint in the web API's Put method (line 72).

  2. 打开 VotingData\VoteDataController.cs 文件,并在此 Web API 的 Put 方法(第 54 行)中设置断点。Open the VotingData\VoteDataController.cs file and set a breakpoint in this web API's Put method (line 54).

  3. F5 以调试模式启动应用程序。Press F5 to start the application in debug mode.

  4. 返回到浏览器,再单击投票选项或添加新的投票选项。Go back to the browser and click a voting option or add a new voting option. 点击 Web 前端 API 控制器中的第一个断点。You hit the first breakpoint in the web front-end's api controller.

    1. 此时,浏览器中的 JavaScript 将请求发送到前端服务中的 Web API 控制器。This is where the JavaScript in the browser sends a request to the web API controller in the front-end service.

      添加投票前端服务

    2. 首先,为后端服务构建 ReverseProxy 的 URL (1)First construct the URL to the ReverseProxy for the back-end service (1).

    3. 然后,向 ReverseProxy 发送 HTTP PUT 请求 (2)Then send the HTTP PUT Request to the ReverseProxy (2).

    4. 最后,将后端服务的响应返回到客户端 (3)Finally the return the response from the back-end service to the client (3).

  5. 按 F5 以继续操作 。Press F5 to continue.

    1. 此时,到达后端服务中的断点。You are now at the break point in the back-end service.

      添加投票后端服务

    2. 方法 (1) 的第一行使用 StateManager 获取或添加 counts 可靠字典。In the first line in the method (1) use the StateManager to get or add a reliable dictionary called counts.

    3. 与可靠字典中的值进行的所有交互都需要使用事务,这个 using 语句(图中标识为2) 负责创建此事务。All interactions with values in a reliable dictionary require a transaction, this using statement (2) creates that transaction.

    4. 在事务中更新投票选项的相关键值,并提交操作 (3)In the transaction, update the value of the relevant key for the voting option and commits the operation (3). 提交方法返回后,便会更新字典中的数据,并将数据复制到群集中的其他节点。Once the commit method returns, the data is updated in the dictionary and replicated to other nodes in the cluster. 数据现在安全地存储在群集中,并且后端服务可以故障转移到其他节点,同时数据仍可用。The data is now safely stored in the cluster, and the back-end service can fail over to other nodes, still having the data available.

  6. 按 F5 以继续操作 。Press F5 to continue.

若要停止调试会话,请按 Shift+F5 。To stop the debugging session, press Shift+F5.

后续步骤Next steps

本教程的此部分介绍了如何:In this part of the tutorial, you learned how to:

  • 将 ASP.NET Core Web API 服务作为有状态可靠服务创建Create an ASP.NET Core Web API service as a stateful reliable service
  • 将 ASP.NET Core Web 应用程序服务作为无状态 Web 服务创建Create an ASP.NET Core Web Application service as a stateless web service
  • 使用反向代理与有状态服务通信Use the reverse proxy to communicate with the stateful service

转到下一教程:Advance to the next tutorial: