From 763630fc30a5f7549dde5f881def9826b72bccb6 Mon Sep 17 00:00:00 2001 From: "Jeremy D. Miller" Date: Tue, 14 Nov 2023 11:22:38 -0600 Subject: [PATCH] Ugly hack to make sure EF Core transactions on Wovlerine.Http endpoints flush cascading messages. Closes GH-620 --- src/Http/Wolverine.Http/HttpChain.Codegen.cs | 13 ++++++++- src/Http/Wolverine.Http/HttpChain.cs | 3 ++- .../ItemService.Tests/end_to_end.cs | 27 ++++++++++++++++++- .../ItemService/CreateItemController.cs | 25 +++++++++++++++++ .../ItemService/ItemService.csproj | 1 + .../EFCoreSample/ItemService/Program.cs | 3 +++ .../Handlers/CaptureCascadingMessages.cs | 2 +- 7 files changed, 70 insertions(+), 4 deletions(-) diff --git a/src/Http/Wolverine.Http/HttpChain.Codegen.cs b/src/Http/Wolverine.Http/HttpChain.Codegen.cs index 52c1201ea..6bf6f7ba6 100644 --- a/src/Http/Wolverine.Http/HttpChain.Codegen.cs +++ b/src/Http/Wolverine.Http/HttpChain.Codegen.cs @@ -10,6 +10,7 @@ using Wolverine.Http.CodeGen; using Wolverine.Http.Resources; using Wolverine.Logging; +using Wolverine.Runtime; using Wolverine.Runtime.Handlers; namespace Wolverine.Http; @@ -114,9 +115,19 @@ internal IEnumerable DetermineFrames(GenerationRules rules) yield return Method; var actionsOnOtherReturnValues = (NoContent ? Method.Creates : Method.Creates.Skip(1)) - .Select(x => x.ReturnAction(this)).SelectMany(x => x.Frames()); + .Select(x => x.ReturnAction(this)).SelectMany(x => x.Frames()).ToArray(); foreach (var frame in actionsOnOtherReturnValues) yield return frame; + if (!Postprocessors.OfType().Any(x => + x.HandlerType == typeof(MessageContext) && + x.Method.Name == nameof(MessageContext.EnqueueCascadingAsync))) + { + if (actionsOnOtherReturnValues.OfType().Any()) + { + yield return MethodCall.For(x => x.FlushOutgoingMessagesAsync()); + } + } + foreach (var frame in Postprocessors) yield return frame; } diff --git a/src/Http/Wolverine.Http/HttpChain.cs b/src/Http/Wolverine.Http/HttpChain.cs index 230c34b1f..845c3c26f 100644 --- a/src/Http/Wolverine.Http/HttpChain.cs +++ b/src/Http/Wolverine.Http/HttpChain.cs @@ -14,6 +14,7 @@ using Wolverine.Configuration; using Wolverine.Http.CodeGen; using Wolverine.Http.Metadata; +using Wolverine.Runtime; namespace Wolverine.Http; @@ -230,7 +231,7 @@ public override string ToString() public override bool RequiresOutbox() { - return ServiceDependencies(_parent.Container, Type.EmptyTypes).Contains(typeof(IMessageBus)); + return ServiceDependencies(_parent.Container, Type.EmptyTypes).Contains(typeof(IMessageBus)) || ServiceDependencies(_parent.Container, Type.EmptyTypes).Contains(typeof(MessageContext)); } private void applyMetadata() diff --git a/src/Samples/EFCoreSample/ItemService.Tests/end_to_end.cs b/src/Samples/EFCoreSample/ItemService.Tests/end_to_end.cs index 25ef8599d..a62166994 100644 --- a/src/Samples/EFCoreSample/ItemService.Tests/end_to_end.cs +++ b/src/Samples/EFCoreSample/ItemService.Tests/end_to_end.cs @@ -54,7 +54,32 @@ await host.Scenario(x => using var nested = host.Services.As().GetNestedContainer(); var context = nested.GetInstance(); - var item = context.Items.FirstOrDefaultAsync(x => x.Name == name); + var item = await context.Items.FirstOrDefaultAsync(x => x.Name == name); + item.ShouldNotBeNull(); + } + + [Fact] + public async Task execute_through_wolverine_http() + { + var name = Guid.NewGuid().ToString(); + using var host = await AlbaHost.For(); + var tracked = await host.ExecuteAndWaitAsync(async () => + { + await host.Scenario(x => + { + var command = new CreateItemCommand { Name = name }; + x.Post.Json(command).ToUrl("/items/create4"); + x.StatusCodeShouldBe(204); + }); + }); + + tracked.FindSingleTrackedMessageOfType() + .ShouldNotBeNull(); + + using var nested = host.Services.As().GetNestedContainer(); + var context = nested.GetInstance(); + + var item = await context.Items.FirstOrDefaultAsync(x => x.Name == name); item.ShouldNotBeNull(); } } \ No newline at end of file diff --git a/src/Samples/EFCoreSample/ItemService/CreateItemController.cs b/src/Samples/EFCoreSample/ItemService/CreateItemController.cs index 36d373602..354e1fc50 100644 --- a/src/Samples/EFCoreSample/ItemService/CreateItemController.cs +++ b/src/Samples/EFCoreSample/ItemService/CreateItemController.cs @@ -1,5 +1,7 @@ using Microsoft.AspNetCore.Mvc; +using Wolverine.Attributes; using Wolverine.EntityFrameworkCore; +using Wolverine.Http; namespace ItemService; @@ -74,4 +76,27 @@ await outbox.PublishAsync(new ItemCreated } #endregion +} + +public static class CreateItemEndpoint +{ + [Transactional] + [WolverinePost("/items/create4"), EmptyResponse] + public static ItemCreated Post(CreateItemCommand command, ItemsDbContext dbContext) + { + // Create a new Item entity + var item = new Item + { + Name = command.Name + }; + + // Add the item to the current + // DbContext unit of work + dbContext.Items.Add(item); + + return new ItemCreated + { + Id = item.Id + }; + } } \ No newline at end of file diff --git a/src/Samples/EFCoreSample/ItemService/ItemService.csproj b/src/Samples/EFCoreSample/ItemService/ItemService.csproj index 22c169750..b69ef787f 100644 --- a/src/Samples/EFCoreSample/ItemService/ItemService.csproj +++ b/src/Samples/EFCoreSample/ItemService/ItemService.csproj @@ -12,6 +12,7 @@ + diff --git a/src/Samples/EFCoreSample/ItemService/Program.cs b/src/Samples/EFCoreSample/ItemService/Program.cs index 684173256..64ab75bda 100644 --- a/src/Samples/EFCoreSample/ItemService/Program.cs +++ b/src/Samples/EFCoreSample/ItemService/Program.cs @@ -4,6 +4,7 @@ using Oakton.Resources; using Wolverine; using Wolverine.EntityFrameworkCore; +using Wolverine.Http; using Wolverine.SqlServer; var builder = WebApplication.CreateBuilder(args); @@ -73,6 +74,8 @@ app.MapControllers(); +app.MapWolverineEndpoints(); + app.MapPost("/items/create", (CreateItemCommand command, IMessageBus bus) => bus.InvokeAsync(command)); app.MapPost("/items/createWithDbContextNotIntegratedWithOutbox", (CreateItemWithDbContextNotIntegratedWithOutboxCommand command, IMessageBus bus) => bus.InvokeAsync(command)); diff --git a/src/Wolverine/Runtime/Handlers/CaptureCascadingMessages.cs b/src/Wolverine/Runtime/Handlers/CaptureCascadingMessages.cs index 620892a13..6874a02d6 100644 --- a/src/Wolverine/Runtime/Handlers/CaptureCascadingMessages.cs +++ b/src/Wolverine/Runtime/Handlers/CaptureCascadingMessages.cs @@ -5,7 +5,7 @@ namespace Wolverine.Runtime.Handlers; -internal class CaptureCascadingMessages : MethodCall +public class CaptureCascadingMessages : MethodCall { private static readonly MethodInfo _method = ReflectionHelper.GetMethod(x => x.EnqueueCascadingAsync(null))!;