如何在.NET SDK中运行和使用 virtual actors

使用此示例试用 .NET Daprvirtual actor

通过Dapr actor 程序包,您可以与.NET应用程序中的Dapr virtual actors进行交互。 在本指南中,您将学习如何:

  • 创建一个 Actor (MyActor)。
  • 在客户端应用程序上调用其方法。
  1. MyActor --- MyActor.Interfaces
  2. |
  3. +- MyActorService
  4. |
  5. +- MyActorClient

接口项目(\MyActor\MyActor.Interfaces)

这个项目包含了 actor 的接口定义。 Actor 接口可以在任何项目中以任意的名称定义。 该接口定义了由以下 actor 共享的 actor 合约:

  • Actor实现
  • 调用actor的客户端

因为客户端项目可能依赖于它,最好将其定义在与actor实现分开的程序集中。

Actor 服务项目 (\MyActor\MyActorService)

该项目实现了托管 actor 的ASP.Net Core Web服务。 它包含了actor的实现,MyActor.cs。 一个 actor 实现是一个类,它:

  • 派生自基础类型Actor
  • 实现了MyActor.Interfaces项目中定义的接口。

一个 actor 类还必须实现一个构造函数,该构造函数接受一个 ActorService 实例和一个 ActorId,并将它们传递给基础 Actor 类。

Actor 客户端项目 (\MyActor\MyActorClient)

这个项目包含actor客户端的实现,它调用Actor接口中定义的MyActor的方法。

前期准备

第 0 步:准备

由于我们将创建3个项目,所以选择一个空的目录开始,在你选择的终端中打开它。

第 1 步:创建 actor 接口

Actor接口定义了actor的实现和调用actor的客户端之间的约定。

Actor接口的定义需要满足以下要求:

  • Actor接口必须继承Dapr.Actors.IActor接口
  • Actor方法的返回类型必须是Task或者Task<object>
  • Actor 方法最多只能有一个参数

创建接口项目并添加依赖

  1. # Create Actor Interfaces
  2. dotnet new classlib -o MyActor.Interfaces
  3. cd MyActor.Interfaces
  4. # Add Dapr.Actors nuget package. Please use the latest package version from nuget.org
  5. dotnet add package Dapr.Actors
  6. cd ..

定义 IMyActor 接口

定义IMyActor接口和MyData数据对象。 将以下代码粘贴到 MyActor.Interfaces 项目中的 MyActor.cs 文件中。

  1. using Dapr.Actors;
  2. using Dapr.Actors.Runtime;
  3. using System.Threading.Tasks;
  4. namespace MyActor.Interfaces
  5. {
  6. public interface IMyActor : IActor
  7. {
  8. Task<string> SetDataAsync(MyData data);
  9. Task<MyData> GetDataAsync();
  10. Task RegisterReminder();
  11. Task UnregisterReminder();
  12. Task<IActorReminder> GetReminder();
  13. Task RegisterTimer();
  14. Task UnregisterTimer();
  15. }
  16. public class MyData
  17. {
  18. public string PropertyA { get; set; }
  19. public string PropertyB { get; set; }
  20. public override string ToString()
  21. {
  22. var propAValue = this.PropertyA == null ? "null" : this.PropertyA;
  23. var propBValue = this.PropertyB == null ? "null" : this.PropertyB;
  24. return $"PropertyA: {propAValue}, PropertyB: {propBValue}";
  25. }
  26. }
  27. }

第 2 步:创建 actor 服务

Dapr 使用 ASP.NET web service来托管Actor服务。 本节将会实现IMyActor actor接口并将Actor注册到Dapr Runtime。

创建 actor 服务项目并添加依赖

  1. # Create ASP.Net Web service to host Dapr actor
  2. dotnet new web -o MyActorService
  3. cd MyActorService
  4. # Add Dapr.Actors.AspNetCore nuget package. Please use the latest package version from nuget.org
  5. dotnet add package Dapr.Actors.AspNetCore
  6. # Add Actor Interface reference
  7. dotnet add reference ../MyActor.Interfaces/MyActor.Interfaces.csproj
  8. cd ..

