Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 10 additions & 11 deletions src/Cnblogs.Architecture.Ddd.Cqrs.AspNetCore/ApiControllerBase.cs
Original file line number Diff line number Diff line change
@@ -1,21 +1,20 @@
using Cnblogs.Architecture.Ddd.Cqrs.Abstractions;
using Cnblogs.Architecture.Ddd.Domain.Abstractions;

using Microsoft.AspNetCore.Mvc;

namespace Cnblogs.Architecture.Ddd.Cqrs.AspNetCore;

/// <summary>
/// Controller 基类,提供自动处理 <see cref="CommandResponse{TError}"/> 的方法。
/// A base class for an API controller with methods that return <see cref="IActionResult"/> based ons <see cref="CommandResponse{TError}"/>.
/// </summary>
[ApiController]
public class ApiControllerBase : ControllerBase
{
/// <summary>
/// 处理 CommandResponse 并返回对应的状态码,成功-204,错误-400
/// Handle command response and return 204 if success, 400 if error.
/// </summary>
/// <param name="response">任务结果。</param>
/// <typeparam name="TError">错误类型。</typeparam>
/// <param name="response">The command response.</param>
/// <typeparam name="TError">The type of error.</typeparam>
/// <returns><see cref="IActionResult"/></returns>
protected IActionResult HandleCommandResponse<TError>(CommandResponse<TError> response)
where TError : Enumeration
Expand All @@ -29,11 +28,11 @@ protected IActionResult HandleCommandResponse<TError>(CommandResponse<TError> re
}

/// <summary>
/// 自动处理命令返回的结果,成功-200,失败-400
/// Handle command response and return 204 if success, 400 if error.
/// </summary>
/// <param name="response">命令执行结果。</param>
/// <typeparam name="TResponse">返回类型。</typeparam>
/// <typeparam name="TError">错误类型。</typeparam>
/// <param name="response">The command response.</param>
/// <typeparam name="TResponse">The response type when success.</typeparam>
/// <typeparam name="TError">The error type.</typeparam>
/// <returns><see cref="IActionResult"/></returns>
protected IActionResult HandleCommandResponse<TResponse, TError>(CommandResponse<TResponse, TError> response)
where TError : Enumeration
Expand All @@ -54,11 +53,11 @@ private IActionResult HandleErrorCommandResponse<TError>(CommandResponse<TError>
return BadRequest(response.ValidationError!.Message);
}

if (response.IsConcurrentError && response.LockAcquired == false)
if (response is { IsConcurrentError: true, LockAcquired: false })
{
return StatusCode(429);
}

return BadRequest(response.ErrorCode?.Name ?? response.ErrorMessage);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@
namespace Microsoft.Extensions.DependencyInjection;

/// <summary>
/// Api Versioning 注入扩展
/// Extension methods to inject api versioning.
/// </summary>
public static class ApiVersioningInjectors
{
/// <summary>
/// 添加 API Versioning,默认使用 <see cref="VersionByNamespaceConvention"/>
/// Add API Versioning, use <see cref="VersionByNamespaceConvention"/> by default.
/// </summary>
/// <param name="services"><see cref="IServiceCollection"/></param>
/// <returns></returns>
Expand All @@ -26,4 +26,4 @@ public static IApiVersioningBuilder AddCnblogsApiVersioning(this IServiceCollect
o.SubstituteApiVersionInUrl = true;
});
}
}
}
Original file line number Diff line number Diff line change
@@ -1,20 +1,18 @@
using Cnblogs.Architecture.Ddd.Cqrs.Abstractions;

using MediatR;

using Microsoft.AspNetCore.Http;

namespace Cnblogs.Architecture.Ddd.Cqrs.AspNetCore;

