Futurum.WebApiEndpoint.Micro 1.0.0

There is a newer version of this package available.
See the version list below for details.
dotnet add package Futurum.WebApiEndpoint.Micro --version 1.0.0                
NuGet\Install-Package Futurum.WebApiEndpoint.Micro -Version 1.0.0                
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="1.0.0" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add Futurum.WebApiEndpoint.Micro --version 1.0.0                
#r "nuget: Futurum.WebApiEndpoint.Micro, 1.0.0"                
#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=1.0.0

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

Futurum.WebApiEndpoint.Micro

license CI Coverage Status NuGet version

A dotnet library that allows you to build WebApiEndpoints using a vertical slice architecture approach. Built on dotnet 7 and minimal apis.

What is a WebApiEndpoint?

  • It's a vertical slice / feature of your application
  • The vertical slice is a self-contained unit of functionality
  • It's a class that inherits implements IWebApiEndpoint
  • They share a route prefix and version
[WebApiEndpoint("weather")]
  • Can have one or many API versions
[WebApiEndpointVersion(1)]

Easy setup

program.cs

AddWebApiEndpoints

Allows you to configure:

  • 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)
{
    OpenApiDocumentVersions =
    {
        {
            WebApiEndpointVersions.V1_0, 
            new OpenApiInfo
            {
                Title = "Futurum.WebApiEndpoint.Micro.Sample v1"
            }
        }
    }
});
AddWebApiEndpointsFor... (per project containing WebApiEndpoints)

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();
Full example
using Futurum.WebApiEndpoint.Micro;
using Futurum.WebApiEndpoint.Micro.Sample;

using Microsoft.OpenApi.Models;

var builder = WebApplication.CreateBuilder(args);

builder.Services
       .AddWebApiEndpoints(new WebApiEndpointConfiguration(WebApiEndpointVersions.V1_0)
       {
           DefaultOpenApiInfo = new OpenApiInfo
           {
               Title = "Futurum.WebApiEndpoint.Micro.Sample",
           }
       })
       .AddWebApiEndpointsForFuturumWebApiEndpointMicroSample();

var app = builder.Build();

app.UseWebApiEndpoints();

if (app.Environment.IsDevelopment())
{
    app.UseWebApiEndpointsOpenApi();
}

app.Run();

IWebApiEndpoint

Configure

You can configure the WebApiEndpoint in the Configure method

public void Configure(RouteGroupBuilder groupBuilder, WebApiEndpointVersion webApiEndpointVersion)
{
}

This allows you to set properties on the RouteGroupBuilder.

You can also configure it differently per ApiVersion.

Register

You can register the WebApiEndpoint in the Register method

public void Register(IEndpointRouteBuilder builder)
{
}

Full example

Weather
[WebApiEndpoint("weather")]
public class WeatherWebApiEndpoint : IWebApiEndpoint
{
    private static readonly string[] Summaries =
    {
        "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
    };

    public void Configure(RouteGroupBuilder groupBuilder, WebApiEndpointVersion webApiEndpointVersion)
    {
    }

