Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
<NoWarn>1570;1571;1572;1573;1574;1587;1591;1701;1702;1711;1735;0618</NoWarn>
<ImplicitUsings>true</ImplicitUsings>
<Nullable>enable</Nullable>
<Version>4.10.1</Version>
<Version>4.11.0</Version>
<RepositoryUrl>$(PackageProjectUrl)</RepositoryUrl>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
Expand Down
20 changes: 20 additions & 0 deletions WolverineWebApiFSharp/Program.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@

open System
open JasperFx
open Marten
open Microsoft.AspNetCore.Builder
open Wolverine
open Wolverine.Http

// We'll come back to this later. Used it for now to manually test some changes
// let args = Environment.GetCommandLineArgs()[1..]
//
// let builder = WebApplication.CreateBuilder(args)
//
// builder.Host.UseWolverine() |> ignore
// builder.Services.AddWolverineHttp() |> ignore
// builder.Services.AddMarten("Host=localhost;Port=12345;Username=postgres;Password=postgres;Database=postgres") |> ignore
//
// let app = builder.Build()
// app.MapWolverineEndpoints();
// app.RunJasperFxCommands(args).GetAwaiter().GetResult() |> ignore
43 changes: 43 additions & 0 deletions WolverineWebApiFSharp/SideEffects.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
module WolverineWebApiFSharp.SideEffects

open System
open Wolverine
open Wolverine.Http
open Wolverine.Marten

type Command = { Name: string }

(* I cannot define a custom side effect here because code generation does not recognize the Execute method
Unhandled exception. Wolverine.InvalidSideEffectException: Invalid Wolverine side effect exception for Wolverine.ISideEffect, no public Execute/ExecuteAsync method found
at Wolverine.SideEffectPolicy.applySideEffectExecution(Variable effect, IChain chain) in /Users/marcpiechura/RiderProjects/wolverine/src/Wolverine/ISideEffect.cs:line 93
at Wolverine.SideEffectPolicy.lookForSingularSideEffects(GenerationRules rules, IServiceContainer container, IChain chain) in /Users/marcpiechura/RiderProjects/wolverine/src/Wolverine/ISideEffect.cs:line 62
at Wolverine.SideEffectPolicy.Apply(IReadOnlyList`1 chains, GenerationRules rules, IServiceContainer container) in /Users/marcpiechura/RiderProjects/wolverine/src/Wolverine/ISideEffect.cs:line 42
at Wolverine.Http.HttpGraph.DiscoverEndpoints(WolverineHttpOptions wolverineHttpOptions) in /Users/marcpiechura/RiderProjects/wolverine/src/Http/Wolverine.Http/HttpGraph.cs:line 107
at Wolverine.Http.WolverineHttpEndpointRouteBuilderExtensions.MapWolverineEndpoints(IEndpointRouteBuilder endpoints, Action`1 configure) in /Users/marcpiechura/RiderProjects/wolverine/src/Http/Wolverine.Http/WolverineHttpEndpointRouteBuilderExtensions.cs:line 202

*)
type SomeSideEffect() =
static member val WasExecuted = false with get, set

interface ISideEffect

member this.Execute() =
SomeSideEffect.WasExecuted <- true


type Event = { Id: Guid; Name: string }
type SomeType = { Id: Guid }

// this one is a bit more tricky, generally using ISideEffect works,
// but for MartenOps.StartStream one has to provide the generic type parameter, otherwise
// codegen will not generate a handler at all.

[<WolverinePost("start-stream")>]
[<EmptyResponse>]
let post (command: Command) =
let event: Event = { Id = Guid.NewGuid(); Name = command.Name }
//this doesn't work
MartenOps.StartStream(event.Id, box event)

// but this does
//MartenOps.StartStream<SomeType>(event.Id, box event)
64 changes: 64 additions & 0 deletions WolverineWebApiFSharp/TupleSupport.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
namespace WolverineWebApiFSharp.TupleSupport

open System
open Microsoft.AspNetCore.Mvc
open Wolverine.Http

type Command = { SomeProperty: string }

// The root problem is, that F# uses System.Tuple by default, but C# uses System.ValueTuple when defining a tuple with (int, string) syntax.
// A workaround for F# is to use struct(int, string) which compiles to ValueTuple.
// That affects at least the ability to pass values from Before/Validate.. methods to the handler method,
// as well as the ability to use cascading messages in the handler method.

module ValidationSupport =
(* Codegen looks like this:
public override async System.Threading.Tasks.Task Handle(Microsoft.AspNetCore.Http.HttpContext httpContext)
{
// Reading the request body via JSON deserialization
var (command, jsonContinue) = await ReadJsonAsync<WolverineWebApiFSharp.TupleSupport.Command>(httpContext);
if (jsonContinue == Wolverine.HandlerContinuation.Stop) return;
var validationResult = httpContext.Request.Query["validationResult"];
var tuple = WolverineWebApiFSharp.TupleSupport.ValidationSupport.Validate(command);

// The actual HTTP request handler execution
var result_of_post = WolverineWebApiFSharp.TupleSupport.ValidationSupport.post(command, validationResult);

await WriteString(httpContext, result_of_post);
}
*)

