diff --git a/docs/en/Dapr/Index.md b/docs/en/Dapr/Index.md new file mode 100644 index 0000000000..4cdf28c7f9 --- /dev/null +++ b/docs/en/Dapr/Index.md @@ -0,0 +1,469 @@ +# ABP Dapr Integration + +> This document assumes that you are already familiar with [Dapr](https://dapr.io/) and you want to use it in your ABP based applications. + +[Dapr](https://dapr.io/) (Distributed Application Runtime) provides APIs that simplify microservice connectivity. It is an open source project that is mainly backed by Microsoft. It is also a CNCF (Cloud Native Computing Foundation) project and trusted by community. + +ABP and Dapr has some intersecting features like service-to-service communication, distributed message bus and distributed locking. However their purposes are totally different. ABP's goal is to provide an end-to-end developer experience by offering an opinionated architecture and providing the necessary infrastructure libraries, reusable modules and tools to implement that architecture properly. Dapr's purpose, on the other hand, is to provide a runtime to decouple common microservice communication patterns from your application logic. + +ABP and Dapr can perfectly work together in the same application. ABP offers some packages to provide better integration where Dapr features intersect with ABP. You can use other Dapr features with no ABP integration packages based on [its own documentation](https://docs.dapr.io/). + +## ABP Dapr Integration Packages + +ABP provides the following NuGet packages for the Dapr integration: + +* [Volo.Abp.Dapr](https://www.nuget.org/packages/Volo.Abp.Dapr): The main Dapr integration package. All other packages depend on this package. +* [Volo.Abp.Http.Client.Dapr](https://www.nuget.org/packages/Volo.Abp.Http.Client.Dapr): Integration package for ABP's [dynamic](../API/Dynamic-CSharp-API-Clients.md) and [static](../API/Static-CSharp-API-Clients.md) C# API Client Proxies systems with Dapr's [service invocation](https://docs.dapr.io/developing-applications/building-blocks/service-invocation/service-invocation-overview/) building block. +* [Volo.Abp.EventBus.Dapr](https://www.nuget.org/packages/Volo.Abp.EventBus.Dapr): Implements ABP's distributed event bus with Dapr's [publish & subscribe](https://docs.dapr.io/developing-applications/building-blocks/pubsub/) building block. With this package, you can send events, but can not receive. +* [Volo.Abp.AspNetCore.Mvc.Dapr.EventBus](https://www.nuget.org/packages/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus): Provides the endpoints to receive events from Dapr's [publish & subscribe](https://docs.dapr.io/developing-applications/building-blocks/pubsub/) building block. Use this package to send and receive events. +* [Volo.Abp.DistributedLocking.Dapr](https://www.nuget.org/packages/Volo.Abp.DistributedLocking.Dapr): Uses Dapr's [distributed lock](https://docs.dapr.io/developing-applications/building-blocks/distributed-lock/) building block for [distributed locking](Distributed-Locking.md) service of the ABP Framework. + +In the following sections, we will see how to use these packages to use Dapr in your ABP based solutions. + +## Basics + +### Installation + +> This section explains how to add [Volo.Abp.Dapr](https://www.nuget.org/packages/Volo.Abp.Dapr), the core Dapr integration package to your project. If you are using one of the other Dapr integration packages, you can skip this section since this package will be indirectly added. + +Use the ABP CLI to add [Volo.Abp.Dapr](https://www.nuget.org/packages/Volo.Abp.Dapr) NuGet package to your project: + +* Install the [ABP CLI](https://docs.abp.io/en/abp/latest/CLI) if you haven't installed before. +* Open a command line (terminal) in the directory of the `.csproj` file you want to add the `Volo.Abp.Dapr` package. +* Run `abp add-package Volo.Abp.Dapr` command. + +If you want to do it manually, install the [Volo.Abp.Dapr](https://www.nuget.org/packages/Volo.Abp.Dapr) NuGet package to your project and add `[DependsOn(typeof(AbpDaprModule))]` to the [ABP module](Module-Development-Basics.md) class inside your project. + +### AbpDaprOptions + +`AbpDaprOptions` is the main [options class](../Options.md) that you can configure the global Dapr settings. **All settings are optional and you mostly don't need to configure them.** If you need, you can configure it in the `ConfigureServices` method of your [module class](../Module-Development-Basics.md): + +````csharp +Configure(options => +{ + // ... +}); +```` + +Available properties of the `AbpDaprOptions` class: + +* `HttpEndpoint` (optional): HTTP endpoint that is used while creating a `DaprClient` object. If you don't specify, the default value is used. +* `GrpcEndpoint` (optional): The gRPC endpoint that is used while creating a `DaprClient` object. If you don't specify, the default value is used. +* `DaprApiToken` (optional): The [Dapr API token](https://docs.dapr.io/operations/security/api-token/) that is used while sending requests from the application to Dapr. It is filled from the `DAPR_API_TOKEN` environment variable by default (which is set by Dapr once it is configured). See the *Security* section in this document for details. +* `AppApiToken` (optional): The [App API token](https://docs.dapr.io/operations/security/app-api-token/) that is used to validate requests coming from Dapr. It is filled from the `APP_API_TOKEN` environment variable by default (which is set by Dapr once it is configured). See the *Security* section in this document for details. + +Alternatively, you can configure the options in the `Dapr` section of your `appsettings.json` file. Example: + +````csharp +"Dapr": { + "HttpEndpoint": "http://localhost:3500/" +} +```` + +### Injecting DaprClient + +ABP registers the `DaprClient` class to the [dependency injection](../Dependency-Injection.md) system. So, you can inject and use it whenever you need: + +````csharp +public class MyService : ITransientDependency +{ + private readonly DaprClient _daprClient; + + public MyService(DaprClient daprClient) + { + _daprClient = daprClient; + } + + public async Task DoItAsync() + { + // TODO: Use the injected _daprClient object + } +} +```` + +Injecting `DaprClient` is the recommended way of using it in your application code. When you inject it, the `IAbpDaprClientFactory` service is used to create it, which is explained in the next section. + +### IAbpDaprClientFactory + +`IAbpDaprClientFactory` can be used to create `DaprClient` or `HttpClient` objects to perform operations on Dapr. It uses `AbpDaprOptions`, so you can configure the settings in a central place. + +**Example usages:** + +````csharp +public class MyService : ITransientDependency +{ + private readonly IAbpDaprClientFactory _daprClientFactory; + + public MyService(IAbpDaprClientFactory daprClientFactory) + { + _daprClientFactory = daprClientFactory; + } + + public async Task DoItAsync() + { + // Create a DaprClient object with default options + DaprClient daprClient = await _daprClientFactory.CreateAsync(); + + /* Create a DaprClient object with configuring + * the DaprClientBuilder object */ + DaprClient daprClient2 = await _daprClientFactory + .CreateAsync(builder => + { + builder.UseDaprApiToken("..."); + }); + + // Create an HttpClient object + HttpClient httpClient = await _daprClientFactory + .CreateHttpClientAsync("target-app-id"); + } +} +```` + +`CreateHttpClientAsync` method also gets optional `daprEndpoint` and `daprApiToken` parameters. + +> ABP uses `IAbpDaprClientFactory` when it needs to create a Dapr client. You can also use Dapr API to create client objects in your application. Using `IAbpDaprClientFactory` is recommended, but not required. + +## C# API Client Proxies Integration + +ABP can [dynamically](../API/Dynamic-CSharp-API-Clients.md) or [statically](../API/Static-CSharp-API-Clients.md) generate proxy classes to invoke your HTTP APIs from a Dotnet client application. It makes dead simple to consume HTTP APIs in a distributed system. [Volo.Abp.Http.Client.Dapr](https://www.nuget.org/packages/Volo.Abp.Http.Client.Dapr) package configures the client-side proxies system, so it uses Dapr's service invocation building block for the communication between your applications. + +### Installation + +Use the ABP CLI to add [Volo.Abp.Http.Client.Dapr](https://www.nuget.org/packages/Volo.Abp.Http.Client.Dapr) NuGet package to your project (to the client side): + +* Install the [ABP CLI](https://docs.abp.io/en/abp/latest/CLI) if you haven't installed before. +* Open a command line (terminal) in the directory of the `.csproj` file you want to add the `Volo.Abp.Http.Client.Dapr` package. +* Run `abp add-package Volo.Abp.Http.Client.Dapr` command. + +If you want to do it manually, install the [Volo.Abp.Http.Client.Dapr](https://www.nuget.org/packages/Volo.Abp.Http.Client.Dapr) NuGet package to your project and add `[DependsOn(typeof(AbpHttpClientDaprModule))]` to the [ABP module](Module-Development-Basics.md) class inside your project. + +### Configuration + +One you install the [Volo.Abp.Http.Client.Dapr](https://www.nuget.org/packages/Volo.Abp.Http.Client.Dapr) NuGet package, all you need to do it to configure ABP's remote services option either in `appsettings.json` or using the `AbpRemoteServiceOptions` [options class](../Options.md). + +**Example:** + +````csharp +{ + "RemoteServices": { + "Default": { + "BaseUrl": "http://dapr-httpapi/" + } + } +} +```` + +`dapr-httpapi` in this example is the application id of the server application in your Dapr configuration. + +The remove service name (`Default` in this example) should match the remote service name specified in the `AddHttpClientProxies` call for dynamic client proxies or `AddStaticHttpClientProxies` call for static client proxies. Using `Default` is fine if your client communicates to a single server. However, if your client uses multiple servers, you typically have multiple keys in the `RemoteServices` configuration. Once you configure the remote service endpoints as Dapr application ids, it will automatically work and make the HTTP calls through Dapr when you use ABP's client proxy system. + +> See the [dynamic](../API/Dynamic-CSharp-API-Clients.md) and [static](../API/Static-CSharp-API-Clients.md) client proxy documents for details about the ABP's client proxy system. + +## Distributed Event Bus Integration + +[ABP's distributed event bus](../Distributed-Event-Bus.md) system provides a convenient abstraction to allow applications communicate asynchronously via events. ABP has integration packages with various distributed messaging systems, like RabbitMQ, Kafka, and Azure. Dapr also has a [publish & subscribe building block](https://docs.dapr.io/developing-applications/building-blocks/pubsub/pubsub-overview/) for the same purpose: distributed messaging / events. + +ABP's [Volo.Abp.EventBus.Dapr](https://www.nuget.org/packages/Volo.Abp.EventBus.Dapr) and [Volo.Abp.AspNetCore.Mvc.Dapr.EventBus](https://www.nuget.org/packages/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus) packages make possible to use the Dapr infrastructure for ABP's distributed event bus. + +The [Volo.Abp.EventBus.Dapr](https://www.nuget.org/packages/Volo.Abp.EventBus.Dapr) package can be used by any type of application (e.g., a Console or ASP.NET Core application) to publish events through Dapr. To be able to receive messages (by subscribing events), you need to have the [Volo.Abp.AspNetCore.Mvc.Dapr.EventBus](https://www.nuget.org/packages/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus) package installed, and your application should be an ASP.NET Core application. + +### Installation + +If your application is an ASP.NET Core application and you want to send and receive events, you need to install the [Volo.Abp.AspNetCore.Mvc.Dapr.EventBus](https://www.nuget.org/packages/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus) package as describe below: + +* Install the [ABP CLI](https://docs.abp.io/en/abp/latest/CLI) if you haven't installed before. +* Open a command line (terminal) in the directory of the `.csproj` file you want to add the `Volo.Abp.AspNetCore.Mvc.Dapr.EventBus` package. +* Run `abp add-package Volo.Abp.AspNetCore.Mvc.Dapr.EventBus` command. + +If you want to do it manually, install the [Volo.Abp.AspNetCore.Mvc.Dapr.EventBus](https://www.nuget.org/packages/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus) NuGet package to your project and add `[DependsOn(typeof(AbpAspNetCoreMvcDaprEventBusModule))]` to the [ABP module](Module-Development-Basics.md) class inside your project. + +> **If you install the [Volo.Abp.AspNetCore.Mvc.Dapr.EventBus](https://www.nuget.org/packages/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus) package, you don't need to install the [Volo.Abp.EventBus.Dapr](https://www.nuget.org/packages/Volo.Abp.EventBus.Dapr) package, because the first one already has a reference to the latter one.** + +If your application is not an ASP.NET Core application, you can't receive events from Dapr, at least with ABP's integration packages (see [Dapr's document](https://docs.dapr.io/developing-applications/building-blocks/pubsub/howto-publish-subscribe/) if you want to receive events in a different type of application). However, you can still publish messages using the [Volo.Abp.EventBus.Dapr](https://www.nuget.org/packages/Volo.Abp.EventBus.Dapr) package. In this case, follow the steps below to install that package to your project: + +* Install the [ABP CLI](https://docs.abp.io/en/abp/latest/CLI) if you haven't installed before. +* Open a command line (terminal) in the directory of the `.csproj` file you want to add the `Volo.Abp.EventBus.Dapr` package. +* Run `abp add-package Volo.Abp.EventBus.Dapr` command. + +If you want to do it manually, install the [Volo.Abp.EventBus.Dapr](https://www.nuget.org/packages/Volo.Abp.EventBus.Dapr) NuGet package to your project and add `[DependsOn(typeof(AbpEventBusDaprModule))]` to the [ABP module](Module-Development-Basics.md) class inside your project. + +### Configuration + +You can configure the `AbpDaprEventBusOptions` [options class](../Options.md) for Dapr configuration: + +````csharp +Configure(options => +{ + options.PubSubName = "pubsub"; +}); +```` + +Available properties of the `AbpDaprEventBusOptions` class: + +* `PubSubName` (optional): The `pubsubName` parameter while publishing messages through `DaprClient.PublishEventAsync` method. Default value: `pubsub`. + +### The ABP Subscription Endpoints + +ABP provides the following endpoints to receive events from Dapr: + +* `dapr/subscribe`: Dapr uses this endpoint to get a list of subscriptions from the application. ABP automatically returns all the subscriptions for your distributed event handler classes and custom controller actions with the `Topic` attribute. +* `api/abp/dapr/event`: The unified endpoint to receive all the events from Dapr. ABP dispatches the events to your event handlers based on the topic name. + +> **Since ABP provides the standard `dapr/subscribe` endpoint, you should not manually call the `app.MapSubscribeHandler()` method of Dapr.** You can use the `app.UseCloudEvents()` middleware in your ASP.NET Core pipeline if you want to support the [CloudEvents](https://cloudevents.io/) standard. + +### Usage + +#### The ABP Way + +You can follow [ABP's distributed event bus documentation](../Distributed-Event-Bus.md) to learn how to publish and subscribe to events in the ABP way. No change required in your application code to use Dapr pub-sub. ABP will automatically subscribe to Dapr for your event handler classes (those implement the `IDistributedEventHandler` interface). + +ABP provides `api/abp/dapr/event` + +**Example: Publish an event using the `IDistributedEventBus` service** + +````csharp +public class MyService : ITransientDependency +{ + private readonly IDistributedEventBus _distributedEventBus; + + public MyService(IDistributedEventBus distributedEventBus) + { + _distributedEventBus = distributedEventBus; + } + + public async Task DoItAsync() + { + await _distributedEventBus.PublishAsync(new StockCountChangedEto + { + ProductCode = "AT837234", + NewStockCount = 42 + }); + } +} +```` + +**Example: Subscribe to an event by implementing the `IDistributedEventHandler` interface** + +````csharp +public class MyHandler : + IDistributedEventHandler, + ITransientDependency +{ + public async Task HandleEventAsync(StockCountChangedEto eventData) + { + var productCode = eventData.ProductCode; + // ... + } +} +```` + +See [ABP's distributed event bus documentation](../Distributed-Event-Bus.md) to learn the details. + +#### Using the Dapr API + +In addition to ABP's standard distributed event bus system, you can also use Dapr's API to publish events. + +> If you directly use the Dapr API to publish events, you may not benefit from ABP's standard distributed event bus features, like the outbox/inbox pattern implementation. + +**Example: Publish an event using `DaprClient`** + +````csharp +public class MyService : ITransientDependency +{ + private readonly DaprClient _daprClient; + + public MyService(DaprClient daprClient) + { + _daprClient = daprClient; + } + + public async Task DoItAsync() + { + await _daprClient.PublishEventAsync( + "pubsub", // pubsub name + "StockChanged", // topic name + new StockCountChangedEto // event data + { + ProductCode = "AT837234", + NewStockCount = 42 + } + ); + } +} +```` + +**Example: Subscribe to an event by creating an ASP.NET Core controller** + +````csharp +public class MyController : AbpController +{ + [HttpPost("/stock-changed")] + [Topic("pubsub", "StockChanged")] + public async Task TestRouteAsync( + [FromBody] StockCountChangedEto model) + { + HttpContext.ValidateDaprAppApiToken(); + + // Do something with the event + return Ok(); + } +} +```` + +`HttpContext.ValidateDaprAppApiToken()` extension method is provided by ABP to check if the request is coming from Dapr. This is optional. You should configure Dapr to send App API token to your application if you want to enable the validation. If not configured, `ValidateDaprAppApiToken()` does nothing. See [Dapr's App API Token document](https://docs.dapr.io/operations/security/app-api-token/) for more information. Also see the *AbpDaprOptions* and *Security* sections in this document. + +See the [Dapr documentation](https://docs.microsoft.com/en-us/dotnet/architecture/dapr-for-net-developers/publish-subscribe) to learn the details of sending & receiving events with Dapr API. + +## Distributed Lock + +> Dapr's distributed lock feature currently in Alpha stage and may not be stable yet. It is not suggested to replace ABP's distributed lock with Dapr in that point. + +ABP provides a [Distributed Locking](../Distributed-Locking.md) abstraction to control access to a shared resource by multiple applications. Dapr also has a [distributed lock building block](https://docs.dapr.io/developing-applications/building-blocks/distributed-lock/). [Volo.Abp.DistributedLocking.Dapr](https://www.nuget.org/packages/Volo.Abp.DistributedLocking.Dapr) package makes ABP using Dapr's distributed locking system. + +### Installation + +Use the ABP CLI to add [Volo.Abp.DistributedLocking.Dapr](https://www.nuget.org/packages/Volo.Abp.DistributedLocking.Dapr) NuGet package to your project (to the client side): + +* Install the [ABP CLI](https://docs.abp.io/en/abp/latest/CLI) if you haven't installed before. +* Open a command line (terminal) in the directory of the `.csproj` file you want to add the `Volo.Abp.DistributedLocking.Dapr` package. +* Run `abp add-package Volo.Abp.DistributedLocking.Dapr` command. + +If you want to do it manually, install the [Volo.Abp.DistributedLocking.Dapr](https://www.nuget.org/packages/Volo.Abp.DistributedLocking.Dapr) NuGet package to your project and add `[DependsOn(typeof(AbpDistributedLockingDaprModule))]` to the [ABP module](../Module-Development-Basics.md) class inside your project. + +### Configuration + +You can use the `AbpDistributedLockDaprOptions` options class in the `ConfigureServices` method of [your module](../Module-Development-Basics.md) to configure the Dapr distributed lock: + +````csharp +Configure(options => +{ + options.StoreName = "mystore"; +}); +```` + +The following options are available: + +* **`StoreName`** (required): The store name used by Dapr. Lock key names are scoped in the same store. That means different applications can acquire the same lock name in different stores. Use the same store name for the same resources you want to control access. +* `Owner` (optional): The `owner` value used by `DaprClient.Lock` method. If you don't specify, ABP uses a random value, which is fine in general. +* `DefaultExpirationTimeout` (optional): Default value of the time after which the lock gets expired. Default value: 2 minutes. + +### Usage + +You can inject and use the `IAbpDistributedLock` service, just like explained in the [Distributed Locking document](../Distributed-Locking.md). + +**Example:** + +````csharp +public class MyService : ITransientDependency +{ + private readonly IAbpDistributedLock _distributedLock; + + public MyService(IAbpDistributedLock distributedLock) + { + _distributedLock = distributedLock; + } + + public async Task MyMethodAsync() + { + await using (var handle = + await _distributedLock.TryAcquireAsync("MyLockName")) + { + if (handle != null) + { + // your code that access the shared resource + } + } + } +} +```` + +There are two points we should mention about the `TryAcquireAsync` method, as different from the ABP's standard usage: + +* `timeout` parameter currently not used (even if you specify it), because Dapr doesn't support to wait to obtain the lock. +* Dapr uses expiration timeout system (that means the lock is automatically released after that timeout even if you don't release the lock by disposing the handler). However, ABP's `TryAcquireAsync` method has no such a parameter. Currently, you can set `AbpDistributedLockDaprOptions.DefaultExpirationTimeout` as a global value in your application. + +As mentioned first, Dapr's distributed lock feature currently in Alpha stage and its API is candidate to change. You should use it as is if you want, but be ready for the changes in the future. For now, we are recommending to use the [DistributedLock](https://github.com/madelson/DistributedLock) library as explained in ABP's [Distributed Locking document](../Distributed-Locking.md). + +## Security + +If you are using Dapr, most or all the incoming and outgoing requests in your application pass through Dapr. Dapr uses two kinds of API tokens to secure the communication between your application and Dapr. + +### Dapr API Token + +> This token is automatically set by default and generally you don't care about it. + +The [Enable API token authentication in Dapr](https://docs.dapr.io/operations/security/api-token/) document describes what the Dapr API token is and how it is configured. Please read that document if you want to enable it for your application. + +If you enabled the Dapr API token, you should send that token in every request to Dapr from your application. `AbpDaprOptions` defines a `DaprApiToken` property as a central point to configure the Dapr API token in your application. + +The default value of the `DaprApiToken` property is set from the `DAPR_API_TOKEN` environment variable and that environment variable is set by Dapr when it runs. So, most of the times, you don't need to configure `AbpDaprOptions.DaprApiToken` in your application. However, if you need to configure (or override) it, you can do in the `ConfigureServices` method of your module class as shown in the following code block: + +````csharp +Configure(options => +{ + options.DaprApiToken = "..."; +}); +```` + +Or you can set it in your `appsettings.json` file: + +````json +"Dapr": { + "DaprApiToken": "..." +} +```` + +Once you set it, it is used when you inject `DaprClient` or use `IAbpDaprClientFactory`. If you need that value in your application, you can inject `IDaprApiTokenProvider` and use its `GetDaprApiToken()` method. + +### App API Token + +> Enabling App API token validation is strongly recommended. Otherwise, for example, any client can directly call your event subscription endpoint, and your application acts like an event has occurred (if there is no other security policy in your event subscription endpoint). + +The [Authenticate requests from Dapr using token authentication](https://docs.dapr.io/operations/security/app-api-token/) document describes what the App API token is and how it is configured. Please read that document if you want to enable it for your application. + +If you enabled the App API token, you can validate it to ensure that the request is coming from Dapr. ABP provides useful shortcuts to validate it. + +**Example: Validate the App API token in an event handling HTTP API** + +````csharp +public class MyController : AbpController +{ + [HttpPost("/stock-changed")] + [Topic("pubsub", "StockChanged")] + public async Task TestRouteAsync( + [FromBody] StockCountChangedEto model) + { + // Validate the App API token! + HttpContext.ValidateDaprAppApiToken(); + + // Do something with the event + return Ok(); + } +} +```` + +`HttpContext.ValidateDaprAppApiToken()` is an extension method provided by the ABP Framework. It throws an `AbpAuthorizationException` if the token was missing or wrong in the HTTP header (the header name is `dapr-api-token`). You can also inject `IDaprAppApiTokenValidator` and use its methods to validate the token in any service (not only in a controller class). + +You can configure `AbpDaprOptions.AppApiToken` if you want to set (or override) the App API token value. The default value is set by the `APP_API_TOKEN` environment variable. You can change it in the `ConfigureServices` method of your module class as shown in the following code block: + +````csharp +Configure(options => +{ + options.AppApiToken = "..."; +}); +```` + +Or you can set it in your `appsettings.json` file: + +````json +"Dapr": { + "AppApiToken": "..." +} +```` + +If you need that value in your application, you can inject `IDaprApiTokenProvider` and use its `GetAppApiToken()` method. + +## See Also + +* [Dapr for .NET Developers](https://docs.microsoft.com/en-us/dotnet/architecture/dapr-for-net-developers/) +* [The Official Dapr Documentation](https://docs.dapr.io/) \ No newline at end of file diff --git a/docs/en/docs-nav.json b/docs/en/docs-nav.json index 035f150705..ed9cb37135 100644 --- a/docs/en/docs-nav.json +++ b/docs/en/docs-nav.json @@ -1283,6 +1283,10 @@ } ] }, + { + "text": "Dapr Integration", + "path": "Dapr/Index.md" + }, { "text": "Testing", "path": "Testing.md" diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus/Volo/Abp/AspNetCore/Mvc/Dapr/EventBus/Controllers/AbpAspNetCoreMvcDaprPubSubController.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus/Volo/Abp/AspNetCore/Mvc/Dapr/EventBus/Controllers/AbpAspNetCoreMvcDaprPubSubController.cs index c97e793b53..674e227cd0 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus/Volo/Abp/AspNetCore/Mvc/Dapr/EventBus/Controllers/AbpAspNetCoreMvcDaprPubSubController.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus/Volo/Abp/AspNetCore/Mvc/Dapr/EventBus/Controllers/AbpAspNetCoreMvcDaprPubSubController.cs @@ -21,6 +21,8 @@ public class AbpAspNetCoreMvcDaprPubSubController : AbpController [HttpPost(AbpAspNetCoreMvcDaprPubSubConsts.DaprEventCallbackUrl)] public virtual async Task EventsAsync() { + this.HttpContext.ValidateDaprAppApiToken(); + var bodyJsonDocument = await JsonDocument.ParseAsync(HttpContext.Request.Body); var request = JsonSerializer.Deserialize(bodyJsonDocument.RootElement.GetRawText(), HttpContext.RequestServices.GetRequiredService>().Value.JsonSerializerOptions); diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus/Volo/Abp/AspNetCore/Mvc/Dapr/EventBus/SystemTextJson/AbpAspNetCoreMvcDaprSubscriptionDefinitionConverter.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus/Volo/Abp/AspNetCore/Mvc/Dapr/EventBus/SystemTextJson/AbpAspNetCoreMvcDaprSubscriptionDefinitionConverter.cs index fa26d35ca6..bcd2a0e1be 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus/Volo/Abp/AspNetCore/Mvc/Dapr/EventBus/SystemTextJson/AbpAspNetCoreMvcDaprSubscriptionDefinitionConverter.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus/Volo/Abp/AspNetCore/Mvc/Dapr/EventBus/SystemTextJson/AbpAspNetCoreMvcDaprSubscriptionDefinitionConverter.cs @@ -6,7 +6,7 @@ namespace Volo.Abp.AspNetCore.Mvc.Dapr.EventBus.SystemTextJson; public class AbpAspNetCoreMvcDaprSubscriptionDefinitionConverter : JsonConverter { - private JsonSerializerOptions _writeJsonSerializerOptions; + private JsonSerializerOptions? _writeJsonSerializerOptions; public override AbpAspNetCoreMvcDaprSubscriptionDefinition Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr/Volo/Abp/AspNetCore/Mvc/Dapr/DaprAppApiTokenValidator.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr/Volo/Abp/AspNetCore/Mvc/Dapr/DaprAppApiTokenValidator.cs new file mode 100644 index 0000000000..817eb9824d --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr/Volo/Abp/AspNetCore/Mvc/Dapr/DaprAppApiTokenValidator.cs @@ -0,0 +1,75 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.SignalR; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.Authorization; +using Volo.Abp.Dapr; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.AspNetCore.Mvc.Dapr; + +public class DaprAppApiTokenValidator : IDaprAppApiTokenValidator, ISingletonDependency +{ + protected IHttpContextAccessor HttpContextAccessor { get; } + protected HttpContext HttpContext => GetHttpContext(); + + public DaprAppApiTokenValidator(IHttpContextAccessor httpContextAccessor) + { + HttpContextAccessor = httpContextAccessor; + } + + public virtual void CheckDaprAppApiToken() + { + var expectedAppApiToken = GetConfiguredAppApiTokenOrNull(); + if (expectedAppApiToken.IsNullOrWhiteSpace()) + { + return; + } + + var headerAppApiToken = GetDaprAppApiTokenOrNull(); + if (headerAppApiToken.IsNullOrWhiteSpace()) + { + throw new AbpAuthorizationException("Expected Dapr App API Token is not provided! Dapr should set the 'dapr-api-token' HTTP header."); + } + + if (expectedAppApiToken != headerAppApiToken) + { + throw new AbpAuthorizationException("The Dapr App API Token (provided in the 'dapr-api-token' HTTP header) doesn't match the expected value!"); + } + } + + public virtual bool IsValidDaprAppApiToken() + { + var expectedAppApiToken = GetConfiguredAppApiTokenOrNull(); + if (expectedAppApiToken.IsNullOrWhiteSpace()) + { + return true; + } + + var headerAppApiToken = GetDaprAppApiTokenOrNull(); + return expectedAppApiToken == headerAppApiToken; + } + + public virtual string? GetDaprAppApiTokenOrNull() + { + string apiTokenHeader = HttpContext.Request.Headers["dapr-api-token"]; + if (string.IsNullOrEmpty(apiTokenHeader) || apiTokenHeader.Length < 1) + { + return null; + } + + return apiTokenHeader; + } + + protected virtual string? GetConfiguredAppApiTokenOrNull() + { + return HttpContext + .RequestServices + .GetRequiredService() + .GetAppApiToken(); + } + + protected virtual HttpContext GetHttpContext() + { + return HttpContextAccessor.HttpContext ?? throw new AbpException("HttpContext is not available!"); + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr/Volo/Abp/AspNetCore/Mvc/Dapr/DaprHttpContextExtensions.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr/Volo/Abp/AspNetCore/Mvc/Dapr/DaprHttpContextExtensions.cs new file mode 100644 index 0000000000..15663d0c3c --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr/Volo/Abp/AspNetCore/Mvc/Dapr/DaprHttpContextExtensions.cs @@ -0,0 +1,31 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; + +namespace Volo.Abp.AspNetCore.Mvc.Dapr; + +public static class DaprHttpContextExtensions +{ + public static void ValidateDaprAppApiToken(this HttpContext httpContext) + { + httpContext + .RequestServices + .GetRequiredService() + .CheckDaprAppApiToken(); + } + + public static bool IsValidDaprAppApiToken(this HttpContext httpContext) + { + return httpContext + .RequestServices + .GetRequiredService() + .IsValidDaprAppApiToken(); + } + + public static string? GetDaprAppApiTokenOrNull(HttpContext httpContext) + { + return httpContext + .RequestServices + .GetRequiredService() + .GetDaprAppApiTokenOrNull(); + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr/Volo/Abp/AspNetCore/Mvc/Dapr/IDaprAppApiTokenValidator.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr/Volo/Abp/AspNetCore/Mvc/Dapr/IDaprAppApiTokenValidator.cs new file mode 100644 index 0000000000..ed5f281ea8 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr/Volo/Abp/AspNetCore/Mvc/Dapr/IDaprAppApiTokenValidator.cs @@ -0,0 +1,8 @@ +namespace Volo.Abp.AspNetCore.Mvc.Dapr; + +public interface IDaprAppApiTokenValidator +{ + void CheckDaprAppApiToken(); + bool IsValidDaprAppApiToken(); + string? GetDaprAppApiTokenOrNull(); +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Dapr/Volo/Abp/Dapr/AbpDaprClientFactory.cs b/framework/src/Volo.Abp.Dapr/Volo/Abp/Dapr/AbpDaprClientFactory.cs index faaf450ddc..ad284ce156 100644 --- a/framework/src/Volo.Abp.Dapr/Volo/Abp/Dapr/AbpDaprClientFactory.cs +++ b/framework/src/Volo.Abp.Dapr/Volo/Abp/Dapr/AbpDaprClientFactory.cs @@ -1,5 +1,4 @@ -using System.Collections.Concurrent; -using System.Text.Json; +using System.Text.Json; using Dapr.Client; using Microsoft.Extensions.Options; using Volo.Abp.DependencyInjection; @@ -7,42 +6,68 @@ using Volo.Abp.Json.SystemTextJson; namespace Volo.Abp.Dapr; -public class AbpDaprClientFactory : ITransientDependency +public class AbpDaprClientFactory : IAbpDaprClientFactory, ISingletonDependency { - protected AbpDaprOptions Options { get; } - protected AbpSystemTextJsonSerializerOptions SystemTextJsonSerializerOptions { get; } + protected AbpDaprOptions DaprOptions { get; } + protected JsonSerializerOptions JsonSerializerOptions { get; } + protected IDaprApiTokenProvider DaprApiTokenProvider { get; } public AbpDaprClientFactory( IOptions options, - IOptions systemTextJsonSerializerOptions) + IOptions systemTextJsonSerializerOptions, + IDaprApiTokenProvider daprApiTokenProvider) { - Options = options.Value; - SystemTextJsonSerializerOptions = systemTextJsonSerializerOptions.Value; + DaprApiTokenProvider = daprApiTokenProvider; + DaprOptions = options.Value; + JsonSerializerOptions = CreateJsonSerializerOptions(systemTextJsonSerializerOptions.Value); } - public virtual async Task CreateAsync() + public virtual DaprClient Create(Action? builderAction = null) { var builder = new DaprClientBuilder() - .UseJsonSerializationOptions(await CreateJsonSerializerOptions()); + .UseJsonSerializationOptions(JsonSerializerOptions); - if (!Options.HttpEndpoint.IsNullOrWhiteSpace()) + if (!DaprOptions.HttpEndpoint.IsNullOrWhiteSpace()) { - builder.UseHttpEndpoint(Options.HttpEndpoint); + builder.UseHttpEndpoint(DaprOptions.HttpEndpoint); } - if (!Options.GrpcEndpoint.IsNullOrWhiteSpace()) + if (!DaprOptions.GrpcEndpoint.IsNullOrWhiteSpace()) { - builder.UseGrpcEndpoint(Options.GrpcEndpoint); + builder.UseGrpcEndpoint(DaprOptions.GrpcEndpoint); } + var apiToken = DaprApiTokenProvider.GetDaprApiToken(); + if (!apiToken.IsNullOrWhiteSpace()) + { + builder.UseDaprApiToken(apiToken); + } + + builderAction?.Invoke(builder); + return builder.Build(); } - private readonly static ConcurrentDictionary JsonSerializerOptionsCache = new ConcurrentDictionary(); + public virtual HttpClient CreateHttpClient( + string? appId = null, + string? daprEndpoint = null, + string? daprApiToken = null) + { + if(daprEndpoint.IsNullOrWhiteSpace() && + !DaprOptions.HttpEndpoint.IsNullOrWhiteSpace()) + { + daprEndpoint = DaprOptions.HttpEndpoint; + } - protected virtual Task CreateJsonSerializerOptions() + return DaprClient.CreateInvokeHttpClient( + appId, + daprEndpoint, + daprApiToken ?? DaprApiTokenProvider.GetDaprApiToken() + ); + } + + protected virtual JsonSerializerOptions CreateJsonSerializerOptions(AbpSystemTextJsonSerializerOptions systemTextJsonSerializerOptions) { - return Task.FromResult(JsonSerializerOptionsCache.GetOrAdd(nameof(AbpDaprClientFactory), - _ => new JsonSerializerOptions(SystemTextJsonSerializerOptions.JsonSerializerOptions))); + return new JsonSerializerOptions(systemTextJsonSerializerOptions.JsonSerializerOptions); } -} +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Dapr/Volo/Abp/Dapr/AbpDaprModule.cs b/framework/src/Volo.Abp.Dapr/Volo/Abp/Dapr/AbpDaprModule.cs index 43d6d11033..276e96895d 100644 --- a/framework/src/Volo.Abp.Dapr/Volo/Abp/Dapr/AbpDaprModule.cs +++ b/framework/src/Volo.Abp.Dapr/Volo/Abp/Dapr/AbpDaprModule.cs @@ -1,4 +1,6 @@ -using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; using Volo.Abp.Json; using Volo.Abp.Modularity; @@ -10,6 +12,54 @@ public class AbpDaprModule : AbpModule public override void ConfigureServices(ServiceConfigurationContext context) { var configuration = context.Services.GetConfiguration(); + + ConfigureDaprOptions(configuration); + + context.Services.TryAddSingleton( + serviceProvider => serviceProvider + .GetRequiredService() + .Create() + ); + } + + private void ConfigureDaprOptions(IConfiguration configuration) + { Configure(configuration.GetSection("Dapr")); + Configure(options => + { + if (options.DaprApiToken.IsNullOrWhiteSpace()) + { + var confEnv = configuration["DAPR_API_TOKEN"]; + if (!confEnv.IsNullOrWhiteSpace()) + { + options.DaprApiToken = confEnv; + } + else + { + var env = Environment.GetEnvironmentVariable("DAPR_API_TOKEN"); + if (!env.IsNullOrWhiteSpace()) + { + options.DaprApiToken = env; + } + } + } + + if (options.AppApiToken.IsNullOrWhiteSpace()) + { + var confEnv = configuration["APP_API_TOKEN"]; + if (!confEnv.IsNullOrWhiteSpace()) + { + options.AppApiToken = confEnv; + } + else + { + var env = Environment.GetEnvironmentVariable("APP_API_TOKEN"); + if (!env.IsNullOrWhiteSpace()) + { + options.AppApiToken = env; + } + } + } + }); } } diff --git a/framework/src/Volo.Abp.Dapr/Volo/Abp/Dapr/AbpDaprOptions.cs b/framework/src/Volo.Abp.Dapr/Volo/Abp/Dapr/AbpDaprOptions.cs index 9d743263c5..7ac19ad31c 100644 --- a/framework/src/Volo.Abp.Dapr/Volo/Abp/Dapr/AbpDaprOptions.cs +++ b/framework/src/Volo.Abp.Dapr/Volo/Abp/Dapr/AbpDaprOptions.cs @@ -2,9 +2,11 @@ public class AbpDaprOptions { - public string AppId { get; set; } - public string HttpEndpoint { get; set; } public string GrpcEndpoint { get; set; } -} + + public string DaprApiToken { get; set; } + + public string AppApiToken { get; set; } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Dapr/Volo/Abp/Dapr/DaprApiTokenProvider.cs b/framework/src/Volo.Abp.Dapr/Volo/Abp/Dapr/DaprApiTokenProvider.cs new file mode 100644 index 0000000000..9fbed78204 --- /dev/null +++ b/framework/src/Volo.Abp.Dapr/Volo/Abp/Dapr/DaprApiTokenProvider.cs @@ -0,0 +1,24 @@ +using Microsoft.Extensions.Options; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.Dapr; + +public class DaprApiTokenProvider : IDaprApiTokenProvider, ISingletonDependency +{ + protected AbpDaprOptions Options { get; } + + public DaprApiTokenProvider(IOptions options) + { + Options = options.Value; + } + + public virtual string? GetDaprApiToken() + { + return Options.DaprApiToken; + } + + public virtual string? GetAppApiToken() + { + return Options.AppApiToken; + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Dapr/Volo/Abp/Dapr/IAbpDaprClientFactory.cs b/framework/src/Volo.Abp.Dapr/Volo/Abp/Dapr/IAbpDaprClientFactory.cs new file mode 100644 index 0000000000..fc50a07d41 --- /dev/null +++ b/framework/src/Volo.Abp.Dapr/Volo/Abp/Dapr/IAbpDaprClientFactory.cs @@ -0,0 +1,14 @@ +using Dapr.Client; + +namespace Volo.Abp.Dapr; + +public interface IAbpDaprClientFactory +{ + DaprClient Create(Action? builderAction = null); + + HttpClient CreateHttpClient( + string? appId = null, + string? daprEndpoint = null, + string? daprApiToken = null + ); +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Dapr/Volo/Abp/Dapr/IDaprApiTokenProvider.cs b/framework/src/Volo.Abp.Dapr/Volo/Abp/Dapr/IDaprApiTokenProvider.cs new file mode 100644 index 0000000000..89ea4d516d --- /dev/null +++ b/framework/src/Volo.Abp.Dapr/Volo/Abp/Dapr/IDaprApiTokenProvider.cs @@ -0,0 +1,8 @@ +namespace Volo.Abp.Dapr; + +public interface IDaprApiTokenProvider +{ + string? GetDaprApiToken(); + + string? GetAppApiToken(); +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.DistributedLocking.Abstractions/Volo/Abp/DistributedLocking/IAbpDistributedLock.cs b/framework/src/Volo.Abp.DistributedLocking.Abstractions/Volo/Abp/DistributedLocking/IAbpDistributedLock.cs index 760021c90a..754ae807f8 100644 --- a/framework/src/Volo.Abp.DistributedLocking.Abstractions/Volo/Abp/DistributedLocking/IAbpDistributedLock.cs +++ b/framework/src/Volo.Abp.DistributedLocking.Abstractions/Volo/Abp/DistributedLocking/IAbpDistributedLock.cs @@ -14,7 +14,7 @@ public interface IAbpDistributedLock /// Returns null if the lock could not be handled. /// /// The name of the lock - /// Timeout value + /// How long to wait before giving up on the acquisition attempt. Defaults to 0 /// Cancellation token [ItemCanBeNull] Task TryAcquireAsync( diff --git a/framework/src/Volo.Abp.DistributedLocking.Dapr/Volo/Abp/DistributedLocking/Dapr/AbpDistributedLockDaprOptions.cs b/framework/src/Volo.Abp.DistributedLocking.Dapr/Volo/Abp/DistributedLocking/Dapr/AbpDistributedLockDaprOptions.cs index 43feba3c3b..7579796871 100644 --- a/framework/src/Volo.Abp.DistributedLocking.Dapr/Volo/Abp/DistributedLocking/Dapr/AbpDistributedLockDaprOptions.cs +++ b/framework/src/Volo.Abp.DistributedLocking.Dapr/Volo/Abp/DistributedLocking/Dapr/AbpDistributedLockDaprOptions.cs @@ -4,10 +4,12 @@ public class AbpDistributedLockDaprOptions { public string StoreName { get; set; } - public TimeSpan DefaultTimeout { get; set; } + public string? Owner { get; set; } + + public TimeSpan DefaultExpirationTimeout { get; set; } public AbpDistributedLockDaprOptions() { - DefaultTimeout = TimeSpan.FromSeconds(30); + DefaultExpirationTimeout = TimeSpan.FromMinutes(2); } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.DistributedLocking.Dapr/Volo/Abp/DistributedLocking/Dapr/DaprAbpDistributedLock.cs b/framework/src/Volo.Abp.DistributedLocking.Dapr/Volo/Abp/DistributedLocking/Dapr/DaprAbpDistributedLock.cs index b4116c105c..ef99b52227 100644 --- a/framework/src/Volo.Abp.DistributedLocking.Dapr/Volo/Abp/DistributedLocking/Dapr/DaprAbpDistributedLock.cs +++ b/framework/src/Volo.Abp.DistributedLocking.Dapr/Volo/Abp/DistributedLocking/Dapr/DaprAbpDistributedLock.cs @@ -7,41 +7,33 @@ namespace Volo.Abp.DistributedLocking.Dapr; [Dependency(ReplaceServices = true)] public class DaprAbpDistributedLock : IAbpDistributedLock, ITransientDependency { - protected AbpDaprClientFactory DaprClientFactory { get; } + protected IAbpDaprClientFactory DaprClientFactory { get; } protected AbpDistributedLockDaprOptions DistributedLockDaprOptions { get; } - protected AbpDaprOptions DaprOptions { get; } protected IDistributedLockKeyNormalizer DistributedLockKeyNormalizer { get; } public DaprAbpDistributedLock( - AbpDaprClientFactory daprClientFactory, + IAbpDaprClientFactory daprClientFactory, IOptions distributedLockDaprOptions, - IOptions daprOptions, IDistributedLockKeyNormalizer distributedLockKeyNormalizer) { DaprClientFactory = daprClientFactory; DistributedLockKeyNormalizer = distributedLockKeyNormalizer; - DaprOptions = daprOptions.Value; DistributedLockDaprOptions = distributedLockDaprOptions.Value; } - public async Task TryAcquireAsync( + public async Task TryAcquireAsync( string name, TimeSpan timeout = default, CancellationToken cancellationToken = default) { - if (timeout == default) - { - timeout = DistributedLockDaprOptions.DefaultTimeout; - } - - var daprClient = await DaprClientFactory.CreateAsync(); - var key = DistributedLockKeyNormalizer.NormalizeKey(name); + name = DistributedLockKeyNormalizer.NormalizeKey(name); + var daprClient = DaprClientFactory.Create(); var lockResponse = await daprClient.Lock( DistributedLockDaprOptions.StoreName, - key, - DaprOptions.AppId, - (int)timeout.TotalSeconds, + name, + DistributedLockDaprOptions.Owner ?? Guid.NewGuid().ToString(), + (int)DistributedLockDaprOptions.DefaultExpirationTimeout.TotalSeconds, cancellationToken); if (lockResponse == null || !lockResponse.Success) diff --git a/framework/src/Volo.Abp.EventBus.Dapr/Volo/Abp/EventBus/Dapr/DaprDistributedEventBus.cs b/framework/src/Volo.Abp.EventBus.Dapr/Volo/Abp/EventBus/Dapr/DaprDistributedEventBus.cs index 1f241b9c6d..c8d47f6887 100644 --- a/framework/src/Volo.Abp.EventBus.Dapr/Volo/Abp/EventBus/Dapr/DaprDistributedEventBus.cs +++ b/framework/src/Volo.Abp.EventBus.Dapr/Volo/Abp/EventBus/Dapr/DaprDistributedEventBus.cs @@ -18,7 +18,7 @@ public class DaprDistributedEventBus : DistributedEventBusBase, ISingletonDepend { protected IDaprSerializer Serializer { get; } protected AbpDaprEventBusOptions DaprEventBusOptions { get; } - protected AbpDaprClientFactory DaprClientFactory { get; } + protected IAbpDaprClientFactory DaprClientFactory { get; } protected ConcurrentDictionary> HandlerFactories { get; } protected ConcurrentDictionary EventTypes { get; } @@ -33,7 +33,7 @@ public class DaprDistributedEventBus : DistributedEventBusBase, ISingletonDepend IEventHandlerInvoker eventHandlerInvoker, IDaprSerializer serializer, IOptions daprEventBusOptions, - AbpDaprClientFactory daprClientFactory) + IAbpDaprClientFactory daprClientFactory) : base(serviceScopeFactory, currentTenant, unitOfWorkManager, abpDistributedEventBusOptions, guidGenerator, clock, eventHandlerInvoker) { Serializer = serializer; @@ -197,7 +197,7 @@ public class DaprDistributedEventBus : DistributedEventBusBase, ISingletonDepend protected virtual async Task PublishToDaprAsync(string eventName, object eventData) { - var client = await DaprClientFactory.CreateAsync(); + var client = DaprClientFactory.Create(); await client.PublishEventAsync(pubsubName: DaprEventBusOptions.PubSubName, topicName: eventName, data: eventData); }