Skip to content

HTTP Endpoints

First, a little terminology about Wolverine HTTP endpoints. Consider the following endpoint method:

cs
[WolverinePost("/question")]
public static ArithmeticResults PostJson(Question question)
{
    return new ArithmeticResults
    {
        Sum = question.One + question.Two,
        Product = question.One * question.Two
    };
}

snippet source | anchor

In the method signature above, Question is the "request" type (the payload sent from the client to the server) and Answer is the "resource" type (what is being returned to the client). If instead that method were asynchronous like this:

cs
[WolverinePost("/question2")]
public static Task<ArithmeticResults> PostJsonAsync(Question question)
{
    var results = new ArithmeticResults
    {
        Sum = question.One + question.Two,
        Product = question.One * question.Two
    };

    return Task.FromResult(results);
}

snippet source | anchor

The resource type is still Answer. Likewise, if an endpoint returns ValueTask<Answer>, the resource type is also Answer, and Wolverine will worry about the asynchronous (or return Task.CompletedTask;) mechanisms for you in the generated code.

INFO

It's actually possible to create custom conventions for how Wolverine resolves method parameters to the endpoint methods using the IParameterStrategy plugin interface explained later in this page.

First off, every endpoint method must be a public method on a public type to accommodate the runtime code generation. After that, you have quite a bit of flexibility.

In terms of what the legal parameters to your endpoint method, Wolverine uses these rules in order of precedence to determine how to source that parameter at runtime:

Type or DescriptionBehavior
Decorated with [FromServices]The argument is resolved as an IoC service
IMessageBusCreates a new Wolverine message bus object
HttpContext or its membersSee the section below on accessing the HttpContext
Parameter name matches a route parameterSee the routing page for more information
Decorated with [FromHeader]See working with headers for more information
string, int, Guid, etc.All other "simple" .NET types are assumed to be query string values
The first concrete, "not simple" parameterDeserializes the HTTP request body as JSON to this type
Every thing elseWolverine will try to source the type as an IoC service

