Skip to content

MetalHexx/RadEndpoints

Repository files navigation

RadEndpoints

An API library bringing REPR-style endpoint classes to .NET Minimal API.

Library Goals

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.

Features:

REPR Endpoint Classes

  • 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();
    }
}

Request Model Binding and Validation

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

Endpoint Model Mapper

  • 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();
}

Flexibility and Alternate Base Endpoint Class

  • 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

Integration Testing

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

CLI For Scaffolding

  • Scaffold multiple new endpoints very quickly.
  • Import a set of endoints using a JSON definition.
  • Full parameter support for 1 line endpoint creation.

Endpoint Wizard

Description of Image

JSON definition

[
  {
    "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.
]

Bulk JSON Import

Description of Image

Coming Soon:

  • Project templates
  • Observability Tooling
  • Additional code coverage
  • Documentation / How Tos

Credits

  • FastEndpoints -- as many of the ideas from that project inspired this one.

About

Minimal API REPR Pattern Style Endpoint Library

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages