MinimalEndpoints 1.0.7
See the version list below for details.
dotnet add package MinimalEndpoints --version 1.0.7
NuGet\Install-Package MinimalEndpoints -Version 1.0.7
<PackageReference Include="MinimalEndpoints" Version="1.0.7" />
paket add MinimalEndpoints --version 1.0.7
#r "nuget: MinimalEndpoints, 1.0.7"
// Install MinimalEndpoints as a Cake Addin #addin nuget:?package=MinimalEndpoints&version=1.0.7 // Install MinimalEndpoints as a Cake Tool #tool nuget:?package=MinimalEndpoints&version=1.0.7
MinimalEndpoints
A light weight abstraction over ASP.Net Minimal API that implements REPR (Request-Endpoint-Response) Pattern.
Why use MinimalEndpoints?
MinimalEndpoints offers an alternative to the Minimal Api and MVC Controllers with the aim of increasing developer productivity. You get the performance Minimal Api and benefits of MVC Controllers.
Installing MinimalEndpoints
You should install MinimalEndpoints with NuGet:
Install-Package MinimalEndpoints
Or via the .NET command line interface (.NET CLI):
dotnet add package MinimalEndpoints
Either commands, from Package Manager Console or .NET Core CLI, will allow download and installation of MinimalEndpoints and all its required dependencies.
How do I get started?
First, configure MinimalEndpoints to know where the commands are located, in the startup of your application:
var builder = WebApplication.CreateBuilder(args);
//...
// Tells MinimalEndpoints which assembly to scan for endpoints
builder.Services.AddMinimalEndpoints(typeof(MyClass));
//OR Scanning multiple assemblies
builder.Services.AddMinimalEndpoints(typeof(MyService), typeof(MyEndPoint));
Calling AddMinimalEndpoints with an argument will result in the current assembly being scanned for endpoints
//...
app.UseMinimalEndpoints();
Create a class that implements the IEndpoint interface.
public class GetAllCustomers : IEndpoint
{
private readonly ICustomerRepository _customerRepository;
public GetAllCustomers(ICustomerRepository customerRepository)
{
_customerRepository = customerRepository;
}
public string Pattern => "/customers";
public HttpMethod Method => HttpMethod.Get;
public Delegate Handler => GetCustomers;
private IResult GetCustomers()
{
var customers = _customerRepository.GetAll();
return Results.Ok(customers);
}
}
You can also implement the abstract base classes <em>EndpointBase</em> to access helper methods that wraps alot of the static methods on the Results class.
public class DeleteTodoItem : EndpointBase, IEndpoint
{
private readonly ITodoRepository _repository;
public DeleteTodoItem(ITodoRepository repository)
{
_repository = repository;
}
public string Pattern => "/todos/{id}";
public HttpMethod Method => HttpMethod.Delete;
public Delegate Handler => DeleteAsync;
private async Task<IResult> DeleteAsync(string id)
{
if (string.IsNullOrWhiteSpace(id))
{
return BadRequest("id is required"); // EndpointBase wrapper for Results.BadRequest(object?).
}
var todo = await _repository.Get(id);
if (todo == null) return NotFound(); // EndpointBase wrapper method
await _repository.Delete(id);
return Ok();// EndpointBase wrapper method
}
}
You can also inherit your endpoints from any of generic classes that implements the IEndPoint interface.
Endpoint<TResponse> is used for endpoints without a request.
Endpoint<TRequest, TResponse> is used for endpoints with both a request and response
GetByIdEndpoint<TResponse> is used for endpoints that getting an object by its integer id
public class GetCustomerById : GetByIdEndpoint<Customer>
{
private readonly ICustomerRepository _customerRepository;
public GetCustomerById(ICustomerRepository customerRepository)
{
_customerRepository = customerRepository;
}
public override string Pattern => "/customers/{id:int}";
public override Task<Customer> SendAsync(int id)
{
return Task.FromResult(_customerRepository.GetById(id));
}
}
How do I secure MinimalEndpoints?
MinimalEndpoints leverages existing ASP.NET Authorization features and requires little effort for integration.
//...
builder.Services.AddWebMinimalEndpoints();
//...
//Adding JWT Token support (this is just for demo use)
builder.Services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(c =>
{
var key = Encoding.ASCII.GetBytes(builder.Configuration["AuthZ:SecretKey"]);
c.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = builder.Configuration["AuthZ:Issuer"],
ValidAudience = builder.Configuration["AuthZ:Audience"],
IssuerSigningKey = new SymmetricSecurityKey(key)
};
});
builder.Services.AddTransient<IAuthorizationHandler, MaxTodoItemsRequirementHandler>();
builder.Services.AddAuthorization(options =>
{
//Adding sample policies
options.AddPolicy("todo:read-write", policyBuilder =>
{
policyBuilder.RequireClaim("todo:read-write", "true");
});
options.AddPolicy("todo:max-count", policyBuilder =>
{
policyBuilder.AddRequirements(new MaxTodoCountRequirement(5));
});
});
//Add the Authorize attribute to the endpoint
[Authorize(Policy = "todo:read-write")]
public class DeleteTodoItem : EndpointBase, IEndpoint
{
private readonly ITodoRepository _repository;
public DeleteTodoItem(ITodoRepository repository)
{
_repository = repository;
}
public string Pattern => "/todos/{id}";
public HttpMethod Method => HttpMethod.Delete;
public Delegate Handler => DeleteAsync;
private async Task<IResult> DeleteAsync(string id)
{
if (string.IsNullOrWhiteSpace(id))
{
return BadRequest("id is required");
}
var todo = await _repository.Get(id);
if (todo == null) return NotFound();
await _repository.Delete(id);
return Ok();
}
}
MinimalEndpoint can return a more detailed and user friendly response whenever there's an authorization failure. To enable this feature call the UseAuthorizationResultHandler method on the EndpointConfiguration class when adding MinimalEndpoint to the request pipeline. The next step is to pass an instance of the EndpointAuthorizationFailureReason class to the AuthorizationHandlerContext.Fail() method call in your AuthorizationHandler<TRequirement> classes. You can also use the ClaimsRequirement class when configuring authorization to return custom messages to the client.
//start up configuration
app.UseMinimalEndpoints(o =>
{
o.DefaultRoutePrefix = "/api/v1";
o.DefaultGroupName = "v1";
o.Filters.Add(new ProducesResponseTypeAttribute(StatusCodes.Status400BadRequest));
o.UseAuthorizationResultHandler();
});
//Authorization handler
public class MaxTodoItemsRequirementHandler : AuthorizationHandler<MaxTodoCountRequirement>
{
private readonly ITodoRepository _repository;
private readonly IHttpContextAccessor _httpContext;
public MaxTodoItemsRequirementHandler(ITodoRepository repository, IHttpContextAccessor httpContext)
{
_repository = repository;
_httpContext = httpContext;
}
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, MaxTodoCountRequirement requirement)
{
if (requirement == null) return;
var items = await _repository.GetAllAsync();
if (requirement.MaxItems <= items.Count())
{
var instance = _httpContext?.HttpContext?.Request.Path.Value;
var reason = new EndpointAuthorizationFailureReason(this,
"Maximum number of todo items reached. Please remove some items and try again",
instance, "https://httpstatuses.com/403",
"Cannot add new item", 403);
context.Fail(reason);
return;
}
context.Succeed(requirement);
}
}
//Using custom ClaimRequirement
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("todo:read-write", policyBuilder =>
{
policyBuilder.RequireClaim("todo:read-write", "true"); // default claims configuration
});
options.AddPolicy("todo:max-count", policyBuilder =>
{
policyBuilder.AddRequirements(new ClaimsRequirement("todo:read-write2",
"You do not have permission to create, update or delete todo items",
allowedValues: new[] { "true" })); //custom claim configuration
policyBuilder.AddRequirements(new MaxTodoCountRequirement(1));
});
});
How to support OpenAPI/Swagger with MinimalEndpoints?
Your endpoints will be visible via Swagger with no extra effort, however you can used the EndpointAttribute class to customize how your endpoints are exposed via Swagger.
- TagName: This property affects how your endpoints are grouped on the Swagger UI page.
- OperationId: This propery is used to identitfy each endpoint. This is also used when creating calling Results.CreateAtRoute(string routeName, object routeValue).
- GroupName: This property is used to assign an endpoint to a specific Swagger document when multiple Open API soecifications are configured
- ExcludeFromDescription: Set this property to true if you want don't want to list your endpoint on the Swagger UI page
- RoutePrefixOverride: This property is used to override the default route prefix, if it was configured at startup.
- Filters: Use this property to add filters to all endpoints. Only the ProducesResponseType attribute is currently supported for global filters
You can improve your endpoint documentation by using comments to enrich the Swagger UI. You can follow the instructions from this blog to implement cooment support.
[Endpoint(TagName = "Todo", OperatinId = nameof(UpdateTodoItem))]
public class UpdateTodoItem : IEndpoint
{
private readonly ITodoRepository _repository;
public UpdateTodoItem(ITodoRepository repository)
{
_repository = repository;
}
public string Pattern => "/todos/{id}";
public HttpMethod Method => HttpMethod.Put;
public Delegate Handler => UpdateAsync;
/// <summary>
/// Updates a todo item completed status
/// </summary>
/// <param name="id">Todo unique identifier</param>
/// <param name="todo">Todo item to be updated</param>
/// <returns></returns>
/// <response code="200">Item updated sucessfully</response>
/// <response code="400">Invalid data passed from client</response>
/// <response code="404">Item not found</response>
/// <response code="500">Internal server error occured</response>
private async Task<IResult> UpdateAsync(string id, TodoItem todo)
{
if (todo == null || !todo.completed.HasValue)
{
return Results.BadRequest("completed is required");
}
await _repository.Update(id, todo.completed.Value);
return Results.Ok();
}
}
//...
You can also use the ProducesResponseType attribute to provide details of the various HTTP codes return from your endpoint.
How do I enable CORS with MinimalEndpoints?
You can simply add the EnableCors attribute to your endpoint and add the CORS middleware during your application startup.
Setting route prefix?
You can set the route prefix that is used by all your endpoint during application startup. The example below sets the defautl route prefix to /api/v1.
//...
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
//..
app.UseMinimalEndpoints(o =>
{
o.DefaultRoutePrefix = "/api/v1";
});
You can override the default route prefix for an enpoint by adding the EndpointAttribute to the endpoint and setting the RoutePrefixOverride property to the desired route prefix. This can be used to support endpoint versioning.
[Endpoint(TagName = "Todo", OperationId = nameof(CreateTodoItemV2), RoutePrefixOverride = "/api/v2")]
public class CreateTodoItemV2 : Endpoint<string, IResult>
{
private readonly ITodoRepository _repository;
public CreateTodoItemV2(ITodoRepository repository)
{
_repository = repository;
}
public override string Pattern => "/todos";
public override HttpMethod Method => HttpMethod.Post;
/// <summary>
/// This is version 2 of the create todo endpoint
/// </summary>
/// <param name="description">Todo description</param>
/// <returns>New created item</returns>
public override async Task<IResult> SendAsync(string description)
{
if (string.IsNullOrWhiteSpace(description))
{
return Results.BadRequest("description is required");
}
if (description.Length < 5)
{
return Results.BadRequest("description is length must be greater than or equal to five characters");
}
var id = await _repository.CreateAsync(description);
return Results.Created($"/endpoints/todos/{id}", new TodoItem(id, description, false));
}
}
Model Binding & Content Negotiation
ASP.NET Minimal API comes with support for consuming json. If you want to support other content types, such as xml, you need custom binding logic on your models. For detailed instructions on how to implement this method see this blog. MinimalEndpoints offers a similar solution that integrates well with it's other features. You can implement the IEndpointModelBinder interface and register the class in the DI container.
To use your new model binding capabilities you can simply inherit your endpoints fromt the EnpointBase<TRequest,TResponse> class or use the GetModelAsync extension method on the HttpRequest object. MinimalEndpoints supports both json and xml model binding and will throw an EndpointModelBindingException exception when an error occurs during model binding. If you inherit from the EndpointBase<TRequest,TResponse> class the exception will only be sent to the caller when the environment is set to development, otherwise its wraped in the problem details response.
//Implement model binding contract
public class XmlEndpointModelBinder : IEndpointModelBinder
{
public bool CanHandle(string? contentType)
=> contentType?.IndexOf("xml", StringComparison.OrdinalIgnoreCase) != -1;
public async ValueTask<TModel?> BindAsync<TModel>(HttpRequest request, CancellationToken cancellationToken)
{
TModel? model = default;
if (request.HasXmlContentType())
model = await request.ReadFromXmlAsync<TModel>(cancellationToken);
return model;
}
}
//Inheriting from EndpointBase<TRequest,TResponse>
//Use the accept attribute to tell clients what content types are allowed. This example accepts json and xml
[Accept(typeof(CustomerDto), "application/json", AdditionalContentTypes = new[] { "application/xml" })]
[Endpoint(TagName = "Customer", OperationId = nameof(CreateCustomer))]
public class CreateCustomer : EndpointBase<CustomerDto, Customer>
{
private readonly ICustomerRepository _repository;
public CreateCustomer(ILoggerFactory loggerFactory, ICustomerRepository repository) : base(loggerFactory)
{
_repository = repository;
}
public override string Pattern => "/customers";
public override HttpMethod Method => HttpMethod.Post;
public override async Task<IResult> HandleRequestAsync(CustomerDto customerDto, HttpRequest httpRequest, CancellationToken cancellationToken = default)
{
try
{
var customer = await _repository.CreateAsync(customerDto);
return CreatedAtRoute(nameof(GetCustomerById), new { id = customer.Id }, customer);
}
catch
{
//custom error handling or throw for base class to handle
throw;
}
}
}
//Using extenion method to bind models
CustomerDto? model = await httpRequest.GetModelAsync<CustomerDto>(cancellationToken);
Content negotiation can be extended by implementing the IResponseNegotiator interface. Once you've regitered your class, you can utalize the new feature by inheriting from the EndpointBase<TRequest,TResponse> class or using the SendAsync extension method on the HttpResponse object. MinimalEndpoint adds xml support to the existing content types already supported by ASP.NET Minimal API.
//Calling SendAsync method will automatically negotiate the contenttype to send to client
response.Headers.Location = uri;
await response.SendAsync(model, StatusCodes.Status201Created)
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
- Microsoft.AspNetCore.Cors (>= 2.2.0)
- Microsoft.AspNetCore.Mvc.Core (>= 2.2.5)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 8.0.0)
- Microsoft.IO.RecyclableMemoryStream (>= 3.0.0)
- System.IdentityModel.Tokens.Jwt (>= 7.2.0)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on MinimalEndpoints:
Package | Downloads |
---|---|
MinimalEndpoints.Swashbuckle.AspNetCore
Swash buckle support to facilitate xml comment to enrich api swagger endpoints. |
GitHub repositories
This package is not used by any popular GitHub repositories.
Version | Downloads | Last updated | |
---|---|---|---|
1.3.2 | 67 | 11/13/2024 | |
1.3.1 | 98 | 9/19/2024 | |
1.3.0 | 73 | 8/2/2024 | |
1.2.9 | 109 | 6/22/2024 | |
1.2.7 | 99 | 6/15/2024 | |
1.2.6 | 79 | 6/4/2024 | |
1.2.5 | 102 | 5/29/2024 | |
1.2.4 | 152 | 4/12/2024 | |
1.2.3 | 128 | 4/2/2024 | |
1.2.2 | 118 | 4/2/2024 | |
1.2.1 | 196 | 4/1/2024 | |
1.1.0 | 138 | 3/10/2024 | |
1.0.9 | 123 | 3/6/2024 | |
1.0.8 | 132 | 1/29/2024 | |
1.0.7 | 119 | 1/26/2024 | |
1.0.5 | 493 | 7/20/2022 | |
1.0.4 | 435 | 7/19/2022 | |
1.0.3 | 425 | 7/15/2022 | |
1.0.2 | 437 | 7/13/2022 | |
1.0.1 | 455 | 7/11/2022 | |
1.0.0 | 430 | 7/10/2022 |