Skip to content
On this page

HTTP Endpoints

WARNING

In all cases, the resource returned from a Wolverine HTTP endpoint method is not automatically published as a Wolverine cascaded message. At this moment you will have to directly use IMessageBus in your method signature to publish messages.

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

cs
[WolverinePost("/question")]
public static Results PostJson(Question question)
{
    return new Results
    {
        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<Results> PostJsonAsync(Question question)
{ 
    var results = new Results
    {
        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.

Routing

WARNING

The route argument to method name matching is case sensitive.

Wolverine HTTP endpoints need to be decorated with one of the [WolverineVerb("route")] attributes that expresses the routing argument path in standard ASP.Net Core syntax (i.e., the same as when using MVC Core or Minimal API).

If a parameter argument to the HTTP handler method exactly matches a route argument, Wolverine will treat that as a route argument and pass the route argument value at runtime from ASP.Net Core to your handler method. To make that concrete, consider this simple case from the test suite:

sample: sample_using_string_route_parameter

In the sample above, the name argument will be the value of the route argument at runtime. Here's another example, but this time using a numeric value:

cs
[WolverineGet("/age/{age}")]
public static string IntRouteArgument(int age)
{
    return $"Age is {age}";
}

snippet source | anchor

The following code snippet from WolverineFx.Http itself shows the valid route parameter types that are supported at this time:

cs
public static readonly Dictionary<Type, string> TypeOutputs = new Dictionary<Type, string>
{
    {typeof(bool), "bool"},
    {typeof(byte), "byte"},
    {typeof(sbyte), "sbyte"},
    {typeof(char), "char"},
    {typeof(decimal), "decimal"},
    {typeof(float), "float"},
    {typeof(short), "short"},
    {typeof(int), "int"},
    {typeof(double), "double"},
    {typeof(long), "long"},
    {typeof(ushort), "ushort"},
    {typeof(uint), "uint"},
    {typeof(ulong), "ulong"},
    {typeof(Guid), typeof(Guid).FullName},
    {typeof(DateTime), typeof(DateTime).FullName},
    {typeof(DateTimeOffset), typeof(DateTimeOffset).FullName}
};

snippet source | anchor

WARNING

Wolverine will return a 404 status code if a route parameter cannot be correctly parsed. So passing "ABC" into what is expected to be an integer will result in a 404 response.

Working with QueryString

TIP

Wolverine can handle both nullable types and the primitive values here. So int and int? are both valid. In all cases, if the query string does not exist -- or cannot be parsed -- the value passed to your method will be the default for whatever that type is.

Wolverine supports passing query string values to your HTTP method arguments for the exact same set of value types supported for route arguments. In this case, Wolverine treats any value type parameter where the parameter name does not match a route argument name as coming from the HTTP query string.

When Wolverine does the runtime matching, it's using the exact parameter name as the query string key. Here's a quick sample:

cs
[WolverineGet("/querystring/string")]
public static string UsingQueryString(string name) // name is from the query string
{
    return name.IsEmpty() ? "Name is missing" : $"Name is {name}";
}

snippet source | anchor

And the corresponding tests:

cs
[Fact]
public async Task use_string_querystring_hit()
{
    var body = await Scenario(x =>
    {
        x.Get.Url("/querystring/string?name=Magic");
        x.Header("content-type").SingleValueShouldEqual("text/plain");
    });
    
    body.ReadAsText().ShouldBe("Name is Magic");
}

[Fact]
public async Task use_string_querystring_miss()
{
    var body = await Scenario(x =>
    {
        x.Get.Url("/querystring/string");
        x.Header("content-type").SingleValueShouldEqual("text/plain");
    });
    
    body.ReadAsText().ShouldBe("Name is missing");
}

snippet source | anchor

Working with JSON

WARNING

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

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("accepts", "application/json");
    });

    var result = await response.ReadAsJsonAsync<Results>();
    
    result.Product.ShouldBe(12);
    result.Sum.ShouldBe(7);
}

snippet source | anchor

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

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

Released under the MIT License.