From 0796dbf69a254c5a054e3f3fb778a497d98bee4c Mon Sep 17 00:00:00 2001 From: "Jeremy D. Miller" Date: Mon, 8 Dec 2025 09:56:30 -0600 Subject: [PATCH] *Big* fix for how Wolverine handles primitive values bound by [FromForm] --- ...POST_api_tenants_tenant_counters_id_inc.cs | 83 ------------------ ...OST_api_tenants_tenant_counters_id_inc2.cs | 84 ------------------- ...multi_tenancy_detection_and_integration.cs | 20 ++++- ..._form_data_on_primitives_with_tenant_id.cs | 23 +++++ .../CodeGen/FormBindingFrame.cs | 5 +- 5 files changed, 45 insertions(+), 170 deletions(-) delete mode 100644 src/Http/Wolverine.Http.Tests/Internal/Generated/WolverineHandlers/POST_api_tenants_tenant_counters_id_inc.cs delete mode 100644 src/Http/Wolverine.Http.Tests/Internal/Generated/WolverineHandlers/POST_api_tenants_tenant_counters_id_inc2.cs create mode 100644 src/Http/Wolverine.Http.Tests/MultiTenancy/using_form_data_on_primitives_with_tenant_id.cs diff --git a/src/Http/Wolverine.Http.Tests/Internal/Generated/WolverineHandlers/POST_api_tenants_tenant_counters_id_inc.cs b/src/Http/Wolverine.Http.Tests/Internal/Generated/WolverineHandlers/POST_api_tenants_tenant_counters_id_inc.cs deleted file mode 100644 index 683040723..000000000 --- a/src/Http/Wolverine.Http.Tests/Internal/Generated/WolverineHandlers/POST_api_tenants_tenant_counters_id_inc.cs +++ /dev/null @@ -1,83 +0,0 @@ -// -#pragma warning disable -using Microsoft.AspNetCore.Routing; -using System; -using System.Linq; -using Wolverine.Http; -using Wolverine.Marten.Publishing; -using Wolverine.Runtime; - -namespace Internal.Generated.WolverineHandlers -{ - // START: POST_api_tenants_tenant_counters_id_inc - public class POST_api_tenants_tenant_counters_id_inc : Wolverine.Http.HttpHandler - { - private readonly Wolverine.Http.WolverineHttpOptions _wolverineHttpOptions; - private readonly Wolverine.Runtime.IWolverineRuntime _wolverineRuntime; - private readonly Wolverine.Marten.Publishing.OutboxedSessionFactory _outboxedSessionFactory; - - public POST_api_tenants_tenant_counters_id_inc(Wolverine.Http.WolverineHttpOptions wolverineHttpOptions, Wolverine.Runtime.IWolverineRuntime wolverineRuntime, Wolverine.Marten.Publishing.OutboxedSessionFactory outboxedSessionFactory) : base(wolverineHttpOptions) - { - _wolverineHttpOptions = wolverineHttpOptions; - _wolverineRuntime = wolverineRuntime; - _outboxedSessionFactory = outboxedSessionFactory; - } - - - - public override async System.Threading.Tasks.Task Handle(Microsoft.AspNetCore.Http.HttpContext httpContext) - { - System.Guid id = default; - - if (System.Guid.TryParse((string)httpContext.GetRouteValue("id"), System.Globalization.CultureInfo.InvariantCulture, out id)) - { - - } - - else - { - httpContext.Response.StatusCode = 404; - return; - } - - var messageContext = new Wolverine.Runtime.MessageContext(_wolverineRuntime); - // Building the Marten session - await using var documentSession = _outboxedSessionFactory.OpenSession(messageContext); - var counter = await documentSession.LoadAsync(id, httpContext.RequestAborted).ConfigureAwait(false); - // 404 if this required object is null - if (counter == null) - { - httpContext.Response.StatusCode = 404; - return; - } - - - // The actual HTTP request handler execution - (var result, var martenOp) = Wolverine.Http.Tests.Bugs.CounterEndpoint.Increment(counter); - - if (martenOp != null) - { - - // Placed by Wolverine's ISideEffect policy - martenOp.Execute(documentSession); - - } - - - // Save all pending changes to this Marten session - await documentSession.SaveChangesAsync(httpContext.RequestAborted).ConfigureAwait(false); - - - // Have to flush outgoing messages just in case Marten did nothing because of https://github.com/JasperFx/wolverine/issues/536 - await messageContext.FlushOutgoingMessagesAsync().ConfigureAwait(false); - - await result.ExecuteAsync(httpContext).ConfigureAwait(false); - } - - } - - // END: POST_api_tenants_tenant_counters_id_inc - - -} - diff --git a/src/Http/Wolverine.Http.Tests/Internal/Generated/WolverineHandlers/POST_api_tenants_tenant_counters_id_inc2.cs b/src/Http/Wolverine.Http.Tests/Internal/Generated/WolverineHandlers/POST_api_tenants_tenant_counters_id_inc2.cs deleted file mode 100644 index 0f8d754f4..000000000 --- a/src/Http/Wolverine.Http.Tests/Internal/Generated/WolverineHandlers/POST_api_tenants_tenant_counters_id_inc2.cs +++ /dev/null @@ -1,84 +0,0 @@ -// -#pragma warning disable -using Microsoft.AspNetCore.Routing; -using System; -using System.Linq; -using Wolverine.Http; -using Wolverine.Marten.Publishing; -using Wolverine.Runtime; - -namespace Internal.Generated.WolverineHandlers -{ - // START: POST_api_tenants_tenant_counters_id_inc2 - public class POST_api_tenants_tenant_counters_id_inc2 : Wolverine.Http.HttpHandler - { - private readonly Wolverine.Http.WolverineHttpOptions _wolverineHttpOptions; - private readonly Wolverine.Runtime.IWolverineRuntime _wolverineRuntime; - private readonly Wolverine.Marten.Publishing.OutboxedSessionFactory _outboxedSessionFactory; - - public POST_api_tenants_tenant_counters_id_inc2(Wolverine.Http.WolverineHttpOptions wolverineHttpOptions, Wolverine.Runtime.IWolverineRuntime wolverineRuntime, Wolverine.Marten.Publishing.OutboxedSessionFactory outboxedSessionFactory) : base(wolverineHttpOptions) - { - _wolverineHttpOptions = wolverineHttpOptions; - _wolverineRuntime = wolverineRuntime; - _outboxedSessionFactory = outboxedSessionFactory; - } - - - - public override async System.Threading.Tasks.Task Handle(Microsoft.AspNetCore.Http.HttpContext httpContext) - { - System.Guid id = default; - - if (System.Guid.TryParse((string)httpContext.GetRouteValue("id"), System.Globalization.CultureInfo.InvariantCulture, out id)) - { - - } - - else - { - httpContext.Response.StatusCode = 404; - return; - } - - var messageContext = new Wolverine.Runtime.MessageContext(_wolverineRuntime); - // Building the Marten session - await using var documentSession = _outboxedSessionFactory.OpenSession(messageContext); - var counter = await documentSession.LoadAsync(id, httpContext.RequestAborted).ConfigureAwait(false); - // 404 if this required object is null - if (counter == null) - { - httpContext.Response.StatusCode = 404; - return; - } - - - // The actual HTTP request handler execution - var martenOp = Wolverine.Http.Tests.Bugs.CounterEndpoint.Increment2(counter); - - if (martenOp != null) - { - - // Placed by Wolverine's ISideEffect policy - martenOp.Execute(documentSession); - - } - - - // Save all pending changes to this Marten session - await documentSession.SaveChangesAsync(httpContext.RequestAborted).ConfigureAwait(false); - - - // Have to flush outgoing messages just in case Marten did nothing because of https://github.com/JasperFx/wolverine/issues/536 - await messageContext.FlushOutgoingMessagesAsync().ConfigureAwait(false); - - // Wolverine automatically sets the status code to 204 for empty responses - if (httpContext.Response is { HasStarted: false, StatusCode: 200 }) httpContext.Response.StatusCode = 204; - } - - } - - // END: POST_api_tenants_tenant_counters_id_inc2 - - -} - diff --git a/src/Http/Wolverine.Http.Tests/MultiTenancy/multi_tenancy_detection_and_integration.cs b/src/Http/Wolverine.Http.Tests/MultiTenancy/multi_tenancy_detection_and_integration.cs index 560bda5aa..168223844 100644 --- a/src/Http/Wolverine.Http.Tests/MultiTenancy/multi_tenancy_detection_and_integration.cs +++ b/src/Http/Wolverine.Http.Tests/MultiTenancy/multi_tenancy_detection_and_integration.cs @@ -4,6 +4,7 @@ using Alba.Security; using IntegrationTests; using JasperFx; +using JasperFx.CodeGeneration; using JasperFx.MultiTenancy; using Marten; using Marten.Metadata; @@ -35,7 +36,7 @@ public multi_tenancy_detection_and_integration(ITestOutputHelper output) public void Dispose() { - theHost.Dispose(); + theHost?.Dispose(); } // The configuration of the Wolverine.HTTP endpoints is the only variable @@ -65,6 +66,11 @@ protected async Task configure(Action configure) opts.Policies.AutoApplyTransactions(); }); + builder.Services.CritterStackDefaults(opts => + { + opts.Development.GeneratedCodeMode = TypeLoadMode.Auto; + }); + builder.Services.AddWolverineHttp(); builder.Services.AddAuthentication("test"); builder.Services.AddAuthorization(); @@ -365,9 +371,19 @@ await configure(opts => opts.TenantId.IsRouteArgumentNamed("tenant"); opts.TenantId.AssertExists(); }); + + + + var chain = theHost.Services.GetRequiredService().Endpoints + .ChainFor("POST", "/tenant/{tenant}/formdata"); + + chain.IsFormData.ShouldBeTrue(); var formData = new Dictionary { { "value", "blue" } }; - var result = await theHost.Scenario(x => x.Post.FormData(formData).ToUrl("/tenant/red/formdata")); + var result = await theHost.Scenario(x => + { + x.Post.FormData(formData).ContentType("application/x-www-form-urlencoded").ToUrl("/tenant/red/formdata"); + }); result.ReadAsText().ShouldBe("red"); } diff --git a/src/Http/Wolverine.Http.Tests/MultiTenancy/using_form_data_on_primitives_with_tenant_id.cs b/src/Http/Wolverine.Http.Tests/MultiTenancy/using_form_data_on_primitives_with_tenant_id.cs new file mode 100644 index 000000000..de26ae604 --- /dev/null +++ b/src/Http/Wolverine.Http.Tests/MultiTenancy/using_form_data_on_primitives_with_tenant_id.cs @@ -0,0 +1,23 @@ +using JasperFx; +using JasperFx.CodeGeneration.Frames; +using JasperFx.MultiTenancy; +using Microsoft.Extensions.DependencyInjection; +using Shouldly; + +namespace Wolverine.Http.Tests.MultiTenancy; + +public class using_form_data_on_primitives_with_tenant_id +{ + [Fact] + public void determine_that_it_is_form_data_and_tenant_id_is_sourced_from_tenant_detection() + { + var method = new MethodCall(typeof(TenantedEndpoints), "GetTenantIdWithFormData"); + var serviceCollection = new ServiceCollection(); + var chain1 = new HttpChain(method, + new HttpGraph(new WolverineOptions(), new ServiceContainer(serviceCollection, serviceCollection.BuildServiceProvider()))); + + chain1.IsFormData.ShouldBeTrue(); + chain1.RequestType.ShouldNotBe(typeof(TenantId)); + + } +} \ No newline at end of file diff --git a/src/Http/Wolverine.Http/CodeGen/FormBindingFrame.cs b/src/Http/Wolverine.Http/CodeGen/FormBindingFrame.cs index 76b5c6e80..d12c24849 100644 --- a/src/Http/Wolverine.Http/CodeGen/FormBindingFrame.cs +++ b/src/Http/Wolverine.Http/CodeGen/FormBindingFrame.cs @@ -39,7 +39,10 @@ public bool TryMatch(HttpChain chain, IServiceContainer container, ParameterInfo return false; } variable = chain.TryFindOrCreateFormValue(parameter); - if(variable != null){ + if(variable != null) + { + chain.RequestType = typeof(void); + chain.IsFormData = true; // THIS IS IMPORTANT! return true; }