添加 actor 实现

实现IMyActor接口并继承自Dapr.Actors.Actor类。 下面的例子同样展示了如何使用Actor Reminders。 Actor如果要使用Reminders,则必须实现IRemindable接口 如果你不打算使用Reminder功能,你可以跳过下面代码中实现IRemindable接口和Reminder特定方法的操作。

将以下代码粘贴到 MyActorService 项目中的 MyActor.cs 文件中:

  1. using Dapr.Actors;
  2. using Dapr.Actors.Runtime;
  3. using MyActor.Interfaces;
  4. using System;
  5. using System.Threading.Tasks;
  6. namespace MyActorService
  7. {
  8. internal class MyActor : Actor, IMyActor, IRemindable
  9. {
  10. // The constructor must accept ActorHost as a parameter, and can also accept additional
  11. // parameters that will be retrieved from the dependency injection container
  12. //
  13. /// <summary>
  14. /// Initializes a new instance of MyActor
  15. /// </summary>
  16. /// <param name="host">The Dapr.Actors.Runtime.ActorHost that will host this actor instance.</param>
  17. public MyActor(ActorHost host)
  18. : base(host)
  19. {
  20. }
  21. /// <summary>
  22. /// This method is called whenever an actor is activated.
  23. /// An actor is activated the first time any of its methods are invoked.
  24. /// </summary>
  25. protected override Task OnActivateAsync()
  26. {
  27. // Provides opportunity to perform some optional setup.
  28. Console.WriteLine($"Activating actor id: {this.Id}");
  29. return Task.CompletedTask;
  30. }
  31. /// <summary>
  32. /// This method is called whenever an actor is deactivated after a period of inactivity.
  33. /// </summary>
  34. protected override Task OnDeactivateAsync()
  35. {
  36. // Provides Opporunity to perform optional cleanup.
  37. Console.WriteLine($"Deactivating actor id: {this.Id}");
  38. return Task.CompletedTask;
  39. }
  40. /// <summary>
  41. /// Set MyData into actor's private state store
  42. /// </summary>
  43. /// <param name="data">the user-defined MyData which will be stored into state store as "my_data" state</param>
  44. public async Task<string> SetDataAsync(MyData data)
  45. {
  46. // Data is saved to configured state store implicitly after each method execution by Actor's runtime.
  47. // Data can also be saved explicitly by calling this.StateManager.SaveStateAsync();
  48. // State to be saved must be DataContract serializable.
  49. await this.StateManager.SetStateAsync<MyData>(
  50. "my_data", // state name
  51. data); // data saved for the named state "my_data"
  52. return "Success";
  53. }
  54. /// <summary>
  55. /// Get MyData from actor's private state store
  56. /// </summary>
  57. /// <return>the user-defined MyData which is stored into state store as "my_data" state</return>
  58. public Task<MyData> GetDataAsync()
  59. {
  60. // Gets state from the state store.
  61. return this.StateManager.GetStateAsync<MyData>("my_data");
  62. }
  63. /// <summary>
  64. /// Register MyReminder reminder with the actor
  65. /// </summary>
  66. public async Task RegisterReminder()
  67. {
  68. await this.RegisterReminderAsync(
  69. "MyReminder", // The name of the reminder
  70. null, // User state passed to IRemindable.ReceiveReminderAsync()
  71. TimeSpan.FromSeconds(5), // Time to delay before invoking the reminder for the first time
  72. TimeSpan.FromSeconds(5)); // Time interval between reminder invocations after the first invocation
  73. }
  74. /// <summary>
  75. /// Get MyReminder reminder details with the actor
  76. /// </summary>
  77. public async Task<IActorReminder> GetReminder()
  78. {
  79. await this.GetReminderAsync("MyReminder");
  80. }
  81. /// <summary>
  82. /// Unregister MyReminder reminder with the actor
  83. /// </summary>
  84. public Task UnregisterReminder()
  85. {
  86. Console.WriteLine("Unregistering MyReminder...");
  87. return this.UnregisterReminderAsync("MyReminder");
  88. }
  89. // <summary>
  90. // Implement IRemindeable.ReceiveReminderAsync() which is call back invoked when an actor reminder is triggered.
  91. // </summary>
  92. public Task ReceiveReminderAsync(string reminderName, byte[] state, TimeSpan dueTime, TimeSpan period)
  93. {
  94. Console.WriteLine("ReceiveReminderAsync is called!");
  95. return Task.CompletedTask;
  96. }
  97. /// <summary>
  98. /// Register MyTimer timer with the actor
  99. /// </summary>
  100. public Task RegisterTimer()
  101. {
  102. return this.RegisterTimerAsync(
  103. "MyTimer", // The name of the timer
  104. nameof(this.OnTimerCallBack), // Timer callback
  105. null, // User state passed to OnTimerCallback()
  106. TimeSpan.FromSeconds(5), // Time to delay before the async callback is first invoked
  107. TimeSpan.FromSeconds(5)); // Time interval between invocations of the async callback
  108. }
  109. /// <summary>
  110. /// Unregister MyTimer timer with the actor
  111. /// </summary>
  112. public Task UnregisterTimer()
  113. {
  114. Console.WriteLine("Unregistering MyTimer...");
  115. return this.UnregisterTimerAsync("MyTimer");
  116. }
  117. /// <summary>
  118. /// Timer callback once timer is expired
  119. /// </summary>
  120. private Task OnTimerCallBack(byte[] data)
  121. {
  122. Console.WriteLine("OnTimerCallBack is called!");
  123. return Task.CompletedTask;
  124. }
  125. }
  126. }

使用 ASP.NET Core Startup 来注册 actor runtime

Actor runtime使用ASP.NET Core Startup.cs来配置。

运行时使用ASP.NET Core依赖注入系统来注册actor类型和基本服务。 这个集成是通过在 ConfigureServices(...) 中调用 AddActors(...) 方法来提供的。 使用传递到 AddActors(...) 方法的委托来注册actor类型并配置actor运行时设置。 您可以在ConfigureServices(...)内注册额外的类型以进行依赖注入。 它们都可以被注入到你的Actor类型的构造器。

Actors通过Dapr runtime使用HTTP调用来实现。 此功能是应用程序的HTTP处理管道的一部分,在Configure(...)方法中的UseEndpoints(...)内注册。

将以下代码粘贴到 MyActorService 项目中的 Startup.cs 文件中:

  1. using Microsoft.AspNetCore.Builder;
  2. using Microsoft.AspNetCore.Hosting;
  3. using Microsoft.Extensions.DependencyInjection;
  4. using Microsoft.Extensions.Hosting;
  5. namespace MyActorService
  6. {
  7. public class Startup
  8. {
  9. public void ConfigureServices(IServiceCollection services)
  10. {
  11. services.AddActors(options =>
  12. {
  13. // Register actor types and configure actor settings
  14. options.Actors.RegisterActor<MyActor>();
  15. });
  16. }
  17. public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
  18. {
  19. if (env.IsDevelopment())
  20. {
  21. app.UseDeveloperExceptionPage();
  22. }
  23. app.UseRouting();
  24. app.UseEndpoints(endpoints =>
  25. {
  26. // Register actors handlers that interface with the Dapr runtime.
  27. endpoints.MapActorsHandlers();
  28. });
  29. }
  30. }
  31. }

第 3 步:添加客户端

创建一个简单的控制台应用来调用actor服务。 Dapr SDK 提供 Actor 代理客户端来调用Actor接口中定义的actor方法。

创建 actor 客户端项目并添加依赖

  1. # Create Actor's Client
  2. dotnet new console -o MyActorClient
  3. cd MyActorClient
  4. # Add Dapr.Actors nuget package. Please use the latest package version from nuget.org
  5. dotnet add package Dapr.Actors
  6. # Add Actor Interface reference
  7. dotnet add reference ../MyActor.Interfaces/MyActor.Interfaces.csproj
  8. cd ..

