Reprise 2.2.0

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

// Install Reprise as a Cake Tool
#tool nuget:?package=Reprise&version=2.2.0                

Reprise

Reprise is a micro-framework that brings the REPR (Request-Endpoint-Response) pattern and vertical slice architecture into the ASP.NET Core 6+ Minimal APIs. It aims to be unopioniated towards API behavior and to provide a thin layer of abstractions that encourages the creation of convention-based and modular implementations.

Getting started

  1. Create a new ASP.NET Core 6/7 empty project.

  2. Install the Reprise NuGet package.

  3. Modify your Program.cs to use Reprise.

var builder = WebApplication.CreateBuilder();
builder.ConfigureServices();
var app = builder.Build();
app.UseExceptionHandling();
app.MapEndpoints();
app.Run();
  1. Create an endpoint Endpoints/GetHelloEndpoint.cs.
[Endpoint]
public class GetHelloEndpoint
{
    [Get("/")]
    public static string Handle() => "Hello, world!";
}
  1. Run the application.

Endpoints

An endpoint is a public class that is decorated with the EndpointAttribute. It should contain a public static Handle method which should be decorated with one of the HTTP method and route attributes. These are:

  • GetAttribute
  • PostAttribute
  • PutAttribute
  • PatchAttribute
  • DeleteAttribute
  • MapAttribute - for other/multiple HTTP methods

The Handle method can be synchronous as well as asynchronous and can have any signature and any return type.

[Endpoint]
public class UpdateUserEndpoint
{
    [Put("/users/{id}")]
    public static IResult Handle(int id, UserDto userDto, DataContext context)
    {
        // ...
    }
}

In the example id comes from the route, userDto - from the body, and context - from the DI container. Check the documentation for more information about parameter binding.

Services

Any class (including an endpoint) that has a public parameterless constructor and implements IServiceConfgurator can configure services.

[Endpoint]
public class GetWeather : IServiceConfigurator
{
    public void ConfigureServices(WebApplicationBuilder builder)
    {
        builder.Services.AddWeatherService();
    }

    [Get("/weather")]
    public static async Task<WeatherForecast[]> Handle(IWeatherService weatherService)
    {
        return await weatherService.GetForecast();
    }
}

Configuration

You can bind a strongly typed hierarchical configuration using the ConfigurationAttribute. A simple example would look like this:

[Endpoint]
public class GetGreetings
{
    [Get("/greetings")]
    public static IEnumerable<string> Handle(GreetingConfiguration configuration)
    {
        return configuration.Names.Select(name => string.Format(configuration.Message, name));
    }
}

[Configuration("Greeting")]
public class GreetingConfiguration
{
    public string Message { get; set; } = null!;

    public List<string> Names { get; set; } = null!;
}

The GreetingConfiguration is added to the DI container with a singleton lifetime and is bound to the following section in appsettings.json:

{
    "Greeting": {
        "Message": "Hello, {0}!",
        "Names": [ "Alice", "John", "Skylar" ]
    }
}

Deeper nested sub-sections could as well be bound using a key like "Foo:Bar". Check the documentation for more information about configuration.

Filters (.NET 7 only)

You can add filters for all API endpoints:

app.MapEndpoints(options => 
{
    options.AddEndpointFilter<FilterA>();
    options.AddEndpointFilter<FilterB>();
    options.AddEndpointFilter<FilterC>();
});

The filters should implement IEndpointFilter and will be executed in the order of registration.

You can also apply a filter to specific endpoints by using the FilterAttribute. Such a filter will be executed after the global ones.

Check the documentation for more information about filters.

Validation

Reprise relies on FluentValidation. On application startup, all IValidator<T> implementations are added with a singleton lifetime. You can then inject the validator in your Handle method.

[Endpoint]
public class CreateUserEndpoint
{
    [Post("/users")]
    public static IResult Handle(UserDto userDto, IValidator<UserDto> validator, DataContext context)
    {
        validator.ValidateAndThrow(userDto);
        // ...
    }
}

