Futurum.WebApiEndpoint.Micro 2.0.4

There is a newer version of this package available.
See the version list below for details.
dotnet add package Futurum.WebApiEndpoint.Micro --version 2.0.4                
NuGet\Install-Package Futurum.WebApiEndpoint.Micro -Version 2.0.4                
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Futurum.WebApiEndpoint.Micro" Version="2.0.4" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add Futurum.WebApiEndpoint.Micro --version 2.0.4                
#r "nuget: Futurum.WebApiEndpoint.Micro, 2.0.4"                
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install Futurum.WebApiEndpoint.Micro as a Cake Addin
#addin nuget:?package=Futurum.WebApiEndpoint.Micro&version=2.0.4

// Install Futurum.WebApiEndpoint.Micro as a Cake Tool
#tool nuget:?package=Futurum.WebApiEndpoint.Micro&version=2.0.4                

Futurum.WebApiEndpoint.Micro

license CI Coverage Status NuGet version

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 partial 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();
}

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 Security, EndpointFilters, RateLimiting, OutputCaching, etc.

Easy setup

  • Add the NuGet package ( futurum.webapiendpoint.micro ) to your project
  • Update program.cs as per here

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

See program.cs in sample project

AddWebApiEndpointsFor... (per project containing WebApiEndpoints)

This will be automatically created by the source generator.

You need to call this for each project that contains WebApiEndpoints, in order for them to be added to the pipeline.

e.g.

builder.Services.AddWebApiEndpointsForFuturumWebApiEndpointMicroSample();
UseWebApiEndpoints

Adds the WebApiEndpoints to the pipeline and does various other setup needed for the WebApiEndpoints to work.

app.UseWebApiEndpoints();
UseWebApiEndpointsOpenApi

Register the OpenApi UI (Swagger and SwaggerUI) middleware. This is usually only done in development mode.

app.UseWebApiEndpointsOpenApi();

How to create a WebApiEndpoint

  1. Create a new partial class.
  2. Add the WebApiEndpoint attribute to the class, with the route prefix for all the REST methods in this WebApiEndpoint. You can also optionally add a tag. This is used in the OpenApi documentation. If you do not specify a tag, then the route prefix is used.
  3. Add the WebApiEndpointVersion attribute to the class, if you want to specify a specific ApiVersion. If you do not specify a specific ApiVersion, then the default ApiVersion is used. You can add multiple WebApiEndpointVersion attributes to the class, if you want to support multiple ApiVersions.
  4. Implement the Build method and add minimal api(s) as per usual.
  5. Optionally implement the Configure method to configuration the WebApiEndpoint
Build

You can map your minimal apis for this WebApiEndpoint in the Build method.

The IEndpointRouteBuilder that the Build method receives has already:

protected override void Build(IEndpointRouteBuilder builder)
{
}
Full example
[WebApiEndpoint("weather")]
public partial 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();
}

See WeatherWebApiEndpoint in sample project

Configure

You can optionally configure the WebApiEndpoint in the Configure method.

protected override RouteGroupBuilder Configure(RouteGroupBuilder groupBuilder, WebApiEndpointVersion webApiEndpointVersion)
{
}

This allows you to setup the RouteGroupBuilder. This will effect all minimal apis in this classes Build method.

You can also configure it differently per ApiVersion.

This ia a good place to add a WebApiEndpoint specific EndpointFilter
groupBuilder.AddEndpointFilter<CustomEndpointFilter>();

See EndpointFilterWebApiEndpoint in sample project

This ia a good place to add a WebApiEndpoint specific RateLimiting
groupBuilder.RequireRateLimiting(RateLimiting.SlidingWindow.Policy);

See RateLimitingWebApiEndpoint in sample project

This ia a good place to add a WebApiEndpoint specific OutputCache
groupBuilder.CacheOutput(OutputCaching.ExpiryIn10Seconds.Policy);

See OutputCachingWebApiEndpoint in sample project

This ia a good place to add WebApiEndpoint specific Security
groupBuilder.RequireAuthorization(Authorization.Permission.Admin);

See SecurityProtectedWebApiEndpoint in sample project

Configuration

Configuring Futurum.WebApiEndpoint.Micro

This allows you to configure:

  • DefaultApiVersion (mandatory)
    • This is used if a ApiVersion is not provided for a specific WebApiEndpoint.
  • OpenApi
    • DefaultInfo (optional)
      • This is used if a OpenApiInfo is not provided for a specific ApiVersion
    • VersionedInfo (optional)
      • Allowing you to have an OpenApiInfo per a specific ApiVersion. If you do not provide an OpenApiInfo for a specific ApiVersion, then the DefaultInfo is used.
  • Version
    • Prefix (optional)
    • Format (optional)
      • uses 'Asp.Versioning.ApiVersionFormatProvider'
Example in program.cs
builder.Services
       .AddWebApiEndpoints(new WebApiEndpointConfiguration(WebApiEndpointVersions.V1_0.Version)
       {
           OpenApi =
           {
               DefaultInfo =
               {
                   Title = "Futurum.WebApiEndpoint.Micro.Sample",
               },
               VersionedInfo =
               {
                   {
                       WebApiEndpointVersions.V3_0.Version,
                       new OpenApiInfo
                       {
                           Title = "Futurum.WebApiEndpoint.Micro.Sample v3"
                       }
                   }
               }
           }
       });

