Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs: fix info blocks for asp net core integration #615

Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 57 additions & 19 deletions docs/guide/http/integration.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# ASP.Net Core Integration

::: TIP
::: 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.
Expand All @@ -19,15 +19,17 @@ Then add the `WolverineFx.HTTP` dependency with:
dotnet add package WolverineFx.HTTP
```

::: TIP
::: tip
The [sample project for this page is on GitHub](https://github.com/JasperFx/wolverine/tree/main/src/Samples/TodoWebService/TodoWebService).
:::

From there, let's jump into the application bootstrapping. Stealing the [sample "Todo" project idea from the Minimal API documentation](https://learn.microsoft.com/en-us/aspnet/core/tutorials/min-web-api?view=aspnetcore-7.0&tabs=visual-studio) (and
shifting to [Marten](https://martendb.io) for persistence just out of personal preference), this is the application bootstrapping:

<!-- snippet: sample_bootstrapping_wolverine_http -->

<a id='snippet-sample_bootstrapping_wolverine_http'></a>

```cs
using Marten;
using Oakton;
Expand All @@ -54,7 +56,7 @@ 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();
Expand All @@ -78,23 +80,29 @@ app.MapWolverineEndpoints();

return await app.RunOaktonCommands(args);
```

<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Samples/TodoWebService/TodoWebService/Program.cs#L1-L52' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_bootstrapping_wolverine_http' title='Start of snippet'>anchor</a></sup>

<!-- endSnippet -->

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:

<!-- snippet: sample_hello_world_with_wolverine_http -->

<a id='snippet-sample_hello_world_with_wolverine_http'></a>

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

<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Samples/TodoWebService/TodoWebService/HelloEndpoint.cs#L5-L13' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_hello_world_with_wolverine_http' title='Start of snippet'>anchor</a></sup>

<!-- endSnippet -->

At application startup, WolverineFx.Http will find the `HelloEndpoint.Get()` method and treat it as a Wolverine http endpoint with
Expand All @@ -104,7 +112,9 @@ As you'd expect, that route will write the return value back to the HTTP respons
by this [Alba](https://jasperfx.github.io/alba) specification:

<!-- snippet: sample_testing_hello_world_for_http -->

<a id='snippet-sample_testing_hello_world_for_http'></a>

```cs
[Fact]
public async Task hello_world()
Expand All @@ -114,17 +124,21 @@ public async Task hello_world()
x.Get.Url("/");
x.Header("content-type").SingleValueShouldEqual("text/plain");
});

