Skip to content

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:

bash
dotnet new webapi

Then add the WolverineFx.HTTP dependency with:

bash
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:

cs
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();

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);

snippet source | anchor

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:

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

snippet source | anchor

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

snippet source | anchor

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:

cs
[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.");
}

snippet source | anchor

Moving on to the actual Todo problem domain, let's assume we've got a class like this:

cs
public class Todo
{
    public int Id { get; set; }
    public string? Name { get; set; }
    public bool IsComplete { get; set; }
}

snippet source | anchor

cs
public class Todo
{
    public int Id { get; set; }
    public string? Name { get; set; }
    public bool IsComplete { get; set; }
}

snippet source | anchor

In a sample class called TodoEndpoints let's add an HTTP service endpoint for listing all the known Todo documents:

cs
[WolverineGet("/todoitems")]
public static Task<IReadOnlyList<Todo>> Get(IQuerySession session) 
    => session.Query<Todo>().ToListAsync();

snippet source | anchor

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:

cs
// 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);

snippet source | anchor

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:

cs
[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);
}

snippet source | anchor

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:

cs
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);
    }
}

snippet source | anchor

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:

  1. The entry assembly for your application
  2. Any assembly marked with the [assembly: WolverineModule] attribute
  3. Any assembly that is explicitly added in the UseWolverine() configuration as a handler assembly as shown in the following sample code:

cs
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();

snippet source | anchor

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]

Released under the MIT License.