You can force Wolverine to ignore a parameter as the request body type by decorating the parameter with the [NotBody attribute like this:

cs
[WolverinePost("/notbody")]
// The Recorder parameter will be sourced as an IoC service
// instead of being treated as the HTTP request body
public string PostNotBody([NotBody] Recorder recorder)
{
    recorder.Actions.Add("Called AttributesEndpoints.Post()");
    return "all good";
}

snippet source | anchor

In terms of the response type, you can use:

TypeBodyStatus CodeNotes
void / Task / ValueTaskEmpty200
string"text/plain"200Writes the result to the response
intEmptyValue of response
Type that implements IResultVariesVariesThe IResult.ExecuteAsync() method is executed
CreationResponse or subclassJSON201The response is serialized, and writes a location response header
AcceptResponse or subclassJSON202The response is serialized, and writes a location response header
Any other typeJSON200The response is serialized to JSON

In all cases up above, if the endpoint method is asynchronous using either Task<T> or ValueTask<T>, the T is the response type. In other words, a response of Task<string> has the same rules as a response of string and ValueTask<int> behaves the same as a response of int.

And now to complicate everything, but I promise this is potentially valuable, you can also use Tuples as the return type of an HTTP endpoint. In this case, the first item in the tuple is the official response type that is treated by the rules above. To make that concrete, consider this sample that we wrote in the introduction to Wolverine.Http:

cs
// Introducing this special type just for the http response
// gives us back the 201 status code
public record TodoCreationResponse(int Id) 
    : CreationResponse("/todoitems/" + Id);

// The "Endpoint" suffix is meaningful, but you could use
// any name if you don't mind adding extra attributes or a marker interface
// for discovery
public static class TodoCreationEndpoint
{
    [WolverinePost("/todoitems")]
    public static (TodoCreationResponse, TodoCreated) Post(CreateTodo command, IDocumentSession session)
    {
        var todo = new Todo { Name = command.Name };
        
        // Just telling Marten that there's a new entity to persist,
        // but I'm assuming that the transactional middleware in Wolverine is
        // handling the asynchronous persistence outside of this handler
        session.Store(todo);

        // By Wolverine.Http conventions, the first "return value" is always
        // assumed to be the Http response, and any subsequent values are
        // handled independently
        return (
            new TodoCreationResponse(todo.Id), 
            new TodoCreated(todo.Id)
        );
    }
}

snippet source | anchor

In the case above, TodoCreationResponse is the first item in the tuple, so Wolverine treats that as the response for the HTTP endpoint. The second TodoCreated value in the tuple is treated as a cascading message that will be published through Wolverine's messaging (or a local queue depending on the routing).

How Wolverine handles those extra "return values" is the same return value rules from the messaging handlers.

In the case of wanting to leverage Wolverine "return value" actions but you want your endpoint to return an empty response body, you can use the [Wolverine.Http.EmptyResponse] attribute to tell Wolverine not to use any return values as a the endpoint response and to return an empty response with a 204 status code. Here's an example from the tests:

cs
[AggregateHandler]
[WolverinePost("/orders/ship"), EmptyResponse]
// The OrderShipped return value is treated as an event being posted
// to a Marten even stream
// instead of as the HTTP response body because of the presence of 
// the [EmptyResponse] attribute
public static OrderShipped Ship(ShipOrder command, Order order)
{
    return new OrderShipped();
}

snippet source | anchor

JSON Handling

See JSON serialization for more information

Returning Strings

To create an endpoint that writes a string with content-type = "text/plain", just return a string as your resource type, so string, Task<string>, or ValueTask<string> from your endpoint method like so:

cs
public class HelloEndpoint
{
    [WolverineGet("/")]
    public string Get() => "Hello.";
}

snippet source | anchor

cs
public class HelloEndpoint
{
    [WolverineGet("/")]
    public string Get() => "Hello.";
}

snippet source | anchor

Using IResult

TIP

The IResult mechanics are applied to the return value of any type that can be cast to IResult

Wolverine will execute an ASP.Net Core IResult object returned from an HTTP endpoint method.

cs
[WolverineGet("/choose/color")]
public IResult Redirect(GoToColor request)
{
    switch (request.Color)
    {
        case "Red":
            return Results.Redirect("/red");

        case "Green":
            return Results.Redirect("/green");

        default:
            return Results.Content("Choose red or green!");
    }
}

snippet source | anchor

Using IoC Services

Wolverine HTTP endpoint methods happily support "method injection" of service types that are known in the IoC container. If there's any potential for confusion between the request type argument and what should be coming from the IoC container, you can decorate parameters with the [FromServices] attribute from ASP.Net Core to give Wolverine a hint. Otherwise, Wolverine is asking the underlying Lamar container if it knows how to resolve the service from the parameter argument.

Accessing HttpContext

Simply expose a parameter of any of these types to get either the current HttpContext for the current request or children members of HttpContext:

  1. HttpContext
  2. HttpRequest
  3. HttpResponse
  4. CancellationToken
  5. ClaimsPrincipal

You can also get at the trace identifier for the current HttpContext by a parameter like this:

cs
[WolverineGet("/http/identifier")]
public string UseTraceIdentifier(string traceIdentifier)
{
    return traceIdentifier;
}

snippet source | anchor

Customizing Parameter Handling

There's actually a way to customize how Wolverine handles parameters in HTTP endpoints to create your own conventions. To do so, you'd need to write an implementation of the IParameterStrategy interface from Wolverine.Http:

cs
/// <summary>
/// Apply custom handling to a Wolverine.Http endpoint/chain based on a parameter within the
/// implementing Wolverine http endpoint method
/// </summary>
/// <param name="variable">The Variable referring to the input of this parameter</param>
public interface IParameterStrategy
{
    bool TryMatch(HttpChain chain, IContainer container, ParameterInfo parameter, out Variable? variable);
}

snippet source | anchor

As an example, let's say that you want any parameter of type DateTimeOffset that's named "now" to receive the current system time. To do that, we can write this class:

cs
public class NowParameterStrategy : IParameterStrategy
{
    public bool TryMatch(HttpChain chain, IContainer container, ParameterInfo parameter, out Variable? variable)
    {
        if (parameter.Name == "now" && parameter.ParameterType == typeof(DateTimeOffset))
        {
            // This is tying into Wolverine's code generation model
            variable = new Variable(typeof(DateTimeOffset),
                $"{typeof(DateTimeOffset).FullNameInCode()}.{nameof(DateTimeOffset.UtcNow)}");
            return true;
        }

        variable = default;
        return false;
    }
}

snippet source | anchor

and register that strategy within our MapWolverineEndpoints() set up like so:

cs
// Customizing parameter handling
opts.AddParameterHandlingStrategy<NowParameterStrategy>();

snippet source | anchor

And lastly, here's the application within an HTTP endpoint for extra context:

cs
[WolverineGet("/now")]
public static string GetNow(DateTimeOffset now) // using the custom parameter strategy for "now"
{
    return now.ToString();
}

snippet source | anchor

Released under the MIT License.