Dapr DotNet5 HTTP 調用

Dapr DotNet5 HTTP 調用

版本介紹

  • Dotnet 版本:5.0.100
  • Dapr dotnet 版本:0.12.0-preview01

注意: Asp.Net Core 項目中的 launchSettings.json 文件,該文件的中的端口號應和 darp –app-port 端口號相同,否則 dapr 無法正常啟動 Asp.Net Core 項目。

工程結構

3 個 .NET 5 項目,ClientA、ServiceB、ServiceC。1 個 .NET Standard 項目,Dtos 。Dtos 用於存儲各種傳輸模型。調用路徑如下圖所示。新建兩個 service 的意義在於展示 http 鏈路調用通過 dapr 如何實現。

graph LR;
dotnet5-client-a–1–>dotnet5-service-b;
dotnet5-service-b–2–>dotnet5-service-c;
dotnet5-service-c–3–>dotnet5-service-b;
dotnet5-service-b–4–>dotnet5-client-a;
  1. dotnet5-client-a 做為客戶端調用服務 dotnet5-service-b;
  2. dotnet5-service-b 做為服務中轉,既收來自 dotnet5-client-a 客戶端的請求,又發起對 dotnet5-service-c 的調用;
  3. dotnet5-service-c 響應 dotnet5-service-b 的請求;
  4. dotnet5-service-b 響應 dotnet5-client-a 的請求。

ServiceC

ServiceC 做為 http 調用鏈路調用終端只需監聽 http 調用端口。通過 nuget 包管理工具,選中->Show pre-release packages,搜索 dapr ,選中 Dapr.AspNetCore 安裝包。

Startup

在 ConfigureServices(IServiceCollection services) 方法中通過鏈式調用 AddDapr() 方法註冊 Dapr 到 IOC 容器中。內容如下:

using System.Text.Json;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace ServiceC
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers().AddDapr().AddJsonOptions(options => {
                options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
                options.JsonSerializerOptions.PropertyNameCaseInsensitive = true;
            }
            );
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseRouting();
            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
        }
    }
}

HelloController

在 HelloController 中添加 Talk 方法,打印接收的信息並告訴調用方當前服務是誰。具體內容如下:

[ApiController]
public class HelloController : Controller
{
    [HttpPost("talk")]
    public async Task<SomeResponseBody> Talk(SomeRequestBody someRequestBody)
    {
        Console.WriteLine(string.Format("{0}:{1}", someRequestBody.Id, someRequestBody.Time));
        return await Task.FromResult(new SomeResponseBody
        {
            Msg = "This is ServiceC"
        });
    }
}

launchSetting.json

profiles.ServiceC.applicationUrl 端口號一定要修改為 –app-port 相同的端口號,否則通過 dapr 啟動項目的時候無法正常啟動

{
  "$schema": "//json.schemastore.org/launchsettings.json",
  "iisSettings": {
    "windowsAuthentication": false,
    "anonymousAuthentication": true,
    "iisExpress": {
      "applicationUrl": "//localhost:35737",
      "sslPort": 44379
    }
  },
  "profiles": {
    "IIS Express": {
      "commandName": "IISExpress",
      "launchBrowser": true,
      "launchUrl": "swagger",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    },
    "ServiceC": {
      "commandName": "Project",
      "launchBrowser": true,
      "launchUrl": "swagger",
      "applicationUrl": "//localhost:9201",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    }
  }
}

啟動

dapr run --app-id dotnet-server-c --app-port 9201 --dapr-http-port 3520 dotnet run

ServiceB

ServiceB 做為調用鏈中的一個中轉節點,既要監聽服務,同時還要發起請求。由於 Dapr.AspNetCore 已經引用了 Dapr.Client 。因此不需要再次引用 Dapr.Client

Startup

下面是 Dapr.AspNetCore AddDapr() 源碼,從源碼中可知 AddDapr() 方法向控制器中註冊 Dapr 集成。同時通過依賴注入容器註冊 DaprClient 。DaprClient 可以和 Dapr 運行時交互。比如 HTTP 調用,也正因為如此,ServiceB 的 Startup 文件我們只需拷貝 ServiceC 的 Startup 文件即可。源碼如下:

/// <summary>
/// Provides extension methods for <see cref="IMvcBuilder" />.
/// </summary>
public static class DaprMvcBuilderExtensions
{
    /// <summary>
    /// Adds Dapr integration for MVC to the provided <see cref="IMvcBuilder" />.
    /// </summary>
    /// <param name="builder">The <see cref="IMvcBuilder" />.</param>
    /// <param name="configureClient">The (optional) <see cref="DaprClientBuilder" /> to use for configuring the DaprClient.</param>
    /// <returns>The <see cref="IMvcBuilder" /> builder.</returns>
    public static IMvcBuilder AddDapr(this IMvcBuilder builder, Action<DaprClientBuilder> configureClient = null)
    {
        if (builder is null)
        {
            throw new ArgumentNullException(nameof(builder));
        }

        // This pattern prevents registering services multiple times in the case AddDapr is called
        // by non-user-code.
        if (builder.Services.Any(s => s.ImplementationType == typeof(DaprMvcMarkerService)))
        {
            return builder;
        }

        builder.Services.AddDaprClient(configureClient);

        builder.Services.AddSingleton<DaprMvcMarkerService>();
        builder.Services.AddSingleton<IApplicationModelProvider, StateEntryApplicationModelProvider>();
        builder.Services.Configure<MvcOptions>(options =>
        {
            options.ModelBinderProviders.Insert(0, new StateEntryModelBinderProvider());
        });

        return builder;
    }

    private class DaprMvcMarkerService
    {
    }
}

HelloController

通過構造器注入 DaprClient 以發起 Http 調用 ServiceC 提供的服務。

[ApiController]
public class HelloController : ControllerBase
{

    private readonly DaprClient daprClient;

    public HelloController(DaprClient daprClient)
    {
        this.daprClient = daprClient;
    }

    [HttpPost("talk")]
    public async Task<SomeResponseBody> Talk(SomeRequestBody someRequestBody)
    {
        var data = new { Time = DateTime.Now.ToLongDateString(), Id = "This is Service C." };
        HTTPExtension httpExtension = new HTTPExtension()
        {
            Verb = HTTPVerb.Post
        };
        SomeResponseBody responseBody = await daprClient.InvokeMethodAsync<object, SomeResponseBody>("dotnet-server-c", "talk", data, httpExtension);

        Console.WriteLine(string.Format("{0}:{1} \n recieve message:{2}", someRequestBody.Id, someRequestBody.Time, responseBody.Msg));
        return await Task.FromResult(new SomeResponseBody
        {
            Msg = "This is ServiceB"
        });
    }

launchSetting.json

參考 ServiceC 更改端口號。

啟動

dapr run --app-id dotnet-server-b --app-port 9200 --dapr-http-port 3521 dotnet run

ClientA

ClientA 的目的是發起對 ServiceB 服務的調用,因此只需添加 Dapr.Client 用於和 Dapr 運行時交互即可。內容如下:

class Program
    {
        static async Task Main(string[] args)
        {
            var jsonOptions = new JsonSerializerOptions()
            {
                PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
                PropertyNameCaseInsensitive = true,
            };

            var client = new DaprClientBuilder()
                .UseJsonSerializationOptions(jsonOptions)
                .Build();

            var data = new { Time = DateTime.Now.ToLongDateString(), Id="This is Client A" };
            HTTPExtension httpExtension = new HTTPExtension()
            {
                Verb = HTTPVerb.Post
            };
            while (true)
            {
                var a = await client.InvokeMethodAsync<object, SomeResponseBody>("dotnet-server-b", "talk", data, httpExtension);
                Console.WriteLine(a.Msg);
                await Task.Delay(5 * 1000);
            }
        }
    }

每間隔 5 秒向 ServiceB 發送一次請求。

啟動

dapr run --app-id dotnet5-http-client dotnet run

ClientA 接收內容:

== APP == This is ServiceB

SerivceB 接收內容:

== APP == This is Client A:2020年11月27日 星期五

== APP ==  recieve message:This is ServiceC

ServiceC 接收內容:

== APP == This is Service C.:2020年11月27日 星期五

總結

至此,DOTNET5 通過 dapr HTTP 調用的示例就結束了。

源碼地址