ASP.Net Core Integration
TIP
WolverineFx.HTTP is an alternative to Minimal API or MVC Core for crafting HTTP service endpoints, but absolutely tries to be a good citizen within the greater ASP.Net Core ecosystem and heavily utilizes much of the ASP.Net Core technical foundation. It is also perfectly possible to use any mix of WolverineFx.HTTP, Minimal API, and MVC Core controllers within the same code base as you see fit.
The WolverineFx.HTTP
library extends Wolverine's runtime model to writing HTTP services with ASP.Net Core. As a quick sample, start a new project with:
dotnet new webapi
Then add the WolverineFx.HTTP
dependency with:
dotnet add package WolverineFx.HTTP
From there, let's jump into the application bootstrapping. Stealing the sample "Todo" project idea from the Minimal API documentation (and shifting to Marten for persistence just out of personal preference), this is the application bootstrapping:
using Marten;
using Oakton;
using Oakton.Resources;
using Wolverine;
using Wolverine.Http;
using Wolverine.Marten;
var builder = WebApplication.CreateBuilder(args);
// Adding Marten for persistence
builder.Services.AddMarten(opts =>
{
opts.Connection(builder.Configuration.GetConnectionString("Marten"));
opts.DatabaseSchemaName = "todo";
})
.IntegrateWithWolverine();
builder.Services.AddResourceSetupOnStartup();
// Wolverine usage is required for WolverineFx.Http
builder.Host.UseWolverine(opts =>
{
// This middleware will apply to the HTTP
// endpoints as well
opts.Policies.AutoApplyTransactions();
// Setting up the outbox on all locally handled
// background tasks
opts.Policies.UseDurableLocalQueues();
});
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddWolverineHttp();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
// Let's add in Wolverine HTTP endpoints to the routing tree
app.MapWolverineEndpoints();
return await app.RunOaktonCommands(args);
Do note that the only thing in that sample that pertains to WolverineFx.Http
itself is the call to IEndpointRouteBuilder.MapWolverineEndpoints()
.
Let's move on to "Hello, World" with a new Wolverine http endpoint from this class we'll add to the sample project:
public class HelloEndpoint
{
[WolverineGet("/")]
public string Get() => "Hello.";
}
public class HelloEndpoint
{
[WolverineGet("/")]
public string Get() => "Hello.";
}
At application startup, WolverineFx.Http will find the HelloEndpoint.Get()
method and treat it as a Wolverine http endpoint with the route pattern GET: /
specified in the [WolverineGet]
attribute.
As you'd expect, that route will write the return value back to the HTTP response and behave as specified by this Alba specification:
[Fact]
public async Task hello_world()
{
var result = await _host.Scenario(x =>
{
x.Get.Url("/");
x.Header("content-type").SingleValueShouldEqual("text/plain");
});
result.ReadAsText().ShouldBe("Hello.");
}
Moving on to the actual Todo
problem domain, let's assume we've got a class like this:
public class Todo
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
}
public class Todo
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
}
In a sample class called TodoEndpoints let's add an HTTP service endpoint for listing all the known Todo
documents:
[WolverineGet("/todoitems")]
public static Task<IReadOnlyList<Todo>> Get(IQuerySession session)
=> session.Query<Todo>().ToListAsync();
As you'd guess, this method will serialize all the known Todo
documents from the database into the HTTP response and return a 200 status code. In this particular case the code is a little bit noisier than the Minimal API equivalent, but that's okay, because you can happily use Minimal API and WolverineFx.Http together in the same project. WolverineFx.Http, however, will shine in more complicated endpoints.
Consider this endpoint just to return the data for a single Todo
document:
// Wolverine can infer the 200/404 status codes for you here
// so there's no code noise just to satisfy OpenAPI tooling
[WolverineGet("/todoitems/{id}")]
public static Task<Todo?> GetTodo(int id, IQuerySession session, CancellationToken cancellation)
=> session.LoadAsync<Todo>(id, cancellation);
At this point it's effectively de rigueur for any web service to support OpenAPI documentation directly in the service. Fortunately, WolverineFx.Http is able to glean most of the necessary metadata to support OpenAPI documentation with Swashbuckle from the method signature up above. The method up above will also cleanly set a status code of 404 if the requested Todo
document does not exist.
Now, the bread and butter for WolverineFx.Http is using it in conjunction with Wolverine itself. In this sample, let's create a new Todo
based on submitted data, but also publish a new event message with Wolverine to do some background processing after the HTTP call succeeds. And, oh, yeah, let's make sure this endpoint is actively using Wolverine's transactional outbox support for consistency:
[WolverinePost("/todoitems")]
public static async Task<IResult> Create(CreateTodo command, IDocumentSession session, IMessageBus bus)
{
var todo = new Todo { Name = command.Name };
session.Store(todo);
// Going to raise an event within out system to be processed later
await bus.PublishAsync(new TodoCreated(todo.Id));
return Results.Created($"/todoitems/{todo.Id}", todo);
}
The endpoint code above is automatically enrolled in the Marten transactional middleware by simple virtue of having a dependency on Marten's IDocumentSession
. By also taking in the IMessageBus
dependency, WolverineFx.Http is wrapping the transactional outbox behavior around the method so that the TodoCreated
message is only sent after the database transaction succeeds.
TIP
WolverineFx.Http allows you to place any number of endpoint methods on any public class that follows the naming conventions, but we strongly recommend isolating any kind of complicated endpoint method to its own endpoint class.
Lastly for this page, consider the need to update a Todo
from a PUT
call. Your HTTP endpoint may vary its handling and response by whether or not the document actually exists. Just to show off Wolverine's "composite handler" functionality and also how WolverineFx.Http supports middleware, consider this more complex endpoint:
public static class UpdateTodoEndpoint
{
public static async Task<(Todo? todo, IResult result)> LoadAsync(UpdateTodo command, IDocumentSession session)
{
var todo = await session.LoadAsync<Todo>(command.Id);
return todo != null
? (todo, new WolverineContinue())
: (todo, Results.NotFound());
}
[WolverinePut("/todoitems")]
public static void Put(UpdateTodo command, Todo todo, IDocumentSession session)
{
todo.Name = todo.Name;
todo.IsComplete = todo.IsComplete;
session.Store(todo);
}
}
How it Works
WolverineFx.Http takes advantage of the ASP.Net Core endpoint routing to add additional routes to the ASP.Net Core's routing tree. In Wolverine's case though, the underlying RequestDelegate
is compiled at runtime (or ahead of time for faster cold starts!) with the same code weaving strategy as Wolverine's message handling. Wolverine is able to utilize the same middleware model as the message handlers, with some extensions for recognizing the ASP.Net Core IResult model.
Discovery
TIP
The HTTP endpoint method discovery is very similar to the handler discovery and will scan the same assemblies as with the handlers.
WolverineFx.Http discovers endpoint methods automatically by doing type scanning within your application.
The assemblies scanned are:
- The entry assembly for your application
- Any assembly marked with the
[assembly: WolverineModule]
attribute - Any assembly that is explicitly added in the
UseWolverine()
configuration as a handler assembly as shown in the following sample code:
using var host = await Host.CreateDefaultBuilder()
.UseWolverine(opts =>
{
// This gives you the option to programmatically
// add other assemblies to the discovery of HTTP endpoints
// or message handlers
var assembly = Assembly.Load("my other assembly name that holds HTTP endpoints or handlers");
opts.Discovery.IncludeAssembly(assembly);
}).StartAsync();
INFO
Wolverine 1.6.0 added the looser discovery rules to just go look for any method on public, concrete types that is decorated with a Wolverine route attribute.
In the aforementioned assemblies, Wolverine will look for public, concrete, closed types whose names are suffixed by Endpoint
or Endpoints
and also any public, concrete class with methods that are decorated by any [WolverineVerb]
attribute. Within these types, Wolverine is looking for public methods that are decorated with one of Wolverine's HTTP method attributes:
[WolverineGet]
[WolverinePut]
[WolverinePost]
[WolverineDelete]
[WolverineOptions]
[WolverineHead]
The usage is suspiciously similar to the older [HttpGet]
type attributes in MVC Core.
OpenAPI Metadata
Wolverine is trying to replicate the necessary OpenAPI to fully support Swashbuckle usage with Wolverine endpoints. This is a work in process though. At this point it can at least expose:
- HTTP status codes
- HTTP methods
- Input and output types when an http method either takes in JSON bodies or writes JSON responses
- Authorization rules -- or really any ASP.Net Core attribute like
[Authorize]