使用强类型客户端调用 actor 方法

您可以使用 ActorProxy.Create<IMyActor>(..) 来创建一个强类型客户端,并调用 actor 上的方法。

将以下代码粘贴到 MyActorClient 项目中的 Program.cs 文件中:

  1. using System;
  2. using System.Threading.Tasks;
  3. using Dapr.Actors;
  4. using Dapr.Actors.Client;
  5. using MyActor.Interfaces;
  6. namespace MyActorClient
  7. {
  8. class Program
  9. {
  10. static async Task MainAsync(string[] args)
  11. {
  12. Console.WriteLine("Startup up...");
  13. // Registered Actor Type in Actor Service
  14. var actorType = "MyActor";
  15. // An ActorId uniquely identifies an actor instance
  16. // If the actor matching this id does not exist, it will be created
  17. var actorId = new ActorId("1");
  18. // Create the local proxy by using the same interface that the service implements.
  19. //
  20. // You need to provide the type and id so the actor can be located.
  21. var proxy = ActorProxy.Create<IMyActor>(actorId, actorType);
  22. // Now you can use the actor interface to call the actor's methods.
  23. Console.WriteLine($"Calling SetDataAsync on {actorType}:{actorId}...");
  24. var response = await proxy.SetDataAsync(new MyData()
  25. {
  26. PropertyA = "ValueA",
  27. PropertyB = "ValueB",
  28. });
  29. Console.WriteLine($"Got response: {response}");
  30. Console.WriteLine($"Calling GetDataAsync on {actorType}:{actorId}...");
  31. var savedData = await proxy.GetDataAsync();
  32. Console.WriteLine($"Got response: {savedData}");
  33. }
  34. }
  35. }

运行代码

您创建的项目现在可以测试示例。

  1. 运行 MyActorService

    由于 MyActorService 正在托管 Actors,因此需要使用 Dapr CLI 来运行。

    1. cd MyActorService
    2. dapr run --app-id myapp --app-port 5000 --dapr-http-port 3500 -- dotnet run

    您将在这个终端中看到 daprdMyActorService 的命令行输出。 您应该看到以下情况,这表明应用程序已成功启动。

    1. ...
    2. ℹ️ Updating metadata for app command: dotnet run
    3. You're up and running! Both Dapr and your app logs will appear here.
    4. == APP == info: Microsoft.Hosting.Lifetime[0]
    5. == APP == Now listening on: https://localhost:5001
    6. == APP == info: Microsoft.Hosting.Lifetime[0]
    7. == APP == Now listening on: http://localhost:5000
    8. == APP == info: Microsoft.Hosting.Lifetime[0]
    9. == APP == Application started. Press Ctrl+C to shut down.
    10. == APP == info: Microsoft.Hosting.Lifetime[0]
    11. == APP == Hosting environment: Development
    12. == APP == info: Microsoft.Hosting.Lifetime[0]
    13. == APP == Content root path: /Users/ryan/actortest/MyActorService
  2. 运行 MyActorClient

    MyActorClient作为客户端,它可以用dotnet run正常运行。

    打开一个新的终端窗口,并导航到 MyActorClient 目录。 然后运行此项目:

    1. dotnet run

    您应该看到命令行输出,如:

    1. Startup up...
    2. Calling SetDataAsync on MyActor:1...
    3. Got response: Success
    4. Calling GetDataAsync on MyActor:1...
    5. Got response: PropertyA: ValueA, PropertyB: ValueB

💡 这个示例依赖于几个假设。 ASP.NET Core Web 项目的默认监听端口是 5000,它被作为 dapr run--app-port 5000 参数传递。 Dapr sidecar 的默认 HTTP 端口是 3500。 我们告诉MyActorService的sidecar使用3500,以便MyActorClient可以依赖默认值。

现在您已经成功创建了 actor 服务和客户端。 查看相关链接部分了解更多信息。

相关链接