The aim of this pattern is to get away from the bloated god controllers that have a million action methods and so many dependencies. By following the SimpleEndpoints pattern we keep the endpoint scoped to a small feature and lightweight which makes it easier to understand and manage. The aim is not to blur the line between the controllers and the domain layer. You can choose to dispatch the request to the domain from the endpoint or handle it in the endpoint itself. Make an informed choice based to the context.
More about it in the blog post here.
- Install and reference the Nuget
SimpleEndpoints
In the NuGet Package Manager Console, type:
Install-Package SimpleEndpoints
- Define your request and response models
public class SimpleMessage
{
public string Message { get; set; }
}
public class SimpleResponse
{
public string Message { get; set; }
}
- Create your endpoint and implement your business logic (You can choose to handle in place or dispatch to a domain layer)
public class SimpleMessageEndpoint : AsyncGetEndpoint<SimpleMessage, SimpleResponse>
{
public override async Task<ActionResult<SimpleResponse>> HandleAsync(SimpleMessage requestModel, CancellationToken cancellationToken = default)
{
// Handle in place or dispatch to the domain i.e. return await _someDomainService.HandleAsync(requestModel)
return new SimpleResponse()
{
Message = "Hello " + requestModel.Message
};
}
}
- In the
ConfigureServices()
method in yourStartup.cs
add the following
public void ConfigureServices(IServiceCollection services)
{
// Other services go here
services.AddControllers();
services.AddSimpleEndpointsRouting(); // This is required to translate endpoint names
}
- Navigate to the URL
https://localhost:port_number/simplemessage?message=world
and see the result.
Checkout the Examples folder for more. Await more examples in the coming weeks.
The Endpoints are automatically inherited from a ControllerBase
and decorated with ApiController
attribute. You can decorate the endpoint class/action method with the usual (Route, HttpGet, FromQuery etc) attributes to customise and extend the functionality. Endpoints fully support AspNetCore routing conventions.
If you really need an endpoint with a custom route, a mix of parameters coming from the Route/Query/Body or need full control over of any aspect then you can do something like this. Each of these class/method attributes works independently of each other and you can pick and choose them as required.
[Route("api/some-path/[endpoint]")] //results in "api/some-path/mycustom"
public class MyCustomEndpoint : AsyncEndpoint<Request, Result>
{
[NonAction] // important: mark this as non action to ignore this method when routing
public override async Task<ActionResult<Result>> HandleAsync(Request request, CancellationToken cancellationToken = default)
{
// actual logic here
}
// definew your custom signature
[HttpGet("custom_path")]
public async Task<ActionResult<Result>> CustomMethod([FromQuery]string id, [FromBody]BodyModel model, CancellationToken cancellationToken)
{
// map to the view model
var requestModel = new Request() {
id_property = id,
model_property - model
}
// pass to the handler method
return await this.HandleAsync(requestModel, cancellationToken);
}
}
I've had good success with creating a folder structure like below.
You can take this one step further and create a folder per feature group, then put each endpoint specific folder inside that if you want as well. I recommend keeping the view models in the same folder as it's easier to find related code when they sit next to each other.
Feel free to contribute and raise issues as you see fit :)
- Creator: Dasith Wijesiriwardena (https://dasith.me)