public class UserDtoValidator : AbstractValidator<UserDto>
{
    public UserDtoValidator()
    {
        RuleFor(u => u.FirstName).NotEmpty();
        RuleFor(u => u.LastName).NotEmpty();
    }
}

Check the documentation for more information about FluentValidation.

Validation filter (.NET 7 only)

It's also possible to add validation as a filter for all endpoints. This way, you don't need to manually inject the validator and validate.

app.MapEndpoints(options => options.AddValidationFilter());

The filter is implemented as an EndpointFilterFactory, so it will be invoked only on endpoints having a non-nullable parameter of type for which a validator is found. If multiple parameters are validatable, only the first one will be handled by the filter.

Exception handling

By default, the exception handler returns no body and:

  • On BadHttpRequestException (e.g. thrown when the request body couldn't be deserialized) logs an error and returns status code 400.
  • On ValidationException returns status code 400.
  • On all other exceptions logs an error and returns status code 500.

To customize exception logging, you can implement IExceptionLogger. Similarly, you can customize the response body by implementing IErrorResponseFactory.

Authorization

There are two ways to secure your API. You can require authorization only for specific endpoints by decorating the Handle method with the AuthorizeAttribute from Microsoft.AspNetCore.Authorization. Alternatively, you can require authorization for all endpoints and opt-out for specific ones using the AllowAnonymousAttribute.

app.MapEndpoints(options => options.RequireAuthorization());

Check the documentation for more information about authentication and authorization.

OpenAPI

OpenAPI tags are typically used to group operations in the Swagger UI. You can assign custom tag(s) by decorating the Handle method with the TagsAttribute. If no such attribute is found, Reprise extracts a tag from the route of every endpoint. The first segment that is not "api" (case insensitive) or a parameter is capitalized and set as a tag. "/" is used when no match is found.

Self-checks

Reprise will throw an InvalidOperationExcaption on application startup when:

  • An endpoint has no Handle method.
  • An endpoint has multiple Handle methods.
  • A Handle method has no HTTP method and route attribute.
  • A Handle method has multiple HTTP method and route attributes.
  • A Handle method has an empty route.
  • A Handle method has an empty HTTP method.
  • An HTTP method and route combination is handled by more than one endpoint.
  • A service configurator has no public paramereterless constructor.
  • A configuration model could not be bound (e.g. missing property for a configuration key).
  • A configuration sub-section is bound to multiple types.
  • A model is validated by multiple validators.
  • IExceptionLogger has more than one implementation.
  • IErrorResponseFactory has more than one implementation.
  • An OpenAPI tag is empty.

Performance

Besides the assembly scan at application startup when configuring the DI container and discovering endpoints, Reprise doesn't add any performance overhead when handling requests.

Support

If you spot any problems and/or have improvement ideas, please share them via the issue tracker.

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

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
3.7.0 396 7/16/2023
3.6.0 205 7/12/2023
3.5.0 267 4/30/2023
3.4.2 367 2/20/2023
3.4.1 300 2/16/2023
3.4.0 302 2/5/2023
3.3.1 254 2/3/2023
3.3.0 288 2/1/2023
3.2.0 318 1/20/2023
3.1.6 336 12/20/2022
3.1.5 312 12/18/2022
3.1.4 301 12/12/2022
3.1.3 306 12/11/2022
3.1.2 355 12/4/2022
3.1.1 345 11/30/2022
3.1.0 331 11/27/2022
3.0.0 328 11/26/2022
2.2.0 357 11/23/2022
2.1.0 373 11/21/2022
2.0.0 348 11/19/2022
1.1.0 375 10/30/2022
1.0.3 425 10/22/2022
1.0.2 407 10/21/2022
1.0.1 427 10/20/2022
1.0.0 1,177 10/19/2022