diff --git a/src/Cnblogs.Architecture.Ddd.Cqrs.Abstractions/CommandResponse.cs b/src/Cnblogs.Architecture.Ddd.Cqrs.Abstractions/CommandResponse.cs
index 89fab2e..614059b 100644
--- a/src/Cnblogs.Architecture.Ddd.Cqrs.Abstractions/CommandResponse.cs
+++ b/src/Cnblogs.Architecture.Ddd.Cqrs.Abstractions/CommandResponse.cs
@@ -148,7 +148,8 @@ private CommandResponse(TView response)
///
/// This property can be null even if execution completed with no error.
///
- public TView? Response { get; }
+ // ReSharper disable once AutoPropertyCanBeMadeGetOnly.Global
+ public TView? Response { get; init; }
///
/// Create a with given error.
@@ -184,4 +185,4 @@ public static CommandResponse Success(TView? view)
{
return Response;
}
-}
\ No newline at end of file
+}
diff --git a/src/Cnblogs.Architecture.Ddd.Cqrs.AspNetCore/ApiControllerBase.cs b/src/Cnblogs.Architecture.Ddd.Cqrs.AspNetCore/ApiControllerBase.cs
index aa78382..78fbe03 100644
--- a/src/Cnblogs.Architecture.Ddd.Cqrs.AspNetCore/ApiControllerBase.cs
+++ b/src/Cnblogs.Architecture.Ddd.Cqrs.AspNetCore/ApiControllerBase.cs
@@ -41,7 +41,7 @@ protected IActionResult HandleCommandResponse(CommandResponse re
}
///
- /// Handle command response and return 204 if success, 400 if error.
+ /// Handle command response and return 200 if success, 400 if error.
///
/// The command response.
/// The response type when success.
@@ -52,7 +52,7 @@ protected IActionResult HandleCommandResponse(CommandResponse
{
if (response.IsSuccess())
{
- return Ok(response.Response);
+ return Request.Headers.CqrsVersion() > 1 ? Ok(response) : Ok(response.Response);
}
return HandleCommandResponse((CommandResponse)response);
@@ -62,7 +62,7 @@ private IActionResult HandleErrorCommandResponse(CommandResponse
where TError : Enumeration
{
var errorResponseType = CqrsHttpOptions.CommandErrorResponseType;
- if (Request.Headers.Accept.Contains("application/cqrs"))
+ if (Request.Headers.Accept.Contains("application/cqrs") || Request.Headers.CqrsVersion() > 1)
{
errorResponseType = ErrorResponseType.Cqrs;
}
diff --git a/src/Cnblogs.Architecture.Ddd.Cqrs.AspNetCore/AssemblyInfo.cs b/src/Cnblogs.Architecture.Ddd.Cqrs.AspNetCore/AssemblyInfo.cs
new file mode 100644
index 0000000..f8afacf
--- /dev/null
+++ b/src/Cnblogs.Architecture.Ddd.Cqrs.AspNetCore/AssemblyInfo.cs
@@ -0,0 +1,3 @@
+using System.Runtime.CompilerServices;
+
+[assembly: InternalsVisibleTo("Cnblogs.Architecture.IntegrationTests")]
diff --git a/src/Cnblogs.Architecture.Ddd.Cqrs.AspNetCore/CommandEndpointHandler.cs b/src/Cnblogs.Architecture.Ddd.Cqrs.AspNetCore/CommandEndpointHandler.cs
index c5300f3..0b3b675 100644
--- a/src/Cnblogs.Architecture.Ddd.Cqrs.AspNetCore/CommandEndpointHandler.cs
+++ b/src/Cnblogs.Architecture.Ddd.Cqrs.AspNetCore/CommandEndpointHandler.cs
@@ -58,7 +58,9 @@ public CommandEndpointHandler(IMediator mediator, IOptions opti
// check if response has result
if (commandResponse is IObjectResponse objectResponse)
{
- return Results.Ok(objectResponse.GetResult());
+ return context.HttpContext.Request.Headers.CqrsVersion() > 1
+ ? Results.Extensions.Cqrs(response)
+ : Results.Ok(objectResponse.GetResult());
}
return Results.NoContent();
@@ -70,7 +72,8 @@ public CommandEndpointHandler(IMediator mediator, IOptions opti
private IResult HandleErrorCommandResponse(CommandResponse response, HttpContext context)
{
var errorResponseType = _options.CommandErrorResponseType;
- if (context.Request.Headers.Accept.Contains("application/cqrs"))
+ if (context.Request.Headers.Accept.Contains("application/cqrs")
+ || context.Request.Headers.Accept.Contains("application/cqrs-v2"))
{
errorResponseType = ErrorResponseType.Cqrs;
}
diff --git a/src/Cnblogs.Architecture.Ddd.Cqrs.AspNetCore/CqrsHeaderNames.cs b/src/Cnblogs.Architecture.Ddd.Cqrs.AspNetCore/CqrsHeaderNames.cs
new file mode 100644
index 0000000..e33a1bb
--- /dev/null
+++ b/src/Cnblogs.Architecture.Ddd.Cqrs.AspNetCore/CqrsHeaderNames.cs
@@ -0,0 +1,6 @@
+namespace Cnblogs.Architecture.Ddd.Cqrs.AspNetCore;
+
+internal static class CqrsHeaderNames
+{
+ public const string CqrsVersion = "X-Cqrs-Version";
+}
diff --git a/src/Cnblogs.Architecture.Ddd.Cqrs.AspNetCore/CqrsObjectResult.cs b/src/Cnblogs.Architecture.Ddd.Cqrs.AspNetCore/CqrsObjectResult.cs
new file mode 100644
index 0000000..3395296
--- /dev/null
+++ b/src/Cnblogs.Architecture.Ddd.Cqrs.AspNetCore/CqrsObjectResult.cs
@@ -0,0 +1,17 @@
+using Microsoft.AspNetCore.Mvc;
+
+namespace Cnblogs.Architecture.Ddd.Cqrs.AspNetCore;
+
+///
+/// Send command response as json and report current cqrs version.
+///
+///
+public class CqrsObjectResult(object? value) : ObjectResult(value)
+{
+ ///
+ public override Task ExecuteResultAsync(ActionContext context)
+ {
+ context.HttpContext.Response.Headers.AppendCurrentCqrsVersion();
+ return base.ExecuteResultAsync(context);
+ }
+}
diff --git a/src/Cnblogs.Architecture.Ddd.Cqrs.AspNetCore/CqrsResult.cs b/src/Cnblogs.Architecture.Ddd.Cqrs.AspNetCore/CqrsResult.cs
new file mode 100644
index 0000000..e391fd0
--- /dev/null
+++ b/src/Cnblogs.Architecture.Ddd.Cqrs.AspNetCore/CqrsResult.cs
@@ -0,0 +1,17 @@
+using Microsoft.AspNetCore.Http;
+
+namespace Cnblogs.Architecture.Ddd.Cqrs.AspNetCore;
+
+///
+/// Send object as json and append X-Cqrs-Version header
+///
+///
+public class CqrsResult(object commandResponse) : IResult
+{
+ ///
+ public Task ExecuteAsync(HttpContext httpContext)
+ {
+ httpContext.Response.Headers.Append("X-Cqrs-Version", "2");
+ return httpContext.Response.WriteAsJsonAsync(commandResponse);
+ }
+}
diff --git a/src/Cnblogs.Architecture.Ddd.Cqrs.AspNetCore/CqrsResultExtensions.cs b/src/Cnblogs.Architecture.Ddd.Cqrs.AspNetCore/CqrsResultExtensions.cs
new file mode 100644
index 0000000..cb6a2ef
--- /dev/null
+++ b/src/Cnblogs.Architecture.Ddd.Cqrs.AspNetCore/CqrsResultExtensions.cs
@@ -0,0 +1,21 @@
+using Microsoft.AspNetCore.Http;
+
+namespace Cnblogs.Architecture.Ddd.Cqrs.AspNetCore;
+
+///
+/// Extension methods for creating cqrs result.
+///
+public static class CqrsResultExtensions
+{
+ ///
+ /// Write result as json and append cqrs response header.
+ ///
+ ///
+ /// The command response.
+ ///
+ public static IResult Cqrs(this IResultExtensions extensions, object result)
+ {
+ ArgumentNullException.ThrowIfNull(extensions);
+ return new CqrsResult(result);
+ }
+}
diff --git a/src/Cnblogs.Architecture.Ddd.Cqrs.AspNetCore/CqrsRouteMapper.cs b/src/Cnblogs.Architecture.Ddd.Cqrs.AspNetCore/CqrsRouteMapper.cs
index f968a22..5b4ad3a 100644
--- a/src/Cnblogs.Architecture.Ddd.Cqrs.AspNetCore/CqrsRouteMapper.cs
+++ b/src/Cnblogs.Architecture.Ddd.Cqrs.AspNetCore/CqrsRouteMapper.cs
@@ -21,6 +21,12 @@ public static class CqrsRouteMapper
private static readonly string[] GetAndHeadMethods = { "GET", "HEAD" };
+ private static readonly List PostCommandPrefixes = new() { "Create", "Add", "New" };
+
+ private static readonly List PutCommandPrefixes = new() { "Update", "Modify", "Replace", "Alter" };
+
+ private static readonly List DeleteCommandPrefixes = new() { "Delete", "Remove", "Clean", "Clear", "Purge" };
+
///
/// Map a query API, using GET method. would been constructed from route and query string.
///
@@ -164,7 +170,23 @@ public static IEndpointConventionBuilder MapCommand(
this IEndpointRouteBuilder app,
[StringSyntax("Route")] string route)
{
- return app.MapCommand(route, ([AsParameters] T command) => command);
+ var commandTypeName = typeof(T).Name;
+ if (PostCommandPrefixes.Any(x => commandTypeName.StartsWith(x)))
+ {
+ return app.MapPostCommand(route);
+ }
+
+ if (PutCommandPrefixes.Any(x => commandTypeName.StartsWith(x)))
+ {
+ return app.MapPutCommand(route);
+ }
+
+ if (DeleteCommandPrefixes.Any(x => commandTypeName.StartsWith(x)))
+ {
+ return app.MapDeleteCommand(route);
+ }
+
+ return app.MapPutCommand(route);
}
///
@@ -189,17 +211,17 @@ public static IEndpointConventionBuilder MapCommand(
{
EnsureDelegateReturnTypeIsCommand(handler);
var commandTypeName = handler.Method.ReturnType.Name;
- if (commandTypeName.StartsWith("Create") || commandTypeName.StartsWith("Add"))
+ if (PostCommandPrefixes.Any(x => commandTypeName.StartsWith(x)))
{
return app.MapPostCommand(route, handler);
}
- if (commandTypeName.StartsWith("Update") || commandTypeName.StartsWith("Replace"))
+ if (PutCommandPrefixes.Any(x => commandTypeName.StartsWith(x)))
{
return app.MapPutCommand(route, handler);
}
- if (commandTypeName.StartsWith("Delete") || commandTypeName.StartsWith("Remove"))
+ if (DeleteCommandPrefixes.Any(x => commandTypeName.StartsWith(x)))
{
return app.MapDeleteCommand(route, handler);
}
@@ -297,6 +319,72 @@ public static IEndpointConventionBuilder MapDeleteCommand(
return app.MapDelete(route, handler).AddEndpointFilter();
}
+ ///
+ /// Map prefix to POST method for further MapCommand() calls.
+ ///
+ ///
+ /// The new prefix.
+ public static IEndpointRouteBuilder MapPrefixToPost(this IEndpointRouteBuilder app, string prefix)
+ {
+ PostCommandPrefixes.Add(prefix);
+ return app;
+ }
+
+ ///
+ /// Stop mapping prefix to POST method for further MapCommand() calls.
+ ///
+ ///
+ /// The new prefix.
+ public static IEndpointRouteBuilder StopMappingPrefixToPost(this IEndpointRouteBuilder app, string prefix)
+ {
+ PostCommandPrefixes.Remove(prefix);
+ return app;
+ }
+
+ ///
+ /// Map prefix to PUT method for further MapCommand() calls.
+ ///
+ ///
+ /// The new prefix.
+ public static IEndpointRouteBuilder MapPrefixToPut(this IEndpointRouteBuilder app, string prefix)
+ {
+ PutCommandPrefixes.Add(prefix);
+ return app;
+ }
+
+ ///
+ /// Stop mapping prefix to PUT method for further MapCommand() calls.
+ ///
+ ///
+ /// The new prefix.
+ public static IEndpointRouteBuilder StopMappingPrefixToPut(this IEndpointRouteBuilder app, string prefix)
+ {
+ PutCommandPrefixes.Remove(prefix);
+ return app;
+ }
+
+ ///
+ /// Map prefix to DELETE method for further MapCommand() calls.
+ ///
+ ///
+ /// The new prefix.
+ public static IEndpointRouteBuilder MapPrefixToDelete(this IEndpointRouteBuilder app, string prefix)
+ {
+ DeleteCommandPrefixes.Add(prefix);
+ return app;
+ }
+
+ ///
+ /// Stop mapping prefix to DELETE method for further MapCommand() calls.
+ ///
+ ///
+ /// The new prefix.
+ public static IEndpointRouteBuilder StopMappingPrefixToDelete(this IEndpointRouteBuilder app, string prefix)
+ {
+ DeleteCommandPrefixes.Remove(prefix);
+ return app;
+ }
+
private static void EnsureDelegateReturnTypeIsCommand(Delegate handler)
{
var isCommand = handler.Method.ReturnType.GetInterfaces().Where(x => x.IsGenericType)
diff --git a/src/Cnblogs.Architecture.Ddd.Cqrs.AspNetCore/CqrsVersionExtensions.cs b/src/Cnblogs.Architecture.Ddd.Cqrs.AspNetCore/CqrsVersionExtensions.cs
new file mode 100644
index 0000000..f5d5fec
--- /dev/null
+++ b/src/Cnblogs.Architecture.Ddd.Cqrs.AspNetCore/CqrsVersionExtensions.cs
@@ -0,0 +1,39 @@
+using System.Net.Http.Headers;
+using Microsoft.AspNetCore.Http;
+
+namespace Cnblogs.Architecture.Ddd.Cqrs.AspNetCore;
+
+internal static class CqrsVersionExtensions
+{
+ private const int CurrentCqrsVersion = 2;
+
+ public static int CqrsVersion(this IHeaderDictionary headers)
+ {
+ return int.TryParse(headers[CqrsHeaderNames.CqrsVersion].ToString(), out var version) ? version : 1;
+ }
+
+ public static int CqrsVersion(this HttpHeaders headers)
+ {
+ if (headers.Contains(CqrsHeaderNames.CqrsVersion) == false)
+ {
+ return 1;
+ }
+
+ return headers.GetValues(CqrsHeaderNames.CqrsVersion).Select(x => int.TryParse(x, out var y) ? y : 1).Max();
+ }
+
+ public static void CqrsVersion(this IHeaderDictionary headers, int version)
+ {
+ headers.Append(CqrsHeaderNames.CqrsVersion, version.ToString());
+ }
+
+ public static void AppendCurrentCqrsVersion(this IHeaderDictionary headers)
+ {
+ headers.CqrsVersion(CurrentCqrsVersion);
+ }
+
+ public static void AppendCurrentCqrsVersion(this HttpHeaders headers)
+ {
+ headers.Add(CqrsHeaderNames.CqrsVersion, CurrentCqrsVersion.ToString());
+ }
+}
diff --git a/src/Cnblogs.Architecture.Ddd.Cqrs.ServiceAgent/Cnblogs.Architecture.Ddd.Cqrs.ServiceAgent.csproj b/src/Cnblogs.Architecture.Ddd.Cqrs.ServiceAgent/Cnblogs.Architecture.Ddd.Cqrs.ServiceAgent.csproj
index e340534..223d37c 100644
--- a/src/Cnblogs.Architecture.Ddd.Cqrs.ServiceAgent/Cnblogs.Architecture.Ddd.Cqrs.ServiceAgent.csproj
+++ b/src/Cnblogs.Architecture.Ddd.Cqrs.ServiceAgent/Cnblogs.Architecture.Ddd.Cqrs.ServiceAgent.csproj
@@ -11,4 +11,12 @@
+
+
+ CqrsHeaderNames.cs
+
+
+ CqrsVersionExtensions.cs
+
+
diff --git a/src/Cnblogs.Architecture.Ddd.Cqrs.ServiceAgent/CqrsServiceAgent.cs b/src/Cnblogs.Architecture.Ddd.Cqrs.ServiceAgent/CqrsServiceAgent.cs
index a8b7bff..19f4da5 100644
--- a/src/Cnblogs.Architecture.Ddd.Cqrs.ServiceAgent/CqrsServiceAgent.cs
+++ b/src/Cnblogs.Architecture.Ddd.Cqrs.ServiceAgent/CqrsServiceAgent.cs
@@ -2,6 +2,7 @@
using System.Net.Http.Json;
using System.Text.Json;
using Cnblogs.Architecture.Ddd.Cqrs.Abstractions;
+using Cnblogs.Architecture.Ddd.Cqrs.AspNetCore;
using Cnblogs.Architecture.Ddd.Domain.Abstractions;
using Cnblogs.Architecture.Ddd.Infrastructure.Abstractions;
@@ -266,7 +267,7 @@ private static async Task> HandleCommandRespo
try
{
- if (httpResponseMessage.StatusCode == HttpStatusCode.OK)
+ if (httpResponseMessage.StatusCode == HttpStatusCode.OK && httpResponseMessage.Headers.CqrsVersion() == 1)
{
var result = await httpResponseMessage.Content.ReadFromJsonAsync();
return CommandResponse.Success(result);
diff --git a/src/Cnblogs.Architecture.Ddd.Cqrs.ServiceAgent/InjectExtensions.cs b/src/Cnblogs.Architecture.Ddd.Cqrs.ServiceAgent/InjectExtensions.cs
index 53bb635..a8ed4e5 100644
--- a/src/Cnblogs.Architecture.Ddd.Cqrs.ServiceAgent/InjectExtensions.cs
+++ b/src/Cnblogs.Architecture.Ddd.Cqrs.ServiceAgent/InjectExtensions.cs
@@ -1,5 +1,6 @@
using System.Net;
using System.Net.Http.Headers;
+using Cnblogs.Architecture.Ddd.Cqrs.AspNetCore;
using Microsoft.Extensions.DependencyInjection;
using Polly;
using Polly.Extensions.Http;
@@ -30,7 +31,7 @@ public static IHttpClientBuilder AddServiceAgent(
h =>
{
h.BaseAddress = new Uri(baseUri);
- h.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/cqrs"));
+ h.AddCqrsAcceptHeaders();
}).AddPolicyHandler(policy);
}
@@ -55,10 +56,16 @@ public static IHttpClientBuilder AddServiceAgent(
h =>
{
h.BaseAddress = new Uri(baseUri);
- h.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/cqrs"));
+ h.AddCqrsAcceptHeaders();
}).AddPolicyHandler(policy);
}
+ private static void AddCqrsAcceptHeaders(this HttpClient h)
+ {
+ h.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/cqrs"));
+ h.DefaultRequestHeaders.AppendCurrentCqrsVersion();
+ }
+
private static IAsyncPolicy GetDefaultPolicy()
{
return HttpPolicyExtensions.HandleTransientHttpError()
diff --git a/test/Cnblogs.Architecture.IntegrationTestProject/Application/Commands/CommandHandlers.cs b/test/Cnblogs.Architecture.IntegrationTestProject/Application/Commands/CommandHandlers.cs
index 1496920..fec1c5c 100644
--- a/test/Cnblogs.Architecture.IntegrationTestProject/Application/Commands/CommandHandlers.cs
+++ b/test/Cnblogs.Architecture.IntegrationTestProject/Application/Commands/CommandHandlers.cs
@@ -5,41 +5,34 @@
namespace Cnblogs.Architecture.IntegrationTestProject.Application.Commands;
-public class CommandHandlers
- : ICommandHandler, ICommandHandler,
- ICommandHandler
+public class CommandHandlers(IMediator mediator)
+ : ICommandHandler, ICommandHandler,
+ ICommandHandler
{
- private readonly IMediator _mediator;
-
- public CommandHandlers(IMediator mediator)
- {
- _mediator = mediator;
- }
-
///
- public async Task> Handle(CreateCommand request, CancellationToken cancellationToken)
+ public async Task> Handle(CreateCommand request, CancellationToken cancellationToken)
{
- await _mediator.Publish(new StringCreatedDomainEvent(request.Data ?? string.Empty), cancellationToken);
+ await mediator.Publish(new StringCreatedDomainEvent(request.Data ?? string.Empty), cancellationToken);
return request.NeedError
- ? CommandResponse.Fail(TestError.Default)
- : CommandResponse.Success();
+ ? CommandResponse.Fail(TestError.Default)
+ : CommandResponse.Success("create success");
}
///
- public Task> Handle(UpdateCommand request, CancellationToken cancellationToken)
+ public Task> Handle(UpdateCommand request, CancellationToken cancellationToken)
{
return Task.FromResult(
request.NeedExecutionError
- ? CommandResponse.Fail(TestError.Default)
- : CommandResponse.Success());
+ ? CommandResponse.Fail(TestError.Default)
+ : CommandResponse.Success("update success"));
}
///
- public Task> Handle(DeleteCommand request, CancellationToken cancellationToken)
+ public Task> Handle(DeleteCommand request, CancellationToken cancellationToken)
{
return Task.FromResult(
request.NeedError
- ? CommandResponse.Fail(TestError.Default)
- : CommandResponse.Success());
+ ? CommandResponse.Fail(TestError.Default)
+ : CommandResponse.Success("delete success"));
}
}
diff --git a/test/Cnblogs.Architecture.IntegrationTestProject/Application/Commands/CreateCommand.cs b/test/Cnblogs.Architecture.IntegrationTestProject/Application/Commands/CreateCommand.cs
index eeb78ef..964f08d 100644
--- a/test/Cnblogs.Architecture.IntegrationTestProject/Application/Commands/CreateCommand.cs
+++ b/test/Cnblogs.Architecture.IntegrationTestProject/Application/Commands/CreateCommand.cs
@@ -3,4 +3,4 @@
namespace Cnblogs.Architecture.IntegrationTestProject.Application.Commands;
-public record CreateCommand(bool NeedError, string? Data = null, bool ValidateOnly = false) : ICommand;
+public record CreateCommand(bool NeedError, string? Data = null, bool ValidateOnly = false) : ICommand;
diff --git a/test/Cnblogs.Architecture.IntegrationTestProject/Application/Commands/DeleteCommand.cs b/test/Cnblogs.Architecture.IntegrationTestProject/Application/Commands/DeleteCommand.cs
index d137b40..94816b9 100644
--- a/test/Cnblogs.Architecture.IntegrationTestProject/Application/Commands/DeleteCommand.cs
+++ b/test/Cnblogs.Architecture.IntegrationTestProject/Application/Commands/DeleteCommand.cs
@@ -3,4 +3,4 @@
namespace Cnblogs.Architecture.IntegrationTestProject.Application.Commands;
-public record DeleteCommand(int Id, bool NeedError, bool ValidateOnly = false) : ICommand;
\ No newline at end of file
+public record DeleteCommand(int Id, bool NeedError, bool ValidateOnly = false) : ICommand;
diff --git a/test/Cnblogs.Architecture.IntegrationTestProject/Application/Commands/UpdateCommand.cs b/test/Cnblogs.Architecture.IntegrationTestProject/Application/Commands/UpdateCommand.cs
index 73a3b5e..9121554 100644
--- a/test/Cnblogs.Architecture.IntegrationTestProject/Application/Commands/UpdateCommand.cs
+++ b/test/Cnblogs.Architecture.IntegrationTestProject/Application/Commands/UpdateCommand.cs
@@ -8,7 +8,7 @@ public record UpdateCommand(
bool NeedValidationError,
bool NeedExecutionError,
bool ValidateOnly = false)
- : ICommand, IValidatable
+ : ICommand, IValidatable
{
///
public void Validate(ValidationErrors errors)
diff --git a/test/Cnblogs.Architecture.IntegrationTests/CommandResponseHandlerTests.cs b/test/Cnblogs.Architecture.IntegrationTests/CommandResponseHandlerTests.cs
index dfe5c10..145eb5b 100644
--- a/test/Cnblogs.Architecture.IntegrationTests/CommandResponseHandlerTests.cs
+++ b/test/Cnblogs.Architecture.IntegrationTests/CommandResponseHandlerTests.cs
@@ -23,6 +23,75 @@ public class CommandResponseHandlerTests
new object[] { true, false }, new object[] { false, true }
};
+ [Fact]
+ public async Task MinimalApi_NoCqrsVersionHeader_RawResultAsync()
+ {
+ // Arrange
+ var builder = new WebApplicationFactory();
+ var client = builder.CreateClient();
+ client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/cqrs"));
+
+ // Act
+ var response = await client.PutAsJsonAsync("/api/v1/strings/1", new UpdatePayload());
+ var content = await response.Content.ReadAsStringAsync();
+
+ // Assert
+ content.Should().NotBeNullOrEmpty();
+ }
+
+ [Fact]
+ public async Task MinimalApi_CqrsV2_CommandResponseAsync()
+ {
+ // Arrange
+ var builder = new WebApplicationFactory();
+ var client = builder.CreateClient();
+ client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/cqrs"));
+ client.DefaultRequestHeaders.AppendCurrentCqrsVersion();
+
+ // Act
+ var response = await client.PutAsJsonAsync("/api/v1/strings/1", new UpdatePayload());
+ var content = await response.Content.ReadFromJsonAsync>();
+
+ // Assert
+ content.Should().NotBeNull();
+ content!.Response.Should().NotBeNullOrEmpty();
+ }
+
+ [Fact]
+ public async Task Mvc_NoCqrsVersionHeader_RawResultAsync()
+ {
+ // Arrange
+ var builder = new WebApplicationFactory();
+ var client = builder.CreateClient();
+ client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/cqrs"));
+
+ // Act
+ var response = await client.PutAsJsonAsync("/api/v1/mvc/strings/1", new UpdatePayload());
+ var content = await response.Content.ReadAsStringAsync();
+
+ // Assert
+ response.Should().BeSuccessful();
+ content.Should().NotBeNullOrEmpty();
+ }
+
+ [Fact]
+ public async Task Mvc_CurrentCqrsVersion_CommandResponseAsync()
+ {
+ // Arrange
+ var builder = new WebApplicationFactory();
+ var client = builder.CreateClient();
+ client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/cqrs"));
+ client.DefaultRequestHeaders.AppendCurrentCqrsVersion();
+
+ // Act
+ var response = await client.PutAsJsonAsync("/api/v1/mvc/strings/1", new UpdatePayload());
+ var content = await response.Content.ReadFromJsonAsync>();
+
+ // Assert
+ response.Should().BeSuccessful();
+ content!.Response.Should().NotBeNull();
+ }
+
[Theory]
[MemberData(nameof(ErrorPayloads))]
public async Task MinimalApi_HavingError_BadRequestAsync(bool needValidationError, bool needExecutionError)
diff --git a/test/Cnblogs.Architecture.IntegrationTests/IntegrationEventPublishTests.cs b/test/Cnblogs.Architecture.IntegrationTests/IntegrationEventPublishTests.cs
index 664d29a..8fbb04c 100644
--- a/test/Cnblogs.Architecture.IntegrationTests/IntegrationEventPublishTests.cs
+++ b/test/Cnblogs.Architecture.IntegrationTests/IntegrationEventPublishTests.cs
@@ -39,7 +39,7 @@ public async Task EventBus_PublishEvent_SuccessAsync()
// Assert
response.Should().BeSuccessful();
- content.Should().BeNullOrEmpty();
+ content.Should().NotBeNullOrEmpty();
await eventBusMock.Received(1).PublishAsync(
Arg.Any(),
Arg.Is(t => t.Message == data));
@@ -77,7 +77,7 @@ public async Task EventBus_Downgrading_DowngradeAsync()
// Assert
response.Should().BeSuccessful();
- content.Should().BeNullOrEmpty();
+ content.Should().NotBeNullOrEmpty();
await eventBusMock.Received(2).PublishAsync(
Arg.Any(),
Arg.Is(t => t.Message == data));