/// <summary>
/// 命令执行器,自动将返回内容提交给 mediator 并返回结果。
/// Execute command returned by endpoint handler, and then map command response to HTTP response.
/// </summary>
public class CommandEndpointHandler : IEndpointFilter
{
private readonly IMediator _mediator;

/// <summary>
/// 构造一个命令执行器。
/// Create a command endpoint handler.
/// </summary>
/// <param name="mediator"><see cref="IMediator"/></param>
public CommandEndpointHandler(IMediator mediator)
Expand Down Expand Up @@ -73,4 +71,4 @@ private static IResult HandleErrorCommandResponse(CommandResponse response)

return Results.BadRequest(response.GetErrorMessage());
}
}
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
using Cnblogs.Architecture.Ddd.Cqrs.AspNetCore;
using Cnblogs.Architecture.Ddd.Infrastructure.Abstractions;
using Microsoft.AspNetCore.Mvc;

// ReSharper disable once CheckNamespace
namespace Microsoft.Extensions.DependencyInjection;

/// <summary>
/// 用于注入 Model Binder 的扩展方法。
/// Extensions to inject custom model binder for CQRS.
/// </summary>
public static class ControllerOptionInjector
{
/// <summary>
/// 添加 CQRS 相关的 Model Binder Provider
/// Add custom model binder used for CQRS, like model binder for <see cref="PagingParams"/>.
/// </summary>
/// <param name="options"><see cref="MvcOptions"/></param>
public static void AddCqrsModelBinderProvider(this MvcOptions options)
Expand All @@ -19,11 +20,11 @@ public static void AddCqrsModelBinderProvider(this MvcOptions options)
}

/// <summary>
/// 添加 CQRS 相关的 Model Binder Provider
/// Add custom model binder used for CQRS, like model binder for <see cref="PagingParams"/>.
/// </summary>
/// <param name="builder"><see cref="IMvcBuilder"/></param>
public static void AddCqrsModelBinderProvider(this IMvcBuilder builder)
{
builder.AddMvcOptions(options => options.AddCqrsModelBinderProvider());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
namespace Cnblogs.Architecture.Ddd.Cqrs.AspNetCore;

/// <summary>
/// Model Binder Provider for custom types
/// Model Binder Provider for custom types used in CQRS.
/// </summary>
public class CqrsModelBinderProvider : IModelBinderProvider
{
Expand All @@ -20,4 +20,4 @@ public class CqrsModelBinderProvider : IModelBinderProvider

return null;
}
}
}
71 changes: 23 additions & 48 deletions src/Cnblogs.Architecture.Ddd.Cqrs.AspNetCore/CqrsRouteMapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
namespace Cnblogs.Architecture.Ddd.Cqrs.AspNetCore;