result.ReadAsText().ShouldBe("Hello.");
}
```

<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Samples/TodoWebService/TodoWebServiceTests/end_to_end.cs#L34-L48' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_testing_hello_world_for_http' title='Start of snippet'>anchor</a></sup>

<!-- endSnippet -->

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

<!-- snippet: sample_Todo -->

<a id='snippet-sample_todo'></a>

```cs
public class Todo
{
Expand All @@ -133,8 +147,10 @@ public class Todo
public bool IsComplete { get; set; }
}
```

<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Samples/MultiTenantedTodoService/MultiTenantedTodoService/Endpoints.cs#L11-L20' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_todo' title='Start of snippet'>anchor</a></sup>
<a id='snippet-sample_todo-1'></a>

```cs
public class Todo
{
Expand All @@ -143,20 +159,26 @@ public class Todo
public bool IsComplete { get; set; }
}
```

<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Samples/TodoWebService/TodoWebService/Endpoints.cs#L9-L18' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_todo-1' title='Start of snippet'>anchor</a></sup>

<!-- endSnippet -->

In a sample class called [TodoEndpoints](https://github.com/JasperFx/wolverine/blob/main/src/Samples/TodoWebService/TodoWebService/Endpoints.cs)
let's add an HTTP service endpoint for listing all the known `Todo` documents:

<!-- snippet: sample_get_to_json -->

<a id='snippet-sample_get_to_json'></a>

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

<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Samples/TodoWebService/TodoWebService/Endpoints.cs#L30-L36' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_get_to_json' title='Start of snippet'>anchor</a></sup>

<!-- endSnippet -->

As you'd guess, this method will serialize all the known `Todo` documents from the database into the HTTP response
Expand All @@ -167,15 +189,19 @@ will shine in more complicated endpoints.
Consider this endpoint just to return the data for a single `Todo` document:

<!-- snippet: sample_GetTodo -->

<a id='snippet-sample_gettodo'></a>

```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)
public static Task<Todo?> GetTodo(int id, IQuerySession session, CancellationToken cancellation)
=> session.LoadAsync<Todo>(id, cancellation);
```

<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Samples/TodoWebService/TodoWebService/Endpoints.cs#L42-L50' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_gettodo' title='Start of snippet'>anchor</a></sup>

<!-- endSnippet -->

At this point it's effectively de rigueur for any web service to support [OpenAPI](https://www.openapis.org/) documentation directly
Expand All @@ -188,7 +214,9 @@ Now, the bread and butter for WolverineFx.Http is using it in conjunction with W
call succeeds. And, oh, yeah, let's make sure this endpoint is actively using Wolverine's [transactional outbox](/guide/durability/) support for consistency:

<!-- snippet: sample_posting_new_todo_with_middleware -->

<a id='snippet-sample_posting_new_todo_with_middleware'></a>

```cs
[WolverinePost("/todoitems")]
public static async Task<IResult> Create(CreateTodo command, IDocumentSession session, IMessageBus bus)
Expand All @@ -198,11 +226,13 @@ public static async Task<IResult> Create(CreateTodo command, IDocumentSession se

// 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);
}
```

<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Samples/TodoWebService/TodoWebService/Endpoints.cs#L52-L66' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_posting_new_todo_with_middleware' title='Start of snippet'>anchor</a></sup>

<!-- endSnippet -->

The endpoint code above is automatically enrolled in the Marten transactional middleware by simple virtue of having a
Expand All @@ -220,15 +250,17 @@ handling and response by whether or not the document actually exists. Just to sh
and also how WolverineFx.Http supports middleware, consider this more complex endpoint:

<!-- snippet: sample_UpdateTodoEndpoint -->

<a id='snippet-sample_updatetodoendpoint'></a>

```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())
return todo != null
? (todo, new WolverineContinue())
: (todo, Results.NotFound());
}

Expand All @@ -241,7 +273,9 @@ public static class UpdateTodoEndpoint
}
}
```

<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Samples/TodoWebService/TodoWebService/Endpoints.cs#L84-L105' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_updatetodoendpoint' title='Start of snippet'>anchor</a></sup>

<!-- endSnippet -->

## How it Works
Expand All @@ -267,7 +301,9 @@ The assemblies scanned are:
3. Any assembly that is explicitly added in the `UseWolverine()` configuration as a handler assembly as shown in the following sample code:

<!-- snippet: sample_programmatically_scan_assemblies -->

<a id='snippet-sample_programmatically_scan_assemblies'></a>

```cs
using var host = await Host.CreateDefaultBuilder()
.UseWolverine(opts =>
Expand All @@ -279,7 +315,9 @@ using var host = await Host.CreateDefaultBuilder()
opts.Discovery.IncludeAssembly(assembly);
}).StartAsync();
```

<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Http/Wolverine.Http.Tests/DocumentationSamples.cs#L11-L23' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_programmatically_scan_assemblies' title='Start of snippet'>anchor</a></sup>

<!-- endSnippet -->

::: info
Expand All @@ -291,12 +329,12 @@ In the aforementioned assemblies, Wolverine will look for **public, concrete, cl
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]`
- `[WolverineGet]`
- `[WolverinePut]`
- `[WolverinePost]`
- `[WolverineDelete]`
- `[WolverineOptions]`
- `[WolverineHead]`

The usage is suspiciously similar to the older `[HttpGet]` type attributes in MVC Core.

Expand All @@ -305,7 +343,7 @@ The usage is suspiciously similar to the older `[HttpGet]` type attributes in MV
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]`
- 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]`