Skip to content

The search box in the website knows all the secrets—try it!

For any queries, join our Discord Channel to reach us faster.

JasperFx Logo

JasperFx provides formal support for Wolverine and other JasperFx libraries. Please check our Support Plans for more details.

JSON Serialization ​

WARNING

At this point WolverineFx.Http only supports System.Text.Json as the default for the HTTP endpoints, with the JSON settings coming from the application's Minimal API configuration.

TIP

You can tell Wolverine to ignore all return values as the request body by decorating either the endpoint method or the whole endpoint class with [EmptyResponse]

As explained up above, the "request" type to a Wolverine endpoint is the first argument that is:

  1. Concrete
  2. Not one of the value types that Wolverine considers for route or query string values
  3. Not marked with [FromServices] from ASP.Net Core

If a parameter like this exists, that will be the request type, and will come at runtime from deserializing the HTTP request body as JSON.

Likewise, any resource type besides strings will be written to the HTTP response body as serialized JSON.

In this sample endpoint, both the request and resource types are dealt with by JSON serialization. Here's the test from the actual Wolverine codebase:

cs
[Fact]
public async Task post_json_happy_path()
{
    // This test is using Alba to run an end to end HTTP request
    // and interrogate the results
    var response = await Scenario(x =>
    {
        x.Post.Json(new Question { One = 3, Two = 4 }).ToUrl("/question");
        x.WithRequestHeader("accept", "application/json");
    });

    var result = await response.ReadAsJsonAsync<ArithmeticResults>();

    result.Product.ShouldBe(12);
    result.Sum.ShouldBe(7);
}

snippet source | anchor

Optional Request Bodies 6.49 ​

A request body is optional when the parameter that receives it is nullable, exactly as in ASP.NET Core minimal APIs. That is true of a top-level body parameter and of a nullable [FromBody] member of an [AsParameters] type alike:

cs
public static class OptionalBodyEndpoint
{
    // A request with no body binds null and the endpoint runs
    [WolverinePost("/api/optional-body")]
    public static string Post(Payload? body)
        => body is null ? "no-body" : $"body:{body.Name}";
}

OpenAPI reports the request body as not required. A non-nullable body parameter stays required, and a request without a body gets a 400.

Wolverine decides whether a request has a body the way minimal APIs do, by asking the server (IHttpRequestBodyDetectionFeature.CanHaveBody) rather than by looking for a Content-Length header, so a body sent with Transfer-Encoding: chunked, from an HttpClient StreamContent of unknown length, or over HTTP/2 without a content-length header, is bound rather than mistaken for a missing one. The flip side is the same as in minimal APIs: when the server says the request can have a body and there is nothing in it, for instance a chunked request with zero bytes, that is an empty body, not a missing one, and it is answered with a 400 rather than bound as null. Content-Length: 0 is always a missing body.

That last point matters in tests. An Alba scenario always reports that the request can have a body, so a scenario that posts nothing at all to an optional-body endpoint gets a 400, just as it would against a minimal API. Send the no-body case with an explicit Content-Length: 0, which is what HttpClient sends for a bodiless POST:

cs
await host.Scenario(x =>
{
    x.Post.Url("/api/optional-body");
    x.ConfigureHttpContext(c => c.Request.ContentLength = 0);
    x.StatusCodeShouldBe(200);
});

Configuring System.Text.Json ​

Wolverine depends on the value of the IOptions<Microsoft.AspNetCore.Http.Json.JsonOptions> value registered in your application container for System.Text.Json configuration.

But, because there are multiple JsonOption types in the AspNetCore world and it's way too easy to pick the wrong one and get confused and angry about why your configuration isn't impacting Wolverine, there's this extension method helper that will do the right thing behind the scenes:

cs
var builder = WebApplication.CreateBuilder();

builder.Host.UseWolverine();

builder.Services.ConfigureSystemTextJsonForWolverineOrMinimalApi(o =>
{
    // Do whatever you want here to customize the JSON
    // serialization
    o.SerializerOptions.WriteIndented = true;
});

var app = builder.Build();

app.MapWolverineEndpoints();

return await app.RunJasperFxCommands(args);

snippet source | anchor

Using Newtonsoft.Json ​

TIP

Newtonsoft.Json is still much more battle hardened than System.Text.Json, and you may need to drop back to Newtonsoft.Json for various scenarios. This feature was added specifically at the request of F# developers.

TIP

As of Wolverine 6.0, Newtonsoft.Json support for Wolverine.Http lives in a separate WolverineFx.Http.Newtonsoft NuGet package. The core WolverineFx.Http package no longer depends on Newtonsoft.Json; install WolverineFx.Http.Newtonsoft alongside it to opt in. This mirrors the symmetric core extraction in WolverineFx.Newtonsoft (see the migration guide).

To install:

bash
dotnet add package WolverineFx.Http.Newtonsoft

The WolverineFx.Http.Newtonsoft package provides the same UseNewtonsoftJsonForSerialization API as Wolverine 5.x, now exposed as an extension method on WolverineHttpOptions. Add the using Wolverine.Http.Newtonsoft; directive to bring the extensions into scope, and register the package's services on the IServiceCollection alongside AddWolverineHttp():

cs
var builder = WebApplication.CreateBuilder([]);
builder.Services.AddScoped<IUserService, UserService>();

builder.Services.AddMarten(Servers.PostgresConnectionString)
    .IntegrateWithWolverine();

builder.Host.UseWolverine(opts =>
{
    opts.Discovery.IncludeAssembly(GetType().Assembly);
});

builder.Services.AddWolverineHttp();
// As of Wolverine 6.0, Newtonsoft.Json HTTP support lives in the
// separate WolverineFx.Http.Newtonsoft package — register its
// services here, then opt in via UseNewtonsoftJsonForSerialization()
// below.
builder.Services.AddWolverineHttpNewtonsoft();

await using var host = await AlbaHost.For(builder, app =>
{
    app.MapWolverineEndpoints(opts =>
    {
        // Opt into using Newtonsoft.Json for JSON serialization just with Wolverine.HTTP routes
        // Configuring the JSON serialization is optional. This extension method comes from
        // the WolverineFx.Http.Newtonsoft package (using Wolverine.Http.Newtonsoft;).
        opts.UseNewtonsoftJsonForSerialization(settings => settings.TypeNameHandling = TypeNameHandling.All);
    });
});

snippet source | anchor

Released under the MIT License.