diff --git a/src/Cnblogs.Architecture.Ddd.Cqrs.AspNetCore/Cnblogs.Architecture.Ddd.Cqrs.AspNetCore.csproj b/src/Cnblogs.Architecture.Ddd.Cqrs.AspNetCore/Cnblogs.Architecture.Ddd.Cqrs.AspNetCore.csproj index f9e4092..f5b3a66 100644 --- a/src/Cnblogs.Architecture.Ddd.Cqrs.AspNetCore/Cnblogs.Architecture.Ddd.Cqrs.AspNetCore.csproj +++ b/src/Cnblogs.Architecture.Ddd.Cqrs.AspNetCore/Cnblogs.Architecture.Ddd.Cqrs.AspNetCore.csproj @@ -8,6 +8,7 @@ + diff --git a/src/Cnblogs.Architecture.Ddd.Cqrs.AspNetCore/CqrsRouteMapper.cs b/src/Cnblogs.Architecture.Ddd.Cqrs.AspNetCore/CqrsRouteMapper.cs index ab233a3..2ab46a4 100644 --- a/src/Cnblogs.Architecture.Ddd.Cqrs.AspNetCore/CqrsRouteMapper.cs +++ b/src/Cnblogs.Architecture.Ddd.Cqrs.AspNetCore/CqrsRouteMapper.cs @@ -114,10 +114,10 @@ public static IEndpointConventionBuilder MapQuery( string nullRouteParameterPattern = "-", bool enableHead = false) { - var returnType = EnsureReturnTypeIsQuery(handler); + var (queryType, returnType) = EnsureReturnTypeIsQuery(handler); if (mapNullableRouteParameters is MapNullableRouteParameter.Disable) { - return MapRoutes(route); + return MapRoutes(queryType, returnType, route); } if (string.IsNullOrWhiteSpace(nullRouteParameterPattern)) @@ -129,7 +129,7 @@ public static IEndpointConventionBuilder MapQuery( var parsedRoute = RoutePatternFactory.Parse(route); var context = new NullabilityInfoContext(); - var nullableRouteProperties = returnType.GetProperties() + var nullableRouteProperties = queryType.GetProperties() .Where( p => p.GetMethod != null && p.SetMethod != null @@ -150,15 +150,24 @@ public static IEndpointConventionBuilder MapQuery( var regex = new Regex("{" + x.Name + "[^}]*?}", RegexOptions.IgnoreCase); return regex.Replace(r, nullRouteParameterPattern); }); - MapRoutes(newRoute); + MapRoutes(queryType, returnType, newRoute); } - return MapRoutes(route); + return MapRoutes(queryType, returnType, route); - IEndpointConventionBuilder MapRoutes(string r) + IEndpointConventionBuilder MapRoutes(Type query, Type queryFor, string r) { var endpoint = enableHead ? app.MapMethods(r, GetAndHeadMethods, handler) : app.MapGet(r, handler); - return endpoint.AddEndpointFilter(); + var builder = endpoint.AddEndpointFilter() + .Produces(200, queryFor) + .WithTags("Queries"); + if (query.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IQuery<>))) + { + // may be null + builder.Produces(404, queryFor); + } + + return builder; } } @@ -220,7 +229,7 @@ public static IEndpointConventionBuilder MapCommand( [StringSyntax("Route")] string route, Delegate handler) { - var commandTypeName = EnsureReturnTypeIsCommand(handler).Name; + var commandTypeName = EnsureReturnTypeIsCommand(handler).CommandType.Name; if (PostCommandPrefixes.Any(x => commandTypeName.StartsWith(x))) { return app.MapPostCommand(route, handler); @@ -265,8 +274,11 @@ public static IEndpointConventionBuilder MapPostCommand( [StringSyntax("Route")] string route, Delegate handler) { - EnsureReturnTypeIsCommand(handler); - return app.MapPost(route, handler).AddEndpointFilter(); + var (commandType, responseType, errorType) = EnsureReturnTypeIsCommand(handler); + var builder = app.MapPost(route, handler) + .AddEndpointFilter() + .AddCommandOpenApiDescriptions(commandType, responseType, errorType); + return builder; } /// @@ -295,8 +307,9 @@ public static IEndpointConventionBuilder MapPutCommand( [StringSyntax("Route")] string route, Delegate handler) { - EnsureReturnTypeIsCommand(handler); - return app.MapPut(route, handler).AddEndpointFilter(); + var (commandType, responseType, errorType) = EnsureReturnTypeIsCommand(handler); + return app.MapPut(route, handler).AddEndpointFilter() + .AddCommandOpenApiDescriptions(commandType, responseType, errorType); } /// @@ -325,8 +338,9 @@ public static IEndpointConventionBuilder MapDeleteCommand( [StringSyntax("Route")] string route, Delegate handler) { - EnsureReturnTypeIsCommand(handler); - return app.MapDelete(route, handler).AddEndpointFilter(); + var (commandType, responseType, errorType) = EnsureReturnTypeIsCommand(handler); + return app.MapDelete(route, handler).AddEndpointFilter() + .AddCommandOpenApiDescriptions(commandType, responseType, errorType); } /// @@ -395,7 +409,7 @@ public static IEndpointRouteBuilder StopMappingPrefixToDelete(this IEndpointRout return app; } - private static Type EnsureReturnTypeIsCommand(Delegate handler) + private static (Type CommandType, Type? ResponseType, Type ErrorType) EnsureReturnTypeIsCommand(Delegate handler) { var returnType = handler.Method.ReturnType; if (returnType.IsGenericType && returnType.GetGenericTypeDefinition() == typeof(Task<>)) @@ -403,18 +417,24 @@ private static Type EnsureReturnTypeIsCommand(Delegate handler) returnType = returnType.GenericTypeArguments.First(); } - var isCommand = returnType.GetInterfaces().Where(x => x.IsGenericType) - .Any(x => CommandTypes.Contains(x.GetGenericTypeDefinition())); - if (isCommand == false) + var commandType = returnType.GetInterfaces().Where(x => x.IsGenericType) + .FirstOrDefault(x => CommandTypes.Contains(x.GetGenericTypeDefinition())); + if (commandType == null) { throw new ArgumentException( "handler does not return command, check if delegate returns type that implements ICommand<> or ICommand<,>"); } - return returnType; + Type?[] genericParams = commandType.GetGenericArguments(); + if (genericParams.Length == 1) + { + genericParams = [null, genericParams[0]]; + } + + return (returnType, genericParams[0], genericParams[1]!); } - private static Type EnsureReturnTypeIsQuery(Delegate handler) + private static (Type, Type) EnsureReturnTypeIsQuery(Delegate handler) { var returnType = handler.Method.ReturnType; if (returnType.IsGenericType && returnType.GetGenericTypeDefinition() == typeof(Task<>)) @@ -422,15 +442,15 @@ private static Type EnsureReturnTypeIsQuery(Delegate handler) returnType = returnType.GenericTypeArguments.First(); } - var isCommand = returnType.GetInterfaces().Where(x => x.IsGenericType) - .Any(x => QueryTypes.Contains(x.GetGenericTypeDefinition())); - if (isCommand == false) + var queryInterface = returnType.GetInterfaces().Where(x => x.IsGenericType) + .FirstOrDefault(x => QueryTypes.Contains(x.GetGenericTypeDefinition())); + if (queryInterface == null) { throw new ArgumentException( "handler does not return query, check if delegate returns type that implements IQuery<>"); } - return returnType; + return (returnType, queryInterface.GenericTypeArguments[0]); } private static List GetNotEmptySubsets(ICollection items) @@ -446,4 +466,24 @@ private static List GetNotEmptySubsets(ICollection items) return results; } + + private static RouteHandlerBuilder AddCommandOpenApiDescriptions( + this RouteHandlerBuilder builder, + Type commandType, + Type? responseType, + Type errorType) + { + var commandResponseType = responseType is null + ? typeof(CommandResponse<>).MakeGenericType(errorType) + : typeof(CommandResponse<,>).MakeGenericType(responseType, errorType); + builder.Produces(200, commandResponseType) + .Produces(400, commandResponseType) + .WithTags("Commands"); + if (commandType.GetInterfaces().Any(i => i == typeof(ILockableRequest))) + { + builder.Produces(429); + } + + return builder; + } } diff --git a/src/Cnblogs.Architecture.Ddd.EventBus.Dapr/EndPointExtensions.cs b/src/Cnblogs.Architecture.Ddd.EventBus.Dapr/EndPointExtensions.cs index 6d9ea3a..fb3923f 100644 --- a/src/Cnblogs.Architecture.Ddd.EventBus.Dapr/EndPointExtensions.cs +++ b/src/Cnblogs.Architecture.Ddd.EventBus.Dapr/EndPointExtensions.cs @@ -1,6 +1,7 @@ using System.Reflection; using Cnblogs.Architecture.Ddd.EventBus.Abstractions; using Cnblogs.Architecture.Ddd.EventBus.Dapr; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; @@ -58,7 +59,8 @@ public static IEndpointRouteBuilder Subscribe( builder .MapPost(route, (TEvent receivedEvent, IEventBus eventBus) => eventBus.ReceiveAsync(receivedEvent)) - .WithTopic(DaprOptions.PubSubName, DaprUtils.GetDaprTopicName(appName)); + .WithTopic(DaprOptions.PubSubName, DaprUtils.GetDaprTopicName(appName)) + .WithTags("Subscriptions"); return builder; } diff --git a/src/Cnblogs.Architecture.Ddd.Infrastructure.MongoDb/Cnblogs.Architecture.Ddd.Infrastructure.MongoDb.csproj b/src/Cnblogs.Architecture.Ddd.Infrastructure.MongoDb/Cnblogs.Architecture.Ddd.Infrastructure.MongoDb.csproj index fdc9bb2..fec8fe0 100644 --- a/src/Cnblogs.Architecture.Ddd.Infrastructure.MongoDb/Cnblogs.Architecture.Ddd.Infrastructure.MongoDb.csproj +++ b/src/Cnblogs.Architecture.Ddd.Infrastructure.MongoDb/Cnblogs.Architecture.Ddd.Infrastructure.MongoDb.csproj @@ -10,7 +10,7 @@ - + diff --git a/test/Cnblogs.Architecture.IntegrationTestProject/Properties/launchSettings.json b/test/Cnblogs.Architecture.IntegrationTestProject/Properties/launchSettings.json index 2b3e363..cc7ea7c 100644 --- a/test/Cnblogs.Architecture.IntegrationTestProject/Properties/launchSettings.json +++ b/test/Cnblogs.Architecture.IntegrationTestProject/Properties/launchSettings.json @@ -14,7 +14,7 @@ "dotnetRunMessages": true, "launchBrowser": true, "launchUrl": "swagger", - "applicationUrl": "https://localhost:7200;http://localhost:5200", + "applicationUrl": "https://localhost:8200;http://localhost:5200", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" }