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.

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:

cs
[WolverineGet("/name/{name}")]
public static string SimpleStringRouteArgument(string name)
{
    return $"Name is {name}";
}

snippet source | anchor

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 native .NET valid route parameter types that are supported at this time:

cs
public static readonly Dictionary<Type, string> TypeOutputs = new()
{
    { 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! },
    { typeof(DateOnly), typeof(DateOnly).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.

Strong Typed Identifiers 5.0

Wolverine.HTTP can support any type as a route argument that implements a TryParse() method. At this point, both the Vogen and StronglyTypedId tools do this for you, and value types generated by these tools are legal route argument variables for Wolverine.HTTP now.

As an example from the Wolverine tests, let's say you have an identity type like this sample that uses StronglyTypedId:

cs
[StronglyTypedId(Template.Guid)]
public readonly partial struct LetterId;

snippet source | anchor

You can use the LetterId type as a route argument as this example shows:

cs
[WolverineGet("/sti/aggregate/longhand/{id}")]
public static async ValueTask<StrongLetterAggregate> Handle2(LetterId id, IDocumentSession session) =>
    (await session.Events.FetchLatest<StrongLetterAggregate>(id.Value))!;

// This is an equivalent to the endpoint above 
[WolverineGet("/sti/aggregate/{id}")]
public static StrongLetterAggregate Handle(
    [ReadAggregate] StrongLetterAggregate aggregate) => aggregate;

snippet source | anchor

Route Prefixes 5.14

Wolverine.HTTP supports route prefix groups to automatically prepend a common prefix to your endpoint routes. This is useful for API versioning, organizing endpoints under a common base path, or grouping related endpoints.

Global Route Prefix

Use the RoutePrefix method on WolverineHttpOptions to set a prefix that applies to all Wolverine HTTP endpoints:

cs
app.MapWolverineEndpoints(opts =>
{
    // All endpoints will be prefixed with /api
    // e.g., /orders becomes /api/orders
    opts.RoutePrefix("api");
});

Namespace-Specific Prefix

You can target endpoints by their handler class namespace:

cs
app.MapWolverineEndpoints(opts =>
{
    // Only endpoints in this namespace (and child namespaces)
    // will get the prefix
    opts.RoutePrefix("api/orders",
        forEndpointsInNamespace: "MyApp.Features.Orders");

    opts.RoutePrefix("api/customers",
        forEndpointsInNamespace: "MyApp.Features.Customers");
});

When multiple namespace prefixes could match (e.g., MyApp and MyApp.Features), the most specific (longest) matching namespace wins.

[RoutePrefix] Attribute

You can also apply a prefix to all endpoints in a specific class using the [RoutePrefix] attribute:

cs
[RoutePrefix("api/v2")]
public class OrderEndpoints
{
    // Route becomes /api/v2/orders
    [WolverineGet("/orders")]
    public string GetOrders() => "orders";

    // Route becomes /api/v2/orders/{id}
    [WolverineGet("/orders/{id}")]
    public string GetOrder(int id) => $"order {id}";
}

Precedence

When multiple prefix sources are configured, the following precedence applies (most specific wins):

  1. [RoutePrefix] attribute on the endpoint class
  2. Namespace-specific prefix (most specific namespace match)
  3. Global prefix

Only one prefix is applied per endpoint -- they do not stack.

Route Prefixes

You can apply a common URL prefix to all endpoints in a handler class using the [RoutePrefix] attribute:

csharp
[RoutePrefix("/api/v1")]
public static class V1Endpoints
{
    // Resolves to /api/v1/orders
    [WolverineGet("/orders")]
    public static string GetOrders() => "V1 Orders";

    // Resolves to /api/v1/orders/{id}
    [WolverineGet("/orders/{id}")]
    public static string GetOrder(int id) => $"V1 Order {id}";
}

You can also configure route prefixes globally or by namespace in your application setup:

csharp
app.MapWolverineEndpoints(opts =>
{
    // Apply a global prefix to all Wolverine endpoints
    opts.RoutePrefix("api");

    // Apply a prefix to all endpoints in a specific namespace
    opts.RoutePrefix("api/v2", forEndpointsInNamespace: "MyApp.Endpoints.V2");
});

When multiple prefix sources apply, the precedence is: [RoutePrefix] attribute > namespace prefix > global prefix. Only one prefix is applied per endpoint — they do not stack.

API Versioning

For API versioning scenarios, Wolverine recommends using route prefixes to version your endpoints. This approach is simple, explicit, and requires no additional packages:

csharp
[RoutePrefix("/api/v1")]
public static class OrdersV1
{
    [WolverineGet("/orders")]
    public static OrderListResponse GetOrders() => /* ... */;
}

[RoutePrefix("/api/v2")]
public static class OrdersV2
{
    // V2 returns a different shape
    [WolverineGet("/orders")]
    public static OrderListResponseV2 GetOrders() => /* ... */;
}

You can also version by namespace:

csharp
app.MapWolverineEndpoints(opts =>
{
    opts.RoutePrefix("api/v1", forEndpointsInNamespace: "MyApp.Endpoints.V1");
    opts.RoutePrefix("api/v2", forEndpointsInNamespace: "MyApp.Endpoints.V2");
});

This lets you organize versioned endpoints into separate namespaces and apply the version prefix automatically without any attributes on individual classes.

Route Name

You can add a name to the ASP.Net route with this property that is on all of the route definition attributes:

cs
[WolverinePost("/named/route", RouteName = "NamedRoute")]
public string Post()
{
    return "Hello";
}

snippet source | anchor

Released under the MIT License.