From cc92a2fe59109b4e5e848d9855623f66842974a1 Mon Sep 17 00:00:00 2001 From: maliming Date: Thu, 3 Nov 2022 18:05:57 +0800 Subject: [PATCH] Add JSON converter and update documents. --- docs/en/Dapr/Index.md | 38 ++++------------ docs/zh-Hans/Dapr/Index.md | 40 +++++------------ .../AbpAspNetCoreMvcDaprEventBusModule.cs | 18 +++++++- .../AbpAspNetCoreMvcDaprEventsController.cs | 40 ++++++++++++----- .../AbpDaprSubscriptionRequestConverter.cs | 44 +++++++++++++++++++ ...DaprSubscriptionRequestConverterFactory.cs | 33 ++++++++++++++ ...DaprSubscriptionRequestJsonNamingPolicy.cs | 11 +++++ ...quest.cs => AbpDaprSubscriptionRequest.cs} | 5 ++- 8 files changed, 157 insertions(+), 72 deletions(-) create mode 100644 framework/src/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus/Volo/Abp/AspNetCore/Mvc/Dapr/EventBus/Json/AbpDaprSubscriptionRequestConverter.cs create mode 100644 framework/src/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus/Volo/Abp/AspNetCore/Mvc/Dapr/EventBus/Json/AbpDaprSubscriptionRequestConverterFactory.cs create mode 100644 framework/src/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus/Volo/Abp/AspNetCore/Mvc/Dapr/EventBus/Json/AbpDaprSubscriptionRequestJsonNamingPolicy.cs rename framework/src/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus/Volo/Abp/AspNetCore/Mvc/Dapr/EventBus/Models/{AbpAspNetCoreMvcDaprSubscriptionRequest.cs => AbpDaprSubscriptionRequest.cs} (60%) diff --git a/docs/en/Dapr/Index.md b/docs/en/Dapr/Index.md index 2596c449d1..20abaa3f31 100644 --- a/docs/en/Dapr/Index.md +++ b/docs/en/Dapr/Index.md @@ -62,27 +62,6 @@ Alternatively, you can configure the options in the `Dapr` section of your `apps ### 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. @@ -209,7 +188,7 @@ 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. +> **Since ABP will call `MapSubscribeHandler` internally, you should not manually call it anymore.** 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 @@ -270,15 +249,18 @@ In addition to ABP's standard distributed event bus system, you can also use Dap ````csharp public class MyService : ITransientDependency { - private readonly DaprClient _daprClient; + private readonly IAbpDaprClientFactory _daprClientFactory; - public MyService(DaprClient daprClient) + public MyService(IAbpDaprClientFactory daprClientFactory) { - _daprClient = daprClient; + _daprClientFactory = daprClientFactory; } public async Task DoItAsync() { + // Create a DaprClient object with default options + DaprClient _daprClient = await _daprClientFactory.CreateAsync(); + await _daprClient.PublishEventAsync( "pubsub", // pubsub name "StockChanged", // topic name @@ -299,8 +281,7 @@ public class MyController : AbpController { [HttpPost("/stock-changed")] [Topic("pubsub", "StockChanged")] - public async Task TestRouteAsync( - [FromBody] StockCountChangedEto model) + public async Task TestRouteAsync([FromBody] AbpDaprSubscriptionRequest model) { HttpContext.ValidateDaprAppApiToken(); @@ -430,8 +411,7 @@ public class MyController : AbpController { [HttpPost("/stock-changed")] [Topic("pubsub", "StockChanged")] - public async Task TestRouteAsync( - [FromBody] StockCountChangedEto model) + public async Task TestRouteAsync([FromBody] AbpDaprSubscriptionRequest model) { // Validate the App API token! HttpContext.ValidateDaprAppApiToken(); diff --git a/docs/zh-Hans/Dapr/Index.md b/docs/zh-Hans/Dapr/Index.md index 30936eb6da..efeb6ca770 100644 --- a/docs/zh-Hans/Dapr/Index.md +++ b/docs/zh-Hans/Dapr/Index.md @@ -62,27 +62,6 @@ Configure(options => ### 注入DaprClient -ABP 将 `DaprClient` 类注册到 [依赖注入](../Dependency-Injection.md) 系统中.因此,你可以在需要时注入并使用它: - -````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 - } -} -```` - -注入 `DaprClient` 是在应用程序代码中使用它的推荐方法.当你注入它时,将使用 `IAbpDaprClientFactory` 服务创建它,这会在下一节中将进行说明. - ### IAbpDaprClientFactory `IAbpDaprClientFactory` 可用于创建 `DaprClient` 或 `HttpClient` 对象来执行对 Dapr 的操作.它使用 `AbpDaprOptions`,因此你可以配置设置. @@ -209,7 +188,7 @@ ABP提供了以下端点来接收来自Dapr的事件: * `dapr/subscribe`: Dapr使用此端点从应用程序获取订阅列表.ABP会自动返回所有分布式事件处理程序类和具有`Topic`属性的自定义控制器操作的订阅. * `api/abp/dapr/event`: 用于接收来自Dapr的所有事件的统一端点.ABP根据主题名称将事件分派给您的事件处理程序. -> **由于ABP提供了标准的`dapr/subscribe`端点,所以你不应该手动调用Dapr的`app.MapSubscribeHandler()`方法.** 如果你想支持[CloudEvents](https://cloudevents.io/)标准,你可以在你的ASP.NET Core管道中使用`app.UseCloudEvents()`中间件. +> **由于ABP会在内部调用`MapSubscribeHandler` 方法,所以你不应该手动调用了.** 如果你想支持[CloudEvents](https://cloudevents.io/)标准,你可以在你的ASP.NET Core管道中使用`app.UseCloudEvents()`中间件. ### 用法 @@ -270,16 +249,19 @@ public class MyHandler : ````csharp public class MyService : ITransientDependency { - private readonly DaprClient _daprClient; + private readonly IAbpDaprClientFactory _daprClientFactory; - public MyService(DaprClient daprClient) + public MyService(IAbpDaprClientFactory daprClientFactory) { - _daprClient = daprClient; + _daprClientFactory = daprClientFactory; } public async Task DoItAsync() { - await _daprClient.PublishEventAsync( + // Create a DaprClient object with default options + DaprClient daprClient = await _daprClientFactory.CreateAsync(); + + await daprClient.PublishEventAsync( "pubsub", // pubsub name "StockChanged", // topic name new StockCountChangedEto // event data @@ -299,8 +281,7 @@ public class MyController : AbpController { [HttpPost("/stock-changed")] [Topic("pubsub", "StockChanged")] - public async Task TestRouteAsync( - [FromBody] StockCountChangedEto model) + public async Task TestRouteAsync([FromBody] AbpDaprSubscriptionRequest model) { HttpContext.ValidateDaprAppApiToken(); @@ -430,8 +411,7 @@ public class MyController : AbpController { [HttpPost("/stock-changed")] [Topic("pubsub", "StockChanged")] - public async Task TestRouteAsync( - [FromBody] StockCountChangedEto model) + public async Task TestRouteAsync([FromBody] AbpDaprSubscriptionRequest model) { // Validate the App API token! HttpContext.ValidateDaprAppApiToken(); diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus/Volo/Abp/AspNetCore/Mvc/Dapr/EventBus/AbpAspNetCoreMvcDaprEventBusModule.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus/Volo/Abp/AspNetCore/Mvc/Dapr/EventBus/AbpAspNetCoreMvcDaprEventBusModule.cs index e3c199c001..ef5594f2c5 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus/Volo/Abp/AspNetCore/Mvc/Dapr/EventBus/AbpAspNetCoreMvcDaprEventBusModule.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus/Volo/Abp/AspNetCore/Mvc/Dapr/EventBus/AbpAspNetCoreMvcDaprEventBusModule.cs @@ -1,11 +1,14 @@ -using System.Collections.Generic; +using System; using System.Linq; using System.Threading.Tasks; using Dapr; using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; +using Volo.Abp.AspNetCore.Mvc.Dapr.EventBus.Json; +using Volo.Abp.Dapr; using Volo.Abp.DependencyInjection; using Volo.Abp.EventBus; using Volo.Abp.EventBus.Dapr; @@ -22,6 +25,12 @@ public class AbpAspNetCoreMvcDaprEventBusModule : AbpModule { public override void ConfigureServices(ServiceConfigurationContext context) { + context.Services.AddOptions() + .Configure((options, serviceProvider) => + { + options.JsonSerializerOptions.Converters.Add(new AbpDaprSubscriptionRequestConverterFactory(serviceProvider.GetRequiredService())); + }); + var subscribeOptions = context.Services.ExecutePreConfiguredActions(); Configure(options => @@ -38,6 +47,13 @@ public class AbpAspNetCoreMvcDaprEventBusModule : AbpModule { var eventType = @interface.GetGenericArguments()[0]; var eventName = EventNameAttribute.GetNameOrDefault(eventType); + + if (subscriptions.Any(x => x.PubsubName == daprEventBusOptions.PubSubName && x.Topic == eventName)) + { + // Controllers with a [Topic] attribute can replace built-in event handlers. + continue; + } + subscriptions.Add(new AbpSubscription() { PubsubName = daprEventBusOptions.PubSubName, diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus/Volo/Abp/AspNetCore/Mvc/Dapr/EventBus/Controllers/AbpAspNetCoreMvcDaprEventsController.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus/Volo/Abp/AspNetCore/Mvc/Dapr/EventBus/Controllers/AbpAspNetCoreMvcDaprEventsController.cs index 92953350ae..fdfad1d722 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus/Volo/Abp/AspNetCore/Mvc/Dapr/EventBus/Controllers/AbpAspNetCoreMvcDaprEventsController.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus/Volo/Abp/AspNetCore/Mvc/Dapr/EventBus/Controllers/AbpAspNetCoreMvcDaprEventsController.cs @@ -1,8 +1,10 @@ -using System.Text.Json; +using System.Collections.Concurrent; +using System.Text.Json; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Options; +using Microsoft.Extensions.Logging; +using Volo.Abp.AspNetCore.Mvc.Dapr.EventBus.Json; using Volo.Abp.AspNetCore.Mvc.Dapr.EventBus.Models; using Volo.Abp.Dapr; using Volo.Abp.EventBus.Dapr; @@ -14,20 +16,36 @@ namespace Volo.Abp.AspNetCore.Mvc.Dapr.EventBus.Controllers; public class AbpAspNetCoreMvcDaprEventsController : AbpController { [HttpPost(AbpAspNetCoreMvcDaprPubSubConsts.DaprEventCallbackUrl)] - public virtual async Task EventsAsync() + public virtual async Task EventAsync() { await HttpContext.ValidateDaprAppApiTokenAsync(); - var bodyJsonDocument = await JsonDocument.ParseAsync(HttpContext.Request.Body); - var request = JsonSerializer.Deserialize(bodyJsonDocument.RootElement.GetRawText(), - HttpContext.RequestServices.GetRequiredService>().Value.JsonSerializerOptions); - - var distributedEventBus = HttpContext.RequestServices.GetRequiredService(); var daprSerializer = HttpContext.RequestServices.GetRequiredService(); + var request = (await JsonDocument.ParseAsync(HttpContext.Request.Body)).Deserialize>(CreateJsonSerializerOptions(daprSerializer)); + if (request != null && request.Data is JsonElement jsonElement) + { + var distributedEventBus = HttpContext.RequestServices.GetRequiredService(); + var eventData = daprSerializer.Deserialize(jsonElement.GetRawText(), distributedEventBus.GetEventType(request.Topic)); + await distributedEventBus.TriggerHandlersAsync(distributedEventBus.GetEventType(request.Topic), eventData); + return Ok(); + } + + Logger.LogError("Invalid Dapr event request."); + return BadRequest(); + } - var eventData = daprSerializer.Deserialize(bodyJsonDocument.RootElement.GetProperty("data").GetRawText(), distributedEventBus.GetEventType(request.Topic)); - await distributedEventBus.TriggerHandlersAsync(distributedEventBus.GetEventType(request.Topic), eventData); + private static readonly ConcurrentDictionary JsonSerializerOptionsCache = new ConcurrentDictionary(); - return Ok(); + protected virtual JsonSerializerOptions CreateJsonSerializerOptions(IDaprSerializer daprSerializer) + { + return JsonSerializerOptionsCache.GetOrAdd(nameof(AbpAspNetCoreMvcDaprEventsController), _ => + { + var settings = new JsonSerializerOptions(JsonSerializerDefaults.Web) + { + PropertyNamingPolicy = new AbpDaprSubscriptionRequestJsonNamingPolicy() + }; + settings.Converters.Add(new AbpDaprSubscriptionRequestConverterFactory(daprSerializer)); + return settings; + }); } } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus/Volo/Abp/AspNetCore/Mvc/Dapr/EventBus/Json/AbpDaprSubscriptionRequestConverter.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus/Volo/Abp/AspNetCore/Mvc/Dapr/EventBus/Json/AbpDaprSubscriptionRequestConverter.cs new file mode 100644 index 0000000000..b0a4f72b12 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus/Volo/Abp/AspNetCore/Mvc/Dapr/EventBus/Json/AbpDaprSubscriptionRequestConverter.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.Text.Json; +using System.Text.Json.Serialization; +using Volo.Abp.AspNetCore.Mvc.Dapr.EventBus.Models; +using Volo.Abp.Dapr; + +namespace Volo.Abp.AspNetCore.Mvc.Dapr.EventBus.Json; + +public class AbpDaprSubscriptionRequestConverter : JsonConverter> + where T : class +{ + private JsonSerializerOptions _readJsonSerializerOptions; + + private readonly IDaprSerializer _daprSerializer; + + public AbpDaprSubscriptionRequestConverter(IDaprSerializer daprSerializer) + { + _daprSerializer = daprSerializer; + } + + public override AbpDaprSubscriptionRequest Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + _readJsonSerializerOptions ??= CreateJsonSerializerOptions(options); + + var rootElement = JsonDocument.ParseValue(ref reader).RootElement; + var obj = JsonSerializer.Deserialize>(rootElement.GetRawText(), _readJsonSerializerOptions); + obj.Data = _daprSerializer.Deserialize(rootElement.GetProperty("data").GetRawText(), typeof(T)).As(); + return obj; + } + + public override void Write(Utf8JsonWriter writer, AbpDaprSubscriptionRequest value, JsonSerializerOptions options) + { + throw new NotSupportedException(); + } + + private JsonSerializerOptions CreateJsonSerializerOptions(JsonSerializerOptions options) + { + var newOptions = new JsonSerializerOptions(options); + newOptions.Converters.RemoveAll(x => x == this || x.GetType() == typeof(AbpDaprSubscriptionRequestConverterFactory)); + newOptions.PropertyNamingPolicy = new AbpDaprSubscriptionRequestJsonNamingPolicy(); + return newOptions; + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus/Volo/Abp/AspNetCore/Mvc/Dapr/EventBus/Json/AbpDaprSubscriptionRequestConverterFactory.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus/Volo/Abp/AspNetCore/Mvc/Dapr/EventBus/Json/AbpDaprSubscriptionRequestConverterFactory.cs new file mode 100644 index 0000000000..ec809c23d2 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus/Volo/Abp/AspNetCore/Mvc/Dapr/EventBus/Json/AbpDaprSubscriptionRequestConverterFactory.cs @@ -0,0 +1,33 @@ +using System; +using System.Reflection; +using System.Text.Json; +using System.Text.Json.Serialization; +using Volo.Abp.AspNetCore.Mvc.Dapr.EventBus.Models; +using Volo.Abp.Dapr; + +namespace Volo.Abp.AspNetCore.Mvc.Dapr.EventBus.Json; + +public class AbpDaprSubscriptionRequestConverterFactory : JsonConverterFactory +{ + private readonly IDaprSerializer _daprSerializer; + + public AbpDaprSubscriptionRequestConverterFactory(IDaprSerializer daprSerializer) + { + _daprSerializer = daprSerializer; + } + + public override bool CanConvert(Type typeToConvert) + { + return typeToConvert.GetGenericTypeDefinition() == typeof(AbpDaprSubscriptionRequest<>); + } + + public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) + { + return (JsonConverter)Activator.CreateInstance( + typeof(AbpDaprSubscriptionRequestConverter<>).MakeGenericType(typeToConvert.GetGenericArguments()[0]), + BindingFlags.Instance | BindingFlags.Public, + binder: null, + new object[] { _daprSerializer }, + culture: null)!; + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus/Volo/Abp/AspNetCore/Mvc/Dapr/EventBus/Json/AbpDaprSubscriptionRequestJsonNamingPolicy.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus/Volo/Abp/AspNetCore/Mvc/Dapr/EventBus/Json/AbpDaprSubscriptionRequestJsonNamingPolicy.cs new file mode 100644 index 0000000000..89437f5fa3 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus/Volo/Abp/AspNetCore/Mvc/Dapr/EventBus/Json/AbpDaprSubscriptionRequestJsonNamingPolicy.cs @@ -0,0 +1,11 @@ +using System.Text.Json; + +namespace Volo.Abp.AspNetCore.Mvc.Dapr.EventBus.Json; + +public class AbpDaprSubscriptionRequestJsonNamingPolicy : JsonNamingPolicy +{ + public override string ConvertName(string name) + { + return name.ToLower(); + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus/Volo/Abp/AspNetCore/Mvc/Dapr/EventBus/Models/AbpAspNetCoreMvcDaprSubscriptionRequest.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus/Volo/Abp/AspNetCore/Mvc/Dapr/EventBus/Models/AbpDaprSubscriptionRequest.cs similarity index 60% rename from framework/src/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus/Volo/Abp/AspNetCore/Mvc/Dapr/EventBus/Models/AbpAspNetCoreMvcDaprSubscriptionRequest.cs rename to framework/src/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus/Volo/Abp/AspNetCore/Mvc/Dapr/EventBus/Models/AbpDaprSubscriptionRequest.cs index 46c04b5a44..ccf3724579 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus/Volo/Abp/AspNetCore/Mvc/Dapr/EventBus/Models/AbpAspNetCoreMvcDaprSubscriptionRequest.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus/Volo/Abp/AspNetCore/Mvc/Dapr/EventBus/Models/AbpDaprSubscriptionRequest.cs @@ -1,8 +1,11 @@ namespace Volo.Abp.AspNetCore.Mvc.Dapr.EventBus.Models; -public class AbpAspNetCoreMvcDaprSubscriptionRequest +public class AbpDaprSubscriptionRequest + where T : class { public string PubSubName { get; set; } public string Topic { get; set; } + + public T Data { get; set; } }