See program.cs in sample project

Configuring the entire API

The entire API can be configured. This is a good place to configure things like:

  • Global route prefix
  • Global authorization (don't forget to set AllowAnonymous on the individual WebApiEndpoint that you don't want to be secured i.e. Login endpoint)

The class must implement IGlobalWebApiEndpoint interface

** NOTE - there can only be one of these classes. There is an analyser to check for this. **

** NOTE - this is applied before the version route is created. **

Example
public class GlobalWebApiEndpoint : IGlobalWebApiEndpoint
{
    public IEndpointRouteBuilder Configure(IEndpointRouteBuilder builder, WebApiEndpointConfiguration configuration)
    {
        return builder.MapGroup("api").RequireAuthorization(Authorization.Permission.Admin);
    }
}

See GlobalWebApiEndpoint in sample project

Configuring a specific API version

A specific API version can be configured. This is a good place to configure things like:

  • API version specific authorization (don't forget to set AllowAnonymous on the individual WebApiEndpoint that you don't want to be secured i.e. Login endpoint)

The class must:

  • implement IWebApiVersionEndpoint interface
  • be decorated with at least one WebApiVersionEndpointVersion attribute, for the version(s) it applies to

** NOTE - there can only be one of these classes per version. There is an analyser to check for this. **

** NOTE - this is applied after the version route is created, but before the WebApiEndpoint specific route is created. **

Example
[WebApiVersionEndpointVersion(WebApiEndpointVersions.V3_0.Number)]
[WebApiVersionEndpointVersion(WebApiEndpointVersions.V1_20_Beta.Text)]
public class WebApiVersionEndpoint3_0a : IWebApiVersionEndpoint
{
    public RouteGroupBuilder Configure(IEndpointRouteBuilder builder, WebApiEndpointConfiguration configuration)
    {
        return builder.MapGroup("test-api").RequireAuthorization(Authorization.Permission.Admin);
    }
}

See WebApiVersionEndpoint3_0a in sample project

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 void or 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>

void -> 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
  • Configuring setting for entire API versions

Comprehensive samples

Security example

How to use in Swagger UI:

  1. Run the Sample project
  2. In the Swagger UI, go to the 'Security' 'Login' endpoint
  3. Set the following Username = user1 Password = password1 SetPermissions = true SetClaim = true SetRole = true
  4. Copy the value returned without double quotes.
  5. Go to the 'Security' 'Protected' endpoint
  6. Click on the padlock
  7. 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.
  8. Click "Authorize"
  9. 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();

Extendable GlobalExceptionHandler

Built in support for handling unhandled exceptions, returning a ProblemDetails response.

You can extend the GlobalExceptionHandler by adding your own custom exception handling and overriding the default exception handler.

NOTE: ExceptionToProblemDetailsMapperService is not thread-safe for either:

  • adding custom exception to ProblemDetails mapping
  • overriding default exception to ProblemDetails mapping

It is recommended to do this in the program.cs file.

Add to program.cs

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddExceptionHandler<GlobalExceptionHandler>();

...
    
var app = builder.Build();

app.UseExceptionHandler();

Add custom Exception to ProblemDetails mapping

In program.cs add the following:

ExceptionToProblemDetailsMapperService.Add<CustomException>((exception, httpContext, errorMessage) => new()
{
    Detail = "An custom error occurred.",
    Instance = httpContext.Request.Path,
    Status = StatusCodes.Status500InternalServerError,
    Title = ReasonPhrases.GetReasonPhrase(StatusCodes.Status500InternalServerError)
});

Override the Default Exception to ProblemDetails mapping

In program.cs add the following:

ExceptionToProblemDetailsMapperService.OverrideDefault((exception, httpContext, errorMessage) => new()
{
    Detail = "An error occurred.",
    Instance = httpContext.Request.Path,
    Status = StatusCodes.Status500InternalServerError,
    Title = ReasonPhrases.GetReasonPhrase(StatusCodes.Status500InternalServerError)
});

Roslyn Analysers

  • FWAEM0001 - Non empty constructor found on WebApiEndpoint
  • FWAEM0002 - BadRequest without 'ProblemDetails' use found on WebApiEndpoint
  • FWAEM0003 - Multiple instances found of GlobalWebApiEndpoint
  • FWAEM0004 - Multiple instances found of WebApiVersionEndpoint for the same version
Product 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

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 352 12/26/2023
2.0.7 119 12/25/2023
2.0.6 183 12/14/2023
2.0.5 106 12/12/2023
2.0.4 121 12/10/2023
2.0.3 120 12/8/2023
2.0.2 133 12/6/2023
2.0.1 123 12/6/2023
2.0.0 130 12/6/2023
1.0.5 171 4/21/2023
1.0.4 180 4/14/2023
1.0.3 171 4/7/2023
1.0.2 201 4/2/2023
1.0.1 202 3/27/2023
1.0.0 209 3/26/2023