An API library bringing REPR-style endpoint classes to .NET Minimal API.
Should be:
- Lightweight -- and easy to work with or without.
- Junior Developer Friendly -- without impeding more experienced engineers.
- Backward Compatible -- with Minimal API configuration and features.
- Configurable -- using MinimalApi RouteHandlerBuilder.
- Well Structured -- for common tasks such as validation, mapping and error handling.
- Extensible -- to allow for custom alternate endpoint implmentations.
- Fast and Easy -- to rapidly scaffold projects, endpoints and tests.
- Low Maintenance -- for testing.
- Reduced configuration noise over minimal api endpoints
- Constructor dependency injection
- Scoped lifetime
- Assembly scanned and configured request validator and model mapper
- Built-in Endpoint Class Conveniences
- HttpContext
- Logger
- Environment Info
- Response Object
- Model Mapper
- TypedResult Shortcuts
public class GetSampleEndpoint(ISampleService sampleService) : RadEndpoint<GetSampleRequest, GetSampleResponse, GetSampleMapper>
{
public override void Configure()
{
Get("/samples/{id}")
.Produces<GetSampleResponse>(StatusCodes.Status200OK)
.ProducesProblem(StatusCodes.Status404NotFound)
.ProducesValidationProblem()
.WithDocument(tag: "Sample", desc: "Get Sample by ID");
//Any NET minimal api (RouteHandlerBuilder) configuration works here.
}
public override async Task Handle(GetSampleRequest r, CancellationToken ct)
{
var sample = await sampleService.GetSampleById(r.Id, ct);
if(sample is null)
{
SendNotFound("Sample not found.");
return;
}
Response = Map.FromEntity(sample);
Send();
}
}
- Automatic request model binding from route, query, header, and body using [AsParameters].
- Automatic request model validation execution using FluentValidation
public class GetSampleRequest
{
[FromRoute]
public int Id { get; set; }
}
public class GetSampleRequestValidator : AbstractValidator<GetSampleRequest>
{
public GetSampleRequestValidator()
{
RuleFor(e => e.Id).GreaterThan(0);
}
}
public class GetSampleResponse
{
public SampleDto Data { get; set; } = null!;
public string Message { get; set; } = "Sample retrieved successfully";
}
- Assign mappers for conventient access from endpoint
- Makes mapping a first class citizen with the endpoint configuration
- Map manually or with a mapping tool like AutoMapper or Mapster
public class GetExampleMapper : IRadMapper<GetExampleRequest, GetExampleResponse, Example>
{
public GetExampleResponse FromEntity(Example e) => new()
{
Data = new()
{
Id = e.Id,
FirstName = e.FirstName,
LastName = e.LastName
}
};
public Example ToEntity(GetExampleRequest r) => throw new NotImplementedException();
}
- Don't like the endpoint base classes? Make your own using the included abstractions.
- Want to use a bare minimum REPR endpoint with Open Union Types? Go for it.
- Need to create a super specialized edge case endpoint with pure minimal api endpoint? No problem.
//Code samples coming soon
- Strongly typed "Routeless" HttpClient extensions
- Reduced maintenance with automatic endpoint route discovery and request model binding
- Easy to navigate to endpoint code from test
- Consistent and convenient response assertions for HttpResponse and ProblemDetails using FluentAssertions
- Detailed exception messages so you dig less to find test issues.
[Fact]
public async void When_RequestValid_ReturnsSuccess()
{
//Arrange
var request = new GetSampleRequest { Id = 1 };
//Act
var r = await f.Client.GetAsync<GetSampleEndpoint, GetSampleRequest, GetSampleResponse>(request);
//Assert
r.Should()
.BeSuccessful<GetSampleResponse>()
.WithStatusCode(HttpStatusCode.OK);
}
- Scaffold multiple new endpoints very quickly.
- Import a set of endoints using a JSON definition.
- Full parameter support for 1 line endpoint creation.
[
{
"BaseNamepace": "Demo.Api.Endpoints",
"ResourceName": "User",
"Verb": "Get",
"EndpointName": "GetUser",
"Path": "/users/{id}",
"Entity": "User",
"Tag": "User",
"Description": "Get User by ID",
"WithMapper": true
},
{
"BaseNamepace": "Demo.Api.Endpoints",
"ResourceName": "User",
"Verb": "Post",
"EndpointName": "CreateUser",
"Path": "/users",
"Entity": "User",
"Tag": "User",
"Description": "Create a new User",
"WithMapper": true
}
...other endpoints.
]
- Project templates
- Observability Tooling
- Additional code coverage
- Documentation / How Tos
- FastEndpoints -- as many of the ideas from that project inspired this one.