diff --git a/Directory.Packages.props b/Directory.Packages.props
index bfaa105dd..3aea6d1be 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -32,13 +32,13 @@
-
-
-
+
+
+
-
+
diff --git a/src/Http/Wolverine.Http/CodeGen/JsonHandling.cs b/src/Http/Wolverine.Http/CodeGen/JsonHandling.cs
index d6f682297..09a4a56d4 100644
--- a/src/Http/Wolverine.Http/CodeGen/JsonHandling.cs
+++ b/src/Http/Wolverine.Http/CodeGen/JsonHandling.cs
@@ -6,6 +6,7 @@
using JasperFx.Core.Reflection;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
+using Wolverine.Configuration;
using Wolverine.Runtime;
namespace Wolverine.Http.CodeGen;
@@ -40,6 +41,20 @@ public override void GenerateCode(GeneratedMethod method, ISourceWriter writer)
Next?.GenerateCode(method, writer);
}
+
+ public override void GenerateFSharpCode(GeneratedMethod method, ISourceWriter writer)
+ {
+ writer.WriteComment("Reading the request body via JSON deserialization");
+
+ // ReadJsonAsync is an inherited *instance* method on HttpHandler (qualified with the member's
+ // `this` self identifier, jasperfx#393) returning (body, continuation). F# has no early
+ // `return`, so the abort guard renders the rest of the chain inside its `else` branch.
+ writer.Write(
+ $"let! ({Variable.Usage}, jsonContinue) = this.{nameof(HttpHandler.ReadJsonAsync)}<{Variable.VariableType.FSharpName()}>(httpContext)");
+
+ var condition = $"jsonContinue = {typeof(HandlerContinuation).FSharpName()}.{nameof(HandlerContinuation.Stop)}";
+ FSharpEmitHelpers.WriteAbortGuard(writer, method, condition, Next);
+ }
}
internal class JsonBodyParameterStrategy : IParameterStrategy
diff --git a/src/Http/Wolverine.Http/CodeGen/WriteJsonFrame.cs b/src/Http/Wolverine.Http/CodeGen/WriteJsonFrame.cs
index 514008603..18b7bf32a 100644
--- a/src/Http/Wolverine.Http/CodeGen/WriteJsonFrame.cs
+++ b/src/Http/Wolverine.Http/CodeGen/WriteJsonFrame.cs
@@ -20,4 +20,16 @@ public override void GenerateCode(GeneratedMethod method, ISourceWriter writer)
writer.Write($"await {nameof(HttpHandler.WriteJsonAsync)}(httpContext, {_resourceVariable.Usage});");
Next?.GenerateCode(method, writer);
}
+
+ public override void GenerateFSharpCode(GeneratedMethod method, ISourceWriter writer)
+ {
+ writer.WriteComment("Writing the response body to JSON because this was the first 'return variable' in the method signature");
+
+ // WriteJsonAsync is an inherited *instance* method on HttpHandler, so it must be qualified with
+ // the generated member's `this` self identifier (jasperfx#393).
+ var call = $"this.{nameof(HttpHandler.WriteJsonAsync)}(httpContext, {_resourceVariable.Usage})";
+ writer.Write(method.AsyncMode == AsyncMode.AsyncTask ? $"do! {call}" : call);
+
+ Next?.GenerateFSharpCode(method, writer);
+ }
}
\ No newline at end of file
diff --git a/src/Testing/Wolverine.Core.FSharpFixture/Generated.fs b/src/Testing/Wolverine.Core.FSharpFixture/Generated.fs
index cdc79fa08..8a56551db 100644
--- a/src/Testing/Wolverine.Core.FSharpFixture/Generated.fs
+++ b/src/Testing/Wolverine.Core.FSharpFixture/Generated.fs
@@ -14,7 +14,7 @@ type CheckThingHandler649476295(loggerForMessage: Microsoft.Extensions.Logging.I
inherit Wolverine.Runtime.Handlers.MessageHandler()
let _loggerForMessage = loggerForMessage
- override _.HandleAsync(context: Wolverine.Runtime.MessageContext, cancellation: System.Threading.CancellationToken) : System.Threading.Tasks.Task =
+ override this.HandleAsync(context: Wolverine.Runtime.MessageContext, cancellation: System.Threading.CancellationToken) : System.Threading.Tasks.Task =
// The actual message body
let checkThing = context.Envelope.Message :?> Wolverine.Core.FSharpContracts.CheckThing
@@ -32,13 +32,14 @@ type CheckThingHandler649476295(loggerForMessage: Microsoft.Extensions.Logging.I
// The actual message execution
checkThingHandler.Handle(checkThing)
+ this.RecordCauseAndEffect(context, context.Runtime.Observer)
System.Threading.Tasks.Task.CompletedTask
type CreateNameHandler1923366998(loggerForMessage: Microsoft.Extensions.Logging.ILogger) =
inherit Wolverine.Runtime.Handlers.MessageHandler()
let _loggerForMessage = loggerForMessage
- override _.HandleAsync(context: Wolverine.Runtime.MessageContext, cancellation: System.Threading.CancellationToken) : System.Threading.Tasks.Task =
+ override this.HandleAsync(context: Wolverine.Runtime.MessageContext, cancellation: System.Threading.CancellationToken) : System.Threading.Tasks.Task =
task {
// The actual message body
let createName = context.Envelope.Message :?> Wolverine.Core.FSharpContracts.CreateName
@@ -64,11 +65,12 @@ type CreateNameHandler1923366998(loggerForMessage: Microsoft.Extensions.Logging.
// Outgoing, cascaded message
do! context.EnqueueCascadingAsync(outgoing1)
+ this.RecordCauseAndEffect(context, context.Runtime.Observer)
}
type GateHandler1696712162() =
inherit Wolverine.Runtime.Handlers.MessageHandler()
- override _.HandleAsync(context: Wolverine.Runtime.MessageContext, cancellation: System.Threading.CancellationToken) : System.Threading.Tasks.Task =
+ override this.HandleAsync(context: Wolverine.Runtime.MessageContext, cancellation: System.Threading.CancellationToken) : System.Threading.Tasks.Task =
// The actual message body
let gate = context.Envelope.Message :?> Wolverine.Core.FSharpContracts.Gate
@@ -86,13 +88,14 @@ type GateHandler1696712162() =
// The actual message execution
gateHandler.Handle(gate)
+ this.RecordCauseAndEffect(context, context.Runtime.Observer)
System.Threading.Tasks.Task.CompletedTask
type IncrementCountHandler540640831(inMemorySagaPersistor: Wolverine.Persistence.Sagas.InMemorySagaPersistor) =
inherit Wolverine.Runtime.Handlers.MessageHandler()
let _inMemorySagaPersistor = inMemorySagaPersistor
- override _.HandleAsync(context: Wolverine.Runtime.MessageContext, cancellation: System.Threading.CancellationToken) : System.Threading.Tasks.Task =
+ override this.HandleAsync(context: Wolverine.Runtime.MessageContext, cancellation: System.Threading.CancellationToken) : System.Threading.Tasks.Task =
// The actual message body
let incrementCount = context.Envelope.Message :?> Wolverine.Core.FSharpContracts.IncrementCount
@@ -126,7 +129,7 @@ type StartCountHandler1561563330(inMemorySagaPersistor: Wolverine.Persistence.Sa
inherit Wolverine.Runtime.Handlers.MessageHandler()
let _inMemorySagaPersistor = inMemorySagaPersistor
- override _.HandleAsync(context: Wolverine.Runtime.MessageContext, cancellation: System.Threading.CancellationToken) : System.Threading.Tasks.Task =
+ override this.HandleAsync(context: Wolverine.Runtime.MessageContext, cancellation: System.Threading.CancellationToken) : System.Threading.Tasks.Task =
// The actual message body
let startCount = context.Envelope.Message :?> Wolverine.Core.FSharpContracts.StartCount
diff --git a/src/Testing/Wolverine.Core.FSharpTests/FSharpCodegenSample.cs b/src/Testing/Wolverine.Core.FSharpTests/FSharpCodegenSample.cs
index 941b8449e..5587f2bf9 100644
--- a/src/Testing/Wolverine.Core.FSharpTests/FSharpCodegenSample.cs
+++ b/src/Testing/Wolverine.Core.FSharpTests/FSharpCodegenSample.cs
@@ -38,6 +38,10 @@ public static string GenerateCode()
// Inserts ApplyExecutionDiagnosticTagsFrame at the head of every chain.
opts.Tracking.HandlerExecutionDiagnosticsEnabled = true;
+
+ // Inserts RecordMessageCausationFrame after the handler call (a `this.`-qualified
+ // inherited instance call, now resolvable per JasperFx 2.2.4 / jasperfx#393).
+ opts.Tracking.EnableMessageCausationTracking = true;
})
.Build();
diff --git a/src/Testing/Wolverine.Http.FSharpFixture/Generated.fs b/src/Testing/Wolverine.Http.FSharpFixture/Generated.fs
index 44e81e55f..70e95a965 100644
--- a/src/Testing/Wolverine.Http.FSharpFixture/Generated.fs
+++ b/src/Testing/Wolverine.Http.FSharpFixture/Generated.fs
@@ -13,7 +13,7 @@ type GET_fsharp_hello(wolverineHttpOptions: Wolverine.Http.WolverineHttpOptions)
inherit Wolverine.Http.HttpHandler(wolverineHttpOptions)
let _wolverineHttpOptions = wolverineHttpOptions
- override _.Handle(httpContext: Microsoft.AspNetCore.Http.HttpContext) : System.Threading.Tasks.Task =
+ override this.Handle(httpContext: Microsoft.AspNetCore.Http.HttpContext) : System.Threading.Tasks.Task =
task {
let thingEndpoints = Wolverine.Http.FSharpContracts.ThingEndpoints()
@@ -23,3 +23,23 @@ type GET_fsharp_hello(wolverineHttpOptions: Wolverine.Http.WolverineHttpOptions)
do! Wolverine.Http.HttpHandler.WriteString(httpContext, result_of_Hello)
}
+type POST_fsharp_things(wolverineHttpOptions: Wolverine.Http.WolverineHttpOptions) =
+ inherit Wolverine.Http.HttpHandler(wolverineHttpOptions)
+ let _wolverineHttpOptions = wolverineHttpOptions
+
+ override this.Handle(httpContext: Microsoft.AspNetCore.Http.HttpContext) : System.Threading.Tasks.Task =
+ task {
+ // Reading the request body via JSON deserialization
+ let! (command, jsonContinue) = this.ReadJsonAsync(httpContext)
+ if jsonContinue = Wolverine.HandlerContinuation.Stop then
+ ()
+ else
+ let thingEndpoints = Wolverine.Http.FSharpContracts.ThingEndpoints()
+
+ // The actual HTTP request handler execution
+ let thingCreated_response = thingEndpoints.Create(command)
+
+ // Writing the response body to JSON because this was the first 'return variable' in the method signature
+ do! this.WriteJsonAsync(httpContext, thingCreated_response)
+ }
+
diff --git a/src/Testing/Wolverine.Http.FSharpTests/HttpFSharpCodegenSample.cs b/src/Testing/Wolverine.Http.FSharpTests/HttpFSharpCodegenSample.cs
index 60032482f..e4059681a 100644
--- a/src/Testing/Wolverine.Http.FSharpTests/HttpFSharpCodegenSample.cs
+++ b/src/Testing/Wolverine.Http.FSharpTests/HttpFSharpCodegenSample.cs
@@ -27,14 +27,13 @@ public static string GenerateCode()
var container = new ServiceContainer(registry, registry.BuildServiceProvider());
var httpGraph = new HttpGraph(new WolverineOptions { ApplicationAssembly = typeof(ThingEndpoints).Assembly }, container);
- // Phase C increment 1 renders the static-response GET endpoint. The JSON POST endpoint
- // (ReadJsonBody / WriteJsonFrame) calls *instance* HttpHandler methods unqualified, which F#
- // cannot resolve from a `member _.Handle` body — blocked on the JasperFx F# self-identifier
- // gap (tracked upstream; same gap as RecordMessageCausationFrame). Add it back once JasperFx
- // emits a named self for generated members.
+ // GET (static string response) + POST (JSON body bind + JSON response). The JSON path calls
+ // the inherited instance HttpHandler methods ReadJsonAsync/WriteJsonAsync, now qualified with
+ // the generated member's `this` self identifier (JasperFx 2.2.4 / jasperfx#393).
var chains = new[]
{
- HttpChain.ChainFor(x => x.Hello(), httpGraph)
+ HttpChain.ChainFor(x => x.Hello(), httpGraph),
+ HttpChain.ChainFor(x => x.Create(null!), httpGraph)
};
var generatedAssembly = httpGraph.StartAssembly(httpGraph.Rules);
diff --git a/src/Wolverine/Runtime/RecordMessageCausationFrame.cs b/src/Wolverine/Runtime/RecordMessageCausationFrame.cs
index c41aaec52..61005d551 100644
--- a/src/Wolverine/Runtime/RecordMessageCausationFrame.cs
+++ b/src/Wolverine/Runtime/RecordMessageCausationFrame.cs
@@ -45,4 +45,14 @@ public override void GenerateCode(GeneratedMethod method, ISourceWriter writer)
Next?.GenerateCode(method, writer);
}
+
+ public override void GenerateFSharpCode(GeneratedMethod method, ISourceWriter writer)
+ {
+ // RecordCauseAndEffect is an inherited instance method on the generated MessageHandler subclass,
+ // so it must be qualified with the member's `this` self identifier (jasperfx#393).
+ writer.Write(
+ $"this.{nameof(MessageHandler.RecordCauseAndEffect)}({_context!.Usage}, {_context!.Usage}.Runtime.Observer)");
+
+ Next?.GenerateFSharpCode(method, writer);
+ }
}