HTTP Endpoints
First, a little terminology about Wolverine HTTP endpoints. Consider the following endpoint method:
[WolverinePost("/question")]
public static ArithmeticResults PostJson(Question question)
{
return new ArithmeticResults
{
Sum = question.One + question.Two,
Product = question.One * question.Two
};
}
In the method signature above, Question
is the "request" type (the payload sent from the client to the server) and ArithmeticResults
is the "resource" type (what is being returned to the client). If instead that method were asynchronous like this:
[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);
}
The resource type is still ArithmeticResults
. Likewise, if an endpoint returns ValueTask<ArithmeticResults>
, the resource type is also ArithmeticResults
, and Wolverine will worry about the asynchronous (or return Task.CompletedTask;
) mechanisms for you in the generated code.
Legal Endpoint Signatures
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 Description | Behavior |
---|---|
Decorated with [FromServices] | The argument is resolved as an IoC service |
IMessageBus | Creates a new Wolverine message bus object |
HttpContext or its members | See the section below on accessing the HttpContext |
Parameter name matches a route parameter | See 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" parameter | Deserializes the HTTP request body as JSON to this type |
Every thing else | Wolverine 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:
[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";
}
In terms of the response type, you can use:
Type | Body | Status Code | Notes |
---|---|---|---|
void / Task / ValueTask | Empty | 200 | |
string | "text/plain" | 200 | Writes the result to the response |
int | Empty | Value of response | |
Type that implements IResult | Varies | Varies | The IResult.ExecuteAsync() method is executed |
CreationResponse or subclass | JSON | 201 | The response is serialized, and writes a location response header |
AcceptResponse or subclass | JSON | 202 | The response is serialized, and writes a location response header |
Any other type | JSON | 200 | The 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:
// 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)
);
}
}
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:
[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();
}
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:
public class HelloEndpoint
{
[WolverineGet("/")]
public string Get() => "Hello.";
}
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.
[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!");
}
}
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
:
HttpContext
HttpRequest
HttpResponse
CancellationToken
ClaimsPrincipal
You can also get at the trace identifier for the current HttpContext
by a parameter like this:
[WolverineGet("/http/identifier")]
public string UseTraceIdentifier(string traceIdentifier)
{
return traceIdentifier;
}
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:
/// <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, IServiceContainer container, ParameterInfo parameter, out Variable? variable);
}
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:
public class NowParameterStrategy : IParameterStrategy
{
public bool TryMatch(HttpChain chain, IServiceContainer 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;
}
}
and register that strategy within our MapWolverineEndpoints()
set up like so:
// Customizing parameter handling
opts.AddParameterHandlingStrategy<NowParameterStrategy>();
And lastly, here's the application within an HTTP endpoint for extra context:
[WolverineGet("/now")]
public static string GetNow(DateTimeOffset now) // using the custom parameter strategy for "now"
{
return now.ToString();
}