Futurum.WebApiEndpoint.Micro
2.0.1
See the version list below for details.
dotnet add package Futurum.WebApiEndpoint.Micro --version 2.0.1
NuGet\Install-Package Futurum.WebApiEndpoint.Micro -Version 2.0.1
<PackageReference Include="Futurum.WebApiEndpoint.Micro" Version="2.0.1" />
paket add Futurum.WebApiEndpoint.Micro --version 2.0.1
#r "nuget: Futurum.WebApiEndpoint.Micro, 2.0.1"
// Install Futurum.WebApiEndpoint.Micro as a Cake Addin #addin nuget:?package=Futurum.WebApiEndpoint.Micro&version=2.0.1 // Install Futurum.WebApiEndpoint.Micro as a Cake Tool #tool nuget:?package=Futurum.WebApiEndpoint.Micro&version=2.0.1
Futurum.WebApiEndpoint.Micro
A dotnet library that allows you to build WebApiEndpoints using a vertical slice architecture approach in a structured way. It's built on top of dotnet 8 and minimal apis.
[WebApiEndpoint("greeting")]
public class GreetingWebApiEndpoint
{
protected override void Build(IEndpointRouteBuilder builder)
{
builder.MapGet("/hello", HelloHandler);
builder.MapGet("/goodbye", GoodbyeHandler);
}
private static Ok<string> HelloHandler(HttpContext context, string name) =>
$"Hello {name}".ToOk();
private static Ok<string> GoodbyeHandler(HttpContext context, string name) =>
$"Goodbye {name}".ToOk();
}
- Vertical Slice Architecture, gives you the ability to add new features without changing existing code
- Structured way of building WebApiEndpoints using minimal apis
- Easy setup
- Developer friendly, with a simple API and with a full suite of samples and tests
- Full support and built on top of minimal apis
- Full support for OpenApi
- Full support for TypedResults
- Supports uploading file(s) with additional JSON payload
- Api Versioning baked-in
- Built in sandbox runner with full TypedResults support, catching unhandled exceptions and returning a ProblemDetails response
- Autodiscovery of WebApiEndpoint(s), based on Source Generators
- Roslyn Analysers to help build your WebApiEndpoint(s), using best practices
- Built on dotnet 8
- Built in use of ProblemDetails support
- Tested solution
- Comprehensive samples
- Convention Customisation
What is a WebApiEndpoint?
- It's a vertical slice / feature of your application
- The vertical slice is a self-contained unit of functionality
- Collection of WebApi's that share a route prefix and version. They can also share things like EndpointFilters, RateLimiting, etc.
Easy setup
- Add the NuGet package ( futurum.webapiendpoint.micro ) to your project
- Update program.cs as per here
- Create a new class that implements IWebApiEndpoint
- Add the WebApiEndpoint attribute to the class, if you want to specify a specific route prefix and tag
- Add the WebApiEndpointVersion attribute to the class, if you want to specify a specific ApiVersion
- Implement the Register method and add minimal api(s) as per usual
- Optionally implement the Configure method to configuration the WebApiEndpoint
program.cs
using Futurum.WebApiEndpoint.Micro;
using Futurum.WebApiEndpoint.Micro.Sample;
var builder = WebApplication.CreateBuilder(args);
builder.Services
.AddWebApiEndpoints(new WebApiEndpointConfiguration(WebApiEndpointVersions.V1_0))
.AddWebApiEndpointsForFuturumWebApiEndpointMicroSample();
var app = builder.Build();
app.UseWebApiEndpoints();
if (app.Environment.IsDevelopment())
{
app.UseWebApiEndpointsOpenApi();
}
app.Run();
AddWebApiEndpoints
Allows you to configure:
- GlobalRoutePrefix (optional)
- This is used if you want to specify a global route prefix for all WebApiEndpoint
- e.g. "/api"
- DefaultApiVersion (mandatory)
- This is used if a specific ApiVersion is not provided for a specific WebApiEndpoint
- DefaultOpenApiInfo (optional)
- This is used if a specific OpenApiInfo is not provided for a specific ApiVersion
- OpenApiDocumentVersions (optional)
- Allowing you to have different OpenApiInfo per ApiVersion
- VersionPrefix (optional)
- VersionFormat (optional)
- uses 'Asp.Versioning.ApiVersionFormatProvider'
builder.Services.AddWebApiEndpoints(new WebApiEndpointConfiguration(WebApiEndpointVersions.V1_0)
{
DefaultOpenApiInfo = new OpenApiInfo
{
Title = "Futurum.WebApiEndpoint.Micro.Sample",
},
OpenApiDocumentVersions =
{
{
WebApiEndpointVersions.V1_0,
new OpenApiInfo
{
Title = "Futurum.WebApiEndpoint.Micro.Sample v1"
}
}
}
});
AddWebApiEndpointsFor... (per project containing WebApiEndpoints)
This will be automatically created by the source generator.
e.g.
builder.Services.AddWebApiEndpointsForFuturumWebApiEndpointMicroSample();
UseWebApiEndpoints
Adds the WebApiEndpoints to the pipeline
app.UseWebApiEndpoints();
UseWebApiEndpointsOpenApi
Register the OpenApi UI (Swagger and SwaggerUI) middleware
app.UseWebApiEndpointsOpenApi();
WebApiEndpoint
Build
You can map your minimal apis for this WebApiEndpoint in the Build method.
The IEndpointRouteBuilder parameter is already:
- configured with the API versioning
- configured with the route prefix
- been through the optional Configure method in the same class
protected override void Build(IEndpointRouteBuilder builder)
{
}
Full example
Weather
[WebApiEndpoint("weather")]
public class WeatherWebApiEndpoint
{
private static readonly string[] Summaries =
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
protected override void Build(IEndpointRouteBuilder builder)
{
builder.MapGet("/", GetHandler);
}
private static Ok<IEnumerable<WeatherForecastDto>> GetHandler(HttpContext httpContext, CancellationToken cancellationToken) =>
Enumerable.Range(1, 5)
.Select(index => new WeatherForecastDto(DateOnly.FromDateTime(DateTime.Now.AddDays(index)), Random.Shared.Next(-20, 55), Summaries[Random.Shared.Next(Summaries.Length)]))
.ToOk();
}
File download
[WebApiEndpoint("bytes", "feature")]
public class BytesWebApiEndpoint
{
protected override void Build(IEndpointRouteBuilder builder)
{
builder.MapGet("download", DownloadHandler);
}
private static Results<NotFound, FileContentHttpResult, BadRequest<ProblemDetails>> DownloadHandler(HttpContext context)
{
return Run(Execute, context, "Failed to read file");
Results<NotFound, FileContentHttpResult> Execute()
{
var path = "./Data/hello-world.txt";
if (!File.Exists(path))
{
return TypedResults.NotFound();
}
var bytes = File.ReadAllBytes(path);
return TypedResults.Bytes(bytes, MediaTypeNames.Application.Octet, "hello-world.txt");
}
}
}
Configure
You can optionally configure the WebApiEndpoint in the Configure method
protected override RouteGroupBuilder Configure(RouteGroupBuilder groupBuilder, WebApiEndpointVersion webApiEndpointVersion)
{
}
This allows you to set properties on the RouteGroupBuilder. This will effect all minimal apis in the Register method.
You can also configure it differently per ApiVersion.
This ia a good place to add a EndpointFilter
groupBuilder.AddEndpointFilter<CustomEndpointFilter>();
This ia a good place to add a RateLimiting
groupBuilder.RequireRateLimiting(RateLimiting.SlidingWindow.Policy);
This ia a good place to add a OutputCache
groupBuilder.CacheOutput(OutputCaching.ExpiryIn10Seconds.Policy);
This ia a good place to add Security
groupBuilder.RequireAuthorization(Authorization.Permission.Admin);
Sandbox runner
Run and RunAsync - If your code returns an IResult
Comprehensive set of extension methods, to run your code in a sandbox
- If your code does not throw an unhandled exception, then the existing return remains the same.
- If your code does throw an unhandled exception, then a BadRequest<ProblemDetails> will be returned, with the appropriate details set on the ProblemDetails.
The returned Results<...> type is always augmented to additionally include BadRequest<ProblemDetails>
TIResult1 -> Results<TIResult1, BadRequest<ProblemDetails>>
Results<TIResult1, TIResult2> -> Results<TIResult1, TIResult2, BadRequest<ProblemDetails>>
Results<TIResult1, TIResult2, TIResult3> -> Results<TIResult1, TIResult2, TIResult3, BadRequest<ProblemDetails>>
Results<TIResult1, TIResult2, TIResult3, TIResult4> -> Results<TIResult1, TIResult2, TIResult3, TIResult4, BadRequest<ProblemDetails>>
Results<TIResult1, TIResult2, TIResult3, TIResult4, TIResult5> -> Results<TIResult1, TIResult2, TIResult3, TIResult4, TIResult5, BadRequest<ProblemDetails>>
Results has a maximum of 6 types. So 5 are allowed leaving one space left for the BadRequest<ProblemDetails>.
Example use
private static Results<NotFound, FileStreamHttpResult, BadRequest<ProblemDetails>> DownloadHandler(HttpContext context)
{
return Run(Execute, context, "Failed to read file");
Results<NotFound, FileStreamHttpResult> Execute()
{
var path = "./Data/hello-world.txt";
if (!File.Exists(path))
{
return TypedResults.NotFound();
}
var fileStream = File.OpenRead(path);
return TypedResults.File(fileStream, MediaTypeNames.Application.Octet, "hello-world.txt");
}
}
In this example the Execute method is being wrapped by the runner. It returns:
- a NotFound if the file does not exist
- a FileStreamHttpResult if the file exists
Results<NotFound, FileStreamHttpResult>
The Run / RunAsync extension method will change this to add BadRequest<ProblemDetails>.
Results<NotFound, FileStreamHttpResult, BadRequest<ProblemDetails>>
Note: It is recommended to add the following to your GlobalUsings.cs file.
global using static Futurum.WebApiEndpoint.Micro.WebApiEndpointRunner;
This means you can use the helper functions without having to specify the namespace. As in the examples.
RunToOk and RunToOkAsync - If your code returns an T (not a IResult)
Comprehensive set of extension methods, to run your code in a sandbox
- If your code does not throw an unhandled exception, then the existing return remains the same, but will be wrapped in an Ok.
- If your code does throw an unhandled exception, then a BadRequest<ProblemDetails> will be returned, with the appropriate details set on the ProblemDetails.
The returned type from Run and RunAsync is always augmented to additionally include BadRequest<ProblemDetails>
_ -> Results<Ok, BadRequest<ProblemDetails>>
T -> Results<Ok<T>, BadRequest<ProblemDetails>>
Example use
private static Results<Ok<IAsyncEnumerable<Todo>>, BadRequest<ProblemDetails>> GetAllHandler(HttpContext context, SqliteConnection db)
{
return RunToOk(Execute, context, "Failed to get todos");
IAsyncEnumerable<Todo> Execute() =>
db.QueryAsync<Todo>("SELECT * FROM Todos");
}
In this example the Execute method returns IAsyncEnumerable<Todo>
IAsyncEnumerable<Todo>
The RunToOk / RunToOkAsync extension method will
- change the T to Ok<T>
- add BadRequest<ProblemDetails>.
Results<Ok<IAsyncEnumerable<Todo>>, BadRequest<ProblemDetails>>
Note: It is recommended to add the following to your GlobalUsings.cs file.
global using static Futurum.WebApiEndpoint.Micro.WebApiEndpointRunner;
This means you can use the helper functions without having to specify the namespace. As in the examples.
Uploading file(s) with additional JSON payload
Upload single file and payload
Use the FormFileWithPayload type to upload a single file and a JSON payload
private static Task<Results<Ok<FileDetailsWithPayloadDto>, BadRequest<ProblemDetails>>> UploadWithPayloadHandler(HttpContext context, FormFileWithPayload<PayloadDto> fileWithPayload)
{
return RunAsync(Execute, context, ToOk, "Failed to read file");
async Task<FileDetailsWithPayloadDto> Execute()
{
var tempFile = Path.GetTempFileName();
await using var stream = File.OpenWrite(tempFile);
await fileWithPayload.File.CopyToAsync(stream);
return new FileDetailsWithPayloadDto(fileWithPayload.File.FileName, fileWithPayload.Payload.Name);
}
}
Upload multiple files and payload
Use the FormFilesWithPayload type to upload multiple files and a JSON payload
private static Task<Results<Ok<IEnumerable<FileDetailsWithPayloadDto>>, BadRequest<ProblemDetails>>> UploadsWithPayloadHandler(
HttpContext context, FormFilesWithPayload<PayloadDto> filesWithPayload)
{
return RunAsync(Execute, context, ToOk, "Failed to read file");
async Task<IEnumerable<FileDetailsWithPayloadDto>> Execute()
{
var fileDetails = new List<FileDetailsWithPayloadDto>();
foreach (var file in filesWithPayload.Files)
{
var tempFile = Path.GetTempFileName();
await using var stream = File.OpenWrite(tempFile);
await file.CopyToAsync(stream);
fileDetails.Add(new FileDetailsWithPayloadDto(file.FileName, filesWithPayload.Payload.Name));
}
return fileDetails;
}
}
Additional helper functions
ToOk
Converts a T to an Ok<T>.
ToOk
ToCreated
Converts a () to a Created.
ToCreated<string>
By default it will take the location from the HttpContext.Request.Path.
or
Converts a T to a Created<T>.
This can be overridden by passing in a string.
ToCreated<T>("/api/articles")
ToAccepted
Converts a () to a Accepted.
ToAccepted<string>
By default it will take the location from the HttpContext.Request.Path.
or
Converts a T to a Accepted<T>.
By default it will take the location from the HttpContext.Request.Path.
This can be overridden by passing in a string.
ToAccepted<T>("/api/articles")
Comprehensive samples
There are examples showing the following:
- A basic blog CRUD implementation
- The ToDo sample from Damian Edwards here
- AsyncEnumerable
- Bytes file download
- EndpointFilter on a specific WebApiEndpoint
- Exception handling
- File(s) upload
- File(s) upload with Payload
- File download
- OpenApi versioning
- Output Caching
- Rate Limiting
- Security with a basic JWT example on a specific WebApiEndpoint
- Weather Forecast
- Addition project containing WebApiEndpoints
Security example
How to use in Swagger UI:
- Run the Sample project
- In the Swagger UI, go to the 'Security' 'Login' endpoint
- Set the following Username = user1 Password = password1 SetPermissions = true SetClaim = true SetRole = true
- Copy the value returned without double quotes.
- Go to the 'Security' 'Protected' endpoint
- Click on the padlock
- In the value textbox, enter "Bearer " (don't forget the space at the end) + the value returned from the 'Login' endpoint that you copied in step 4.
- Click "Authorize"
- Run the 'Protected' endpoint
Convention Customisation
Although the default conventions are good enough for most cases, you can customise them.
IWebApiOpenApiVersionConfigurationService
This is used to get the OpenApiInfo for each WebApiEndpointVersion.
serviceCollection.AddWebApiEndpointOpenApiVersionConfigurationService<WebApiOpenApiVersionConfigurationService>();
IWebApiOpenApiVersionUIConfigurationService
This is used to configure the OpenApi JSON endpoint for each WebApiEndpointVersion.
serviceCollection.AddWebApiEndpointOpenApiVersionUIConfigurationService<WebApiOpenApiVersionUIConfigurationService>();
IWebApiVersionConfigurationService
This is used to configure ApiVersioning and ApiExplorer.
There is an overload of AddWebApiEndpoints that takes a generic type of IWebApiVersionConfigurationService.
builder.Services.AddWebApiEndpoints<CustomWebApiVersionConfigurationService>();
Use this instead
builder.Services.AddWebApiEndpoints();
Roslyn Analysers
- FWAEM0001 - Non empty constructor found on WebApiEndpoint
- FWAEM0002 - BadRequest without 'ProblemDetails' use found on WebApiEndpoint
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. |
-
net8.0
- Asp.Versioning.Http (>= 7.1.0)
- Asp.Versioning.Mvc.ApiExplorer (>= 7.1.0)
- Microsoft.AspNetCore.OpenApi (>= 8.0.0)
- Swashbuckle.AspNetCore (>= 6.5.0)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on Futurum.WebApiEndpoint.Micro:
Package | Downloads |
---|---|
Futurum.WebApiEndpoint.Micro.Core.Extensions
A dotnet library that extends Futurum.WebApiEndpoint.Micro, to make it fully compatible with Futurum.Core. |
GitHub repositories
This package is not used by any popular GitHub repositories.
Version | Downloads | Last updated |
---|---|---|
2.0.8 | 358 | 12/26/2023 |
2.0.7 | 123 | 12/25/2023 |
2.0.6 | 187 | 12/14/2023 |
2.0.5 | 110 | 12/12/2023 |
2.0.4 | 125 | 12/10/2023 |
2.0.3 | 124 | 12/8/2023 |
2.0.2 | 137 | 12/6/2023 |
2.0.1 | 127 | 12/6/2023 |
2.0.0 | 134 | 12/6/2023 |
1.0.5 | 175 | 4/21/2023 |
1.0.4 | 184 | 4/14/2023 |
1.0.3 | 175 | 4/7/2023 |
1.0.2 | 205 | 4/2/2023 |
1.0.1 | 206 | 3/27/2023 |
1.0.0 | 213 | 3/26/2023 |