let Validate (command: Command) =
if String.IsNullOrWhiteSpace(command.SomeProperty) then
ProblemDetails(Status = 400), ""
else
WolverineContinue.NoProblems, command.SomeProperty


[<WolverinePost("/tuple-support/validate")>]
let post (command: Command, validationResult: string) =
validationResult


module CascadingMessages =
(* Codegen looks like this
public override async System.Threading.Tasks.Task Handle(Microsoft.AspNetCore.Http.HttpContext httpContext)
{
// Reading the request body via JSON deserialization
var (command, jsonContinue) = await ReadJsonAsync<WolverineWebApiFSharp.TupleSupport.Command>(httpContext);
if (jsonContinue == Wolverine.HandlerContinuation.Stop) return;

// The actual HTTP request handler execution
var tuple_response = WolverineWebApiFSharp.TupleSupport.AdditionalReturnValues.post(command);

// Writing the response body to JSON because this was the first 'return variable' in the method signature
await WriteJsonAsync(httpContext, tuple_response);
}
*)

type Event = { Id: Guid }

[<WolverinePost("/tuple-support/cascading-messages")>]
let post (command: Command) =
let event = { Id = Guid.NewGuid() }
event.Id, event
43 changes: 43 additions & 0 deletions WolverineWebApiFSharp/UnitSupport.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
namespace WolverineWebApiFSharp.UnitSupport

open System.Threading.Tasks
open Wolverine.Http

type Command = { SomeProperty: string }

// In F# the type Unit defines the absence of a value, similar to void in C#.
// In C# it is represented as a Task with no result, which is equivalent to Task<Unit>.
// But the codegen treats the Unit type as a regular type and generates a cascaded message call for it.
// I've only encountered this in the context of Task handlers, but will add more examples if I find them.

module TaskUnitSupport =
(* Codegen looks like this:
public override async System.Threading.Tasks.Task Handle(Microsoft.AspNetCore.Http.HttpContext httpContext)
{
var messageContext = new Wolverine.Runtime.MessageContext(_wolverineRuntime);
// Reading the request body via JSON deserialization
var (command, jsonContinue) = await ReadJsonAsync<WolverineWebApiFSharp.UnitSupport.Command>(httpContext);
if (jsonContinue == Wolverine.HandlerContinuation.Stop) return;

// The actual HTTP request handler execution
var unit = await WolverineWebApiFSharp.UnitSupport.TaskUnitSupport.post(command).ConfigureAwait(false);


// Outgoing, cascaded message
await messageContext.EnqueueCascadingAsync(unit).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;

// 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);

}
*)

[<WolverinePost("/unit-support/task")>]
[<EmptyResponse>]
let post (command: Command) = task {
do! Task.Delay 10
}

4 changes: 4 additions & 0 deletions WolverineWebApiFSharp/WolverineWebApiFSharp.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,18 @@
<TargetFramework>net8.0</TargetFramework>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<LangVersion>8.0</LangVersion>
<OutputType>Exe</OutputType>
</PropertyGroup>

<ItemGroup>
<Compile Include="Library.fs"/>
<Compile Include="TupleSupport.fs" />
<Compile Include="SideEffects.fs" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\src\Http\Wolverine.Http\Wolverine.Http.csproj" />
<ProjectReference Include="..\src\Persistence\Wolverine.Marten\Wolverine.Marten.csproj" />
</ItemGroup>

<ItemGroup>
Expand Down
3 changes: 3 additions & 0 deletions docs/guide/durability/efcore/operations.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,4 +141,7 @@ public static class TodoHandler

Wolverine also supports the usage of the `[Entity]` attribute to load entity data by its identity with EF Core. As you'd
expect, Wolverine can "find" the right EF Core `DbContext` type for the entity type through IoC service registrations.
The loaded EF core entity does not included related entities.

For more information on the usage of this attribute see
[Automatically loading entities to method parameters](/guide/handlers/persistence#automatically-loading-entities-to-method-parameters).
2 changes: 1 addition & 1 deletion src/Http/Wolverine.Http/HttpChain.EndpointBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ public RouteEndpoint BuildEndpoint(RouteWarmup warmup)
if (Endpoint != null) return Endpoint;

RequestDelegate? requestDelegate = null;
if (_parent.Rules.TypeLoadMode == TypeLoadMode.Static)
if (_parent.Rules.TypeLoadMode == TypeLoadMode.Static && !DynamicCodeBuilder.WithinCodegenCommand)
{
this.InitializeSynchronously(_parent.Rules, _parent, _parent.Container.Services);
var handler = (HttpHandler)_parent.Container.QuickBuild(_handlerType);
Expand Down
Loading