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"
}