/// <summary>
/// 用于 Minimum API CQRS 路径注册的扩展方法。
/// Extension methods used for register Command and Query endpoint in minimal API.
/// </summary>
public static class CqrsRouteMapper
{
Expand All @@ -18,11 +18,11 @@ public static class CqrsRouteMapper
private static readonly List<Type> CommandTypes = new() { typeof(ICommand<>), typeof(ICommand<,>) };

/// <summary>
/// 添加查询 API,使用 GET 方法访问,参数将自动从路径或查询字符串获取。
/// Map a query API, using GET method. <typeparamref name="T"/> would been constructed from route and query string.
/// </summary>
/// <param name="app"><see cref="IApplicationBuilder"/></param>
/// <param name="route">路径模板。</param>
/// <typeparam name="T">查询类型。</typeparam>
/// <param name="route">The route template for API.</param>
/// <typeparam name="T">The type of the query.</typeparam>
/// <returns></returns>
public static IEndpointConventionBuilder MapQuery<T>(
this IEndpointRouteBuilder app,
Expand All @@ -32,11 +32,11 @@ public static IEndpointConventionBuilder MapQuery<T>(
}

/// <summary>
/// 添加一个命令 API,根据前缀选择 HTTP Method,错误会被自动处理。
/// Map a command API, using different HTTP methods based on prefix. See example for details.
/// </summary>
/// <param name="app"><see cref="ApplicationBuilder"/></param>
/// <param name="route">路径模板。</param>
/// <typeparam name="T">命令类型。</typeparam>
/// <param name="route">The route template.</param>
/// <typeparam name="T">The type of the command.</typeparam>
/// <example>
/// <code>
/// app.MapCommand&lt;CreateItemCommand&gt;("/items"); // Starts with 'Create' or 'Add' - POST
Expand All @@ -54,36 +54,11 @@ public static IEndpointConventionBuilder MapCommand<T>(
}

/// <summary>
/// 添加一个命令 API,根据前缀选择 HTTP Method,错误会被自动处理。
/// Map a query API, using GET method.
/// </summary>
/// <param name="app"><see cref="ApplicationBuilder"/></param>
/// <param name="route">路径模板。</param>
/// <param name="handler">返回 <typeparamref name="T"/> 的委托。</param>
/// <typeparam name="T">命令类型。</typeparam>
/// <example>
/// <code>
/// app.MapCommand&lt;CreateItemCommand&gt;("/items"); // Starts with 'Create' or 'Add' - POST
/// app.MapCommand&lt;UpdateItemCommand&gt;("/items/{id:int}") // Starts with 'Update' or 'Replace' - PUT
/// app.MapCommand&lt;DeleteCommand&gt;("/items/{id:int}") // Starts with 'Delete' or 'Remove' - DELETE
/// app.MapCommand&lt;ResetItemCommand&gt;("/items/{id:int}:reset) // Others - PUT
/// </code>
/// </example>
/// <returns></returns>
// ReSharper disable once UnusedTypeParameter
public static IEndpointConventionBuilder MapCommand<T>(
this IEndpointRouteBuilder app,
[StringSyntax("Route")] string route,
Delegate handler)
{
return app.MapCommand(route, handler);
}

/// <summary>
/// 添加一个查询 API,使用 GET 方法访问。
/// </summary>
/// <param name="app"><see cref="ApplicationBuilder"/></param>
/// <param name="route">路径模板。</param>
/// <param name="handler">构造查询的方法,需要返回 <see cref="IQuery{TView}"/> 的对象。</param>
/// <param name="route">The route template.</param>
/// <param name="handler">The delegate that returns a <see cref="IQuery{TView}"/> instance.</param>
/// <returns></returns>
public static IEndpointConventionBuilder MapQuery(
this IEndpointRouteBuilder app,
Expand All @@ -102,11 +77,11 @@ public static IEndpointConventionBuilder MapQuery(
}

/// <summary>
/// 添加一个命令 API,根据前缀选择 HTTP Method,错误会被自动处理。
/// Map a command API, using different method based on type name prefix.
/// </summary>
/// <param name="app"><see cref="ApplicationBuilder"/></param>
/// <param name="route">路径模板。</param>
/// <param name="handler">构造命令的方法,需要返回 <see cref="ICommand{TError}"/> 类型的对象。</param>
/// <param name="route">The route template.</param>
/// <param name="handler">The delegate that returns a instance of <see cref="ICommand{TError}"/>.</param>
/// <returns></returns>
/// <example>
/// <code>
Expand Down Expand Up @@ -142,11 +117,11 @@ public static IEndpointConventionBuilder MapCommand(
}

/// <summary>
/// 添加一个命令 API,使用 POST 方法访问,错误会被自动处理。
/// Map a command API, using POST method.
/// </summary>
/// <param name="app"><see cref="ApplicationBuilder"/></param>
/// <param name="route">路径模板。</param>
/// <param name="handler">构造命令的方法,需要返回 <see cref="ICommand{TError}"/> 类型的对象。</param>
/// <param name="route">The route template.</param>
/// <param name="handler">The delegate that returns a instance of <see cref="ICommand{TError}"/>.</param>
/// <returns></returns>
public static IEndpointConventionBuilder MapPostCommand(
this IEndpointRouteBuilder app,
Expand All @@ -158,11 +133,11 @@ public static IEndpointConventionBuilder MapPostCommand(
}

/// <summary>
/// 添加一个命令 API,使用 PUT 方法访问,错误会被自动处理。
/// Map a command API, using PUT method.
/// </summary>
/// <param name="app"><see cref="ApplicationBuilder"/></param>
/// <param name="route">路径模板。</param>
/// <param name="handler">构造命令的方法,需要返回 <see cref="ICommand{TError}"/> 类型的对象。</param>
/// <param name="route">The route template.</param>
/// <param name="handler">The delegate that returns a instance of <see cref="ICommand{TError}"/>.</param>
/// <returns></returns>
public static IEndpointConventionBuilder MapPutCommand(
this IEndpointRouteBuilder app,
Expand All @@ -174,11 +149,11 @@ public static IEndpointConventionBuilder MapPutCommand(
}

/// <summary>
/// 添加一个命令 API,使用 DELETE 方法访问,错误会被自动处理。
/// Map a command API, using DELETE method.
/// </summary>
/// <param name="app"><see cref="ApplicationBuilder"/></param>
/// <param name="route">路径模板。</param>
/// <param name="handler">构造命令的方法,需要返回 <see cref="ICommand{TError}"/> 类型的对象。</param>
/// <param name="route">The route template.</param>
/// <param name="handler">The delegate that returns a instance of <see cref="ICommand{TError}"/>.</param>
/// <returns></returns>
public static IEndpointConventionBuilder MapDeleteCommand(
this IEndpointRouteBuilder app,
Expand All @@ -199,4 +174,4 @@ private static void EnsureDelegateReturnTypeIsCommand(Delegate handler)
"handler does not return command, check if delegate returns type that implements ICommand<> or ICommand<,>");
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
namespace Cnblogs.Architecture.Ddd.Cqrs.AspNetCore;

/// <summary>
/// Model Binder for <see cref="PagingParams"/>
/// Model Binder for <see cref="PagingParams"/>
/// </summary>
public class PagingParamsModelBinder : IModelBinder
{
Expand Down Expand Up @@ -48,4 +48,4 @@ public Task BindModelAsync(ModelBindingContext bindingContext)
bindingContext.Result = ModelBindingResult.Success(pagingParams);
return Task.CompletedTask;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,16 @@
namespace Cnblogs.Architecture.Ddd.Cqrs.AspNetCore;

/// <summary>
/// 查询执行器,自动将返回内容提交给 mediator 并返回结果。
/// The query executor, auto send query to <see cref="IMediator"/>.
/// </summary>
public class QueryEndpointHandler : IEndpointFilter
{
private readonly IMediator _mediator;

/// <summary>
/// 创建一个查询执行器。
/// Create a <see cref="QueryEndpointHandler"/>.
/// </summary>
/// <param name="mediator"></param>
/// <param name="mediator">The mediator to use.</param>
public QueryEndpointHandler(IMediator mediator)
{
_mediator = mediator;
Expand All @@ -32,4 +32,4 @@ public QueryEndpointHandler(IMediator mediator)
var response = await _mediator.Send(query);
return response;
}
}
}
6 changes: 3 additions & 3 deletions test/Cnblogs.Architecture.IntegrationTestProject/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@
var v1 = apis.MapGroup("/api/v{version:apiVersion}").HasApiVersion(1);
v1.MapQuery<GetStringQuery>("strings/{id:int}");
v1.MapQuery<ListStringsQuery>("strings");
v1.MapCommand<CreateCommand>("strings", (CreatePayload payload) => new CreateCommand(payload.NeedError));
v1.MapCommand<UpdateCommand>(
v1.MapCommand("strings", (CreatePayload payload) => new CreateCommand(payload.NeedError));
v1.MapCommand(
"strings/{id:int}",
(int id, UpdatePayload payload) => new UpdateCommand(id, payload.NeedError));
v1.MapCommand<DeleteCommand>("strings/{id:int}");
Expand All @@ -51,4 +51,4 @@ namespace Cnblogs.Architecture.IntegrationTestProject
public partial class Program
{
}
}
}