Skip to content

Commit

Permalink
Ugly hack to make sure EF Core transactions on Wovlerine.Http endpoin…
Browse files Browse the repository at this point in the history
…ts flush cascading messages. Closes GH-620
  • Loading branch information
jeremydmiller committed Nov 14, 2023
1 parent b44ba3e commit 763630f
Show file tree
Hide file tree
Showing 7 changed files with 70 additions and 4 deletions.
13 changes: 12 additions & 1 deletion src/Http/Wolverine.Http/HttpChain.Codegen.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -114,9 +115,19 @@ internal IEnumerable<Frame> 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<MethodCall>().Any(x =>
x.HandlerType == typeof(MessageContext) &&
x.Method.Name == nameof(MessageContext.EnqueueCascadingAsync)))
{
if (actionsOnOtherReturnValues.OfType<CaptureCascadingMessages>().Any())
{
yield return MethodCall.For<MessageContext>(x => x.FlushOutgoingMessagesAsync());
}
}

foreach (var frame in Postprocessors) yield return frame;
}

Expand Down
3 changes: 2 additions & 1 deletion src/Http/Wolverine.Http/HttpChain.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
using Wolverine.Configuration;
using Wolverine.Http.CodeGen;
using Wolverine.Http.Metadata;
using Wolverine.Runtime;

namespace Wolverine.Http;

Expand Down Expand Up @@ -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()
Expand Down
27 changes: 26 additions & 1 deletion src/Samples/EFCoreSample/ItemService.Tests/end_to_end.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,32 @@ await host.Scenario(x =>
using var nested = host.Services.As<IContainer>().GetNestedContainer();
var context = nested.GetInstance<ItemsDbContext>();

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<Program>();
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<ItemCreated>()
.ShouldNotBeNull();

using var nested = host.Services.As<IContainer>().GetNestedContainer();
var context = nested.GetInstance<ItemsDbContext>();

var item = await context.Items.FirstOrDefaultAsync(x => x.Name == name);
item.ShouldNotBeNull();
}
}
25 changes: 25 additions & 0 deletions src/Samples/EFCoreSample/ItemService/CreateItemController.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using Microsoft.AspNetCore.Mvc;
using Wolverine.Attributes;
using Wolverine.EntityFrameworkCore;
using Wolverine.Http;

namespace ItemService;

Expand Down Expand Up @@ -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
};
}
}
1 change: 1 addition & 0 deletions src/Samples/EFCoreSample/ItemService/ItemService.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\..\Http\Wolverine.Http\Wolverine.Http.csproj" />
<ProjectReference Include="..\..\..\Persistence\Wolverine.EntityFrameworkCore\Wolverine.EntityFrameworkCore.csproj" />
<ProjectReference Include="..\..\..\Persistence\Wolverine.SqlServer\Wolverine.SqlServer.csproj" />
</ItemGroup>
Expand Down
3 changes: 3 additions & 0 deletions src/Samples/EFCoreSample/ItemService/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using Oakton.Resources;
using Wolverine;
using Wolverine.EntityFrameworkCore;
using Wolverine.Http;
using Wolverine.SqlServer;

var builder = WebApplication.CreateBuilder(args);
Expand Down Expand Up @@ -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));
Expand Down
2 changes: 1 addition & 1 deletion src/Wolverine/Runtime/Handlers/CaptureCascadingMessages.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

namespace Wolverine.Runtime.Handlers;

internal class CaptureCascadingMessages : MethodCall
public class CaptureCascadingMessages : MethodCall
{
private static readonly MethodInfo _method
= ReflectionHelper.GetMethod<MessageContext>(x => x.EnqueueCascadingAsync(null))!;
Expand Down

0 comments on commit 763630f

Please sign in to comment.