    public void Register(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 : IWebApiEndpoint
{
    public void Configure(RouteGroupBuilder groupBuilder, WebApiEndpointVersion webApiEndpointVersion)
    {
    }

    public void Register(IEndpointRouteBuilder builder)
    {
        builder.MapGet("download", DownloadHandler);
    }

    private static Results<NotFound, FileContentHttpResult, BadRequest<ProblemDetails>> DownloadHandler(HttpContext context)
    {
        return Result.Try(Execute, () => "Failed to read file")
                     .ToWebApi(context);

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

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
  • Result error handling
  • File(s) upload
  • File(s) upload with Payload
  • File download
  • OpenApi version support
  • Security with a basic JWT example on a specific WebApiEndpoint
  • Validation - DataAnnotations and FluentValidation and both combined
  • Weather Forecast
  • Addition project containing WebApiEndpoints

Futurum.WebApiEndpoint.Micro.Sample-openapi.png

Validation

ValidationService

Executes FluentValidation and DataAnnotations

IValidationService<ArticleDto> validationService
private static Results<Ok<ArticleDto>, ValidationProblem, BadRequest<ProblemDetails>> ValidationHandler(HttpContext context, IValidationService<ArticleDto> validationService,
                                                                                                        ArticleDto articleDto) =>
    validationService.Execute(articleDto)
                     .Map(() => new Article(null, articleDto.Url))
                     .Map(ArticleMapper.MapToDto)
                     .ToWebApi(context, ToOk, ToValidationProblem);

FluentValidationService

Calls FluentValidation

IFluentValidationService<ArticleDto> fluentValidationService

e.g.

private static Results<Ok<ArticleDto>, ValidationProblem, BadRequest<ProblemDetails>> FluentValidationHandler(HttpContext context, IFluentValidationService<ArticleDto> fluentValidationService,
                                                                                                              ArticleDto articleDto) =>
    fluentValidationService.Execute(articleDto)
                           .Map(() => new Article(null, articleDto.Url))
                           .Map(ArticleMapper.MapToDto)
                           .ToWebApi(context, ToOk, ToValidationProblem);

public class ArticleDtoValidator : AbstractValidator<ArticleDto>
{
    public ArticleDtoValidator()
    {
        RuleFor(x => x.Url).NotEmpty().WithMessage("must have a value;");
    }
}

DataAnnotationsService

IDataAnnotationsValidationService dataAnnotationsValidationService
private static Results<Ok<ArticleDto>, ValidationProblem, BadRequest<ProblemDetails>> DataAnnotationsValidationHandler(HttpContext context,
                                                                                                                       IDataAnnotationsValidationService dataAnnotationsValidationService,
                                                                                                                       ArticleDto articleDto) =>
    dataAnnotationsValidationService.Execute(articleDto)
                                    .Map(() => new Article(null, articleDto.Url))
                                    .Map(ArticleMapper.MapToDto)
                                    .ToWebApi(context, ToOk, ToValidationProblem);

Full compatibility with Futurum.Core

WebApiResultsExtensions.ToWebApi

Comprehensive set of extension methods to transform a Result and Result<T> to an TypedResult.

Result<IResult> → Results<IResult, BadRequest<ProblemDetails>>
  • If the Result<IResult> is a Success<IResult> then the IResult will be returned.
  • If the Result<T> is a Failure<T> then the BadRequest<ProblemDetails> will be returned, with the appropriate details set on the ProblemDetails. The error message will be safe to return to the client, that is, it will not contain any sensitive information e.g. StackTrace.

This works for:

Result<IResult>

Result<Results<IResult, IResult>>

Result<Results<IResult, IResult, IResult>>

Result<Results<IResult, IResult, IResult, IResult>>

Result<Results<IResult, IResult, IResult, IResult, IResult>>

Results has a maximum of 6 types. So 5 are allowed leaving one space left for the BadRequest<ProblemDetails>.

Example use

In this example the Execute method will return:

  • a NotFound if the file does not exist
  • a FileStreamHttpResult if the file exists
Results<NotFound, FileStreamHttpResult>

The ToWebApi extension method will change this to add BadRequest<ProblemDetails>.

Results<NotFound, FileStreamHttpResult, BadRequest<ProblemDetails>>
Full Example
private static Results<NotFound, FileStreamHttpResult, BadRequest<ProblemDetails>> DownloadHandler(HttpContext context)
{
    return Result.Try(Execute, () => "Failed to read file")
                 .ToWebApi(context);

    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");
    }
}
How to handle successful and failure cases in a typed way with TypedResult

You can optionally specify which TypedResult success cases you want to handle. This is useful if you want to handle a specific successes case differently.

You can specify which TypedResult error cases you want to handle. This is useful if you want to handle a specific error case differently.

If you have a success case, you must pass in the the success helper function first, then the failure helper functions.

There can only be 1 success helper function, but there can be multiple failure helper functions.

Example use

The ToWebApi extension method will change the method return type to add BadRequest<ProblemDetails>, with the appropriate details set on the ProblemDetails. The error message will be safe to return to the client, that is, it will not contain any sensitive information e.g. StackTrace.

You can then pass in additional helper functions to deal with successes and failures and these will change the return type to the appropriate TypedResult's.

ToOk is a function that will convert a T to an Ok<T>.

ToValidationProblem is a function that will convert a ValidationResultError to a ValidationProblem.

Full Example
private static Results<Ok<ArticleDto>, ValidationProblem, BadRequest<ProblemDetails>> ValidationHandler(HttpContext context, IValidationService<ArticleDto> validationService,
                                                                                                        ArticleDto articleDto) =>
    validationService.Execute(articleDto)
                     .Map(() => new Article(null, articleDto.Url))
                     .Map(ArticleMapper.MapToDto)
                     .ToWebApi(context, ToOk, ToValidationProblem);

Success and Failure helper functions

If you have a success case, you must pass in the the success helper function first, then the failure helper functions.

There can only be 1 success helper function, but there can be multiple failure helper functions.

Note: It is recommended to add the following to your GlobalUsings.cs file.

global using static Futurum.WebApiEndpoint.Micro.WebApiResultsExtensions;

This means you can use the helper functions without having to specify the namespace. As in the examples.

Success
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")
Failure
ToNotFound

If a ResultErrorKeyNotFound has occured then it will convert it to a NotFound<ProblemDetails>, with the correct information set on the ProblemDetails.

ToNotFound
ToValidationProblem

If a ResultErrorValidation has occured then it will convert it to a ValidationProblem, with the correct information set on the HttpValidationProblemDetails.

ToValidationProblem
Product Compatible and additional computed target framework versions.
.NET net7.0 is compatible.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  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