Modern development demands efficiency, clarity, and flexibility, especially when it comes to building RESTful APIs. The Atc.Rest.MinimalApi library is crafted with these principles in mind, offering a comprehensive collection of components specifically designed to streamline API development.
From EndpointDefinitions that standardize and automate endpoint registration to intricate ValidationFilters that ensure data integrity, the components within this package encapsulate best practices in a way that's accessible and customizable. With additional features for error handling, versioning, and enhanced Swagger documentation, the library provides a robust foundation for creating scalable and maintainable APIs.
Whether you're building a brand-new project or seeking to enhance an existing one, Atc.Rest.MinimalApi can significantly simplify development, allowing you to focus on what truly matters: delivering value to your users.
- Atc.Rest.MinimalApi
- Table of Contents
- Automatic endpoint discovery and registration
- Automatic endpoint discovery and registration with services
- SwaggerFilters
- Validation
- Middleware
- Sample Project
- Requirements
- How to contribute
In modern API development, maintaining consistency and automation in endpoint registration is paramount. Utilizing an interface like IEndpointDefinition
can automate the process, seamlessly incorporating all endpoints within the API into the Dependency Container. By inheriting from this interface in your endpoints, you enable a systematic orchestration for automatic registration, as illustrated in the subsequent examples.
This approach simplifies configuration, ensures uniformity across endpoints, and fosters a more maintainable codebase.
public interface IEndpointDefinition
{
void DefineEndpoints(
WebApplication app);
}
Upon defining all endpoints and ensuring they inherit from the specified interface, the process of automatic registration can be systematically orchestrated and configured in the subsequent manner.
var builder = WebApplication.CreateBuilder(args);
/// Adds the endpoint definitions to the specified service collection by scanning the assemblies of the provided marker types.
/// In this example the empty assembly marker interface IApiContractAssemblyMarker defined in the project where EndpointDefinitions reside.
/// This method looks for types that implement the IEndpointDefinition interface and are neither abstract nor an interface,
/// and adds them to the service collection as a single instance of IReadOnlyCollection{IEndpointDefinition}.
builder.Services.AddEndpointDefinitions(typeof(IApiContractAssemblyMarker));
var app = builder.Build();
/// Applies the endpoint definitions to the specified web application.
/// This method retrieves the registered endpoint definitions from the application's services and invokes
/// their <see cref="IEndpointDefinition.DefineEndpoints"/> and/or <see cref="IEndpointAndServiceDefinition.DefineEndpoints"/> method.
app.UseEndpointDefinitions();
An example of how to configure an endpoint upon inheriting from the specified interface.
public sealed class UsersEndpointDefinition : IEndpointDefinition
{
internal const string ApiRouteBase = "/api/users";
public void DefineEndpoints(
WebApplication app)
{
var users = app.NewVersionedApi("Users");
var usersV1 = users
.MapGroup(ApiRouteBase)
.HasApiVersion(1.0);
usersV1
.MapGet("/", GetAllUsers)
.WithName("GetAllUsers");
}
internal Task<Ok<IEnumerable<User>>> GetAllUsers(
[FromServices] IGetUsersHandler handler,
CancellationToken cancellationToken)
=> handler.ExecuteAsync(cancellationToken);
}
An alternative approach is using the interface IEndpointAndServiceDefinition
.
public interface IEndpointAndServiceDefinition : IEndpointDefinition
{
void DefineServices(
IServiceCollection services);
}
Upon defining all endpoints and ensuring they inherit from the specified interface, the process of automatic registration can be systematically orchestrated and configured in the subsequent manner.
var builder = WebApplication.CreateBuilder(args);
/// Adds the endpoint definitions to the specified service collection by scanning the assemblies of the provided marker types.
/// This method looks for types that implement the <see cref="IEndpointDefinition"/> and <see cref="IEndpointAndServiceDefinition"/>
/// interface and are neither abstract nor an interface,
/// and adds them to the service collection as a single instance of <see cref="IReadOnlyCollection{IEndpointDefinition}"/>
/// and <see cref="IReadOnlyCollection{IEndpointAndServiceDefinition}"/>.
builder.Services.AddEndpointAndServiceDefinitions(typeof(IApiContractAssemblyMarker));
var app = builder.Build();
/// Applies the endpoint definitions to the specified web application.
/// This method retrieves the registered endpoint definitions from the application's services and invokes
/// their <see cref="IEndpointDefinition.DefineEndpoints"/> and/or <see cref="IEndpointAndServiceDefinition.DefineEndpoints"/> method.
app.UseEndpointDefinitions();
An example of how to configure an endpoint upon inheriting from the specified interface.
public sealed class UsersEndpointDefinition : IEndpointAndServiceDefinition
{
internal const string ApiRouteBase = "/api/users";
public void DefineServices(
IServiceCollection services)
{
services.AddScoped<IUserService, UserService>();
}
public void DefineEndpoints(
WebApplication app)
{
var users = app.NewVersionedApi("Users");
var usersV1 = users
.MapGroup(ApiRouteBase)
.HasApiVersion(1.0);
usersV1
.MapGet("/", GetAllUsers)
.WithName("GetAllUsers");
}
internal Task<Ok<IEnumerable<User>>> GetAllUsers(
[FromServices] IUserService userService,
CancellationToken cancellationToken)
=> userService.GetAllUsers(cancellationToken);
}
In the development of RESTful APIs, filters play an essential role in shaping the output and behavior of the system. Whether it's providing detailed descriptions for enumerations or handling default values and response formats, filters like those in the Atc.Rest.MinimalApi.Filters.Swagger namespace enhance the API's functionality and documentation, aiding in both development and integration.
Filter Name | Summary | Remarks |
---|---|---|
SwaggerDefaultValues | This class is an operation filter for Swagger/Swashbuckle to document the implicit API version parameter. | This filter is only required due to specific bugs in the SwaggerGenerator. Once fixed and published, this class can be removed. |
SwaggerEnumDescriptionsDocumentFilter | This class is a document filter to handle and describe enumerations within the Swagger documentation. | This filter enhances the Swagger documentation by incorporating detailed descriptions for enumerated types in the SwaggerUI. |
Feature | Description |
---|---|
Description and Summary | Applies the endpoint's description and summary metadata if available. |
Deprecation Handling | Marks an operation as deprecated if applicable. |
Response Types Handling | Adjusts response content types based on the API's supported response types. |
Parameter Handling | Adjusts the description, default values, and required attributes for parameters. |
Feature | Description |
---|---|
Enum Descriptions in Result Models | Adds descriptions for enums within result models. |
Enum Descriptions in Input Params | Appends descriptions to input parameters that are of an enumeration type. |
Enum Descriptions Construction | Constructs a string description for enums, listing integer values along with their corresponding names. |
An example of how to configure the swagger filters when adding Swagger generation to the API.
services.AddSwaggerGen(options =>
{
options.OperationFilter<SwaggerDefaultValues>();
options.DocumentFilter<SwaggerEnumDescriptionsDocumentFilter>();
});
Enhance your Minimal API with powerful validation using the ValidationFilter<T>
class. This filter integrates both DataAnnotations validation and FluentValidation, combining and deduplicating errors into a cohesive validation response.
Out of the box, the filter can be applied as shown below:
usersV1
.MapPost("/", CreateUser)
.WithName("CreateUser")
.AddEndpointFilter<ValidationFilter<CreateUserParameters>>()
.ProducesValidationProblem();
At times, you may find it necessary to manipulate the serialized type names for validation keys/values, or to dictate the naming conventions of these keys/values.
With the ValidationFilterOptions
class, you can customize the filter's behavior to suit your needs, such as opting to skip the first level when resolving serialization type names for validation keys/values.
The filter can be configured in two distinct ways:
Locally injected ValidationFilterOptions
usersV1
.MapPost("/", CreateUser)
.WithName("CreateUser")
.AddEndpointFilter(new ValidationFilter<UpdateUserByIdParameters>(new ValidationFilterOptions
{
SkipFirstLevelOnValidationKeys = true,
}))
.ProducesValidationProblem();
Alternatively, you can use the following method, where the
ValidationFilterOptions
have been registered in the dependency container earlier in the pipeline globally for all endpoints.
builder.services.AddSingleton(_ => new ValidationFilterOptions
{
SkipFirstLevelOnValidationKeys = true,
});
usersV1
.MapPost("/", CreateUser)
.WithName("CreateUser")
.AddEndpointFilter<ValidationFilter<CreateUserParameters>>()
.ProducesValidationProblem();
The GlobalErrorHandlingMiddleware
class provides a mechanism for handling uncaught exceptions globally across an ASP.NET Core application.
This middleware helps in capturing any unhandled exceptions that occur during the request processing pipeline. It translates different types of exceptions into the appropriate HTTP status codes and responds with a standardized error message.
An example of how to configure the middleware.
var app = builder.Build();
app.UseMiddleware<GlobalErrorHandlingMiddleware>();
An example of how to configure the middleware with options.
var app = builder.Build();
var options = new GlobalErrorHandlingMiddlewareOptions
{
IncludeException = true,
UseProblemDetailsAsResponseBody = false,
};
app.UseMiddleware<GlobalErrorHandlingMiddleware>(options);
An example of how to configure the middleware with options through a extension method UseGlobalErrorHandler
.
var app = builder.Build();
app.UseGlobalErrorHandler(options =>
{
options.IncludeException = true;
options.UseProblemDetailsAsResponseBody = false;
});
The sample project Demo.Api
located in the sample folder within the repository illustrates a practical implementation of the Atc.Rest.MinimalApi package, showcasing all the features and best practices detailed in this documentation. It's a comprehensive starting point for those looking to get a hands-on understanding of how to effectively utilize the library in real-world applications.
The Demo.Api project also leverages the Asp.Versioning.Http
Nuget package to establish a proper versioning scheme. It's an example implementation of how API versioning can be easily managed and maintained.