-
-
Notifications
You must be signed in to change notification settings - Fork 116
Returning Result Objects from ASP.NET Core Controller
It is one of the most common usecases to use an ASP.NET Core API controller to provide functionality to the external world (other systems, frontend, etc). A controller action is executed and return a response. If the controller action is executed successfully then a success response (Http status code 200) with a value is returned. If the controller action is not executed successfully then a failure response is returned. The reason of a failure response can be very different.
Failure type | Returning Http status code |
---|---|
failed business validation/rules | 400 - Bad Request |
no entity found | 404 - Not Found |
User is not authorized | 401 - Unauthorized |
... | ... |
Within your application you use Result
and Result<T>
objects and Error
and Success
objects provided by the FluentResult package to model error and success messages in a powerfull way. The problem is that these classes are not designed to use it as return type of controller actions because they contain many internal and duplicate information and are not optimized for serialization.
This problem is solved by the new package FluentResults.Extensions.AspNetCore which provides a great ASP.NET Core integration with fluent syntax and many extension points to customize the behaviour.
Key Features
⭐ A good starting point with the default implemented behaviour
⭐ Customizing - define your own http response dtos
⭐ Customizing - define your own transformation logic from result objects to http responses (status code, ...)
⭐ Your project dependencies keep clean - only the FluentResults.Extensions.AspNetCore package has a dependency to ASP.NET Core packages
Install the package via NugGet in your ASP.NET Core project. You don't need this package in your domain projects - in domain projects you only need the FluentResults package.
Install-Package FluentResults.Extensions.AspNetCore
In general you start with such a controller action. Domain logic is executed and you get back a result object which indicate if the domain logic is executed successfully or not. If not then the result objects contain error messages. If it is a successfull result object then the controller action should return a PersonDto object. If it is a failed result object then the error messages of the result should be returned.
[HttpPost]
public async Task<ActionResult<PersonDto>> CreatePerson(CreatePersonCommand request)
{
Result<Person> result = await Domain.CreatePerson(request);
return ????;
}
This challenge can be easily solved now.
- First map with the method
Map(...)
the object of typePerson
to an object of typePersonDto
. - Second call the method
ToActionResult()
to transform the result object of typeResult<PersonDto>
to your action result.
[HttpPost]
public async Task<ActionResult<PersonDto>> CreatePerson(CreatePersonCommand request)
{
return await Domain.CreatePerson(request)
.Map(person => new PersonDto
{
Id = person.Id,
Vorname = person.Vorname,
Nachname = person.Nachname
})
.ToActionResult();
}
If you want to use the default behaviour then your are finished now - congratulation!
Currently three extension points are supported to transform Result
and Result<T>
objects to an Http ActionResult.
Extension Point | Description |
---|---|
TransformFailedResultToActionResult | This logic is called when a failed result should be transformed to an action result |
TransformOkNoValueResultToActionResult | This logic is called when a successful result without internal value (=Result ) should be transformed to an action result |
TransformOkValueResultToActionResult | This logic is called when a successful result with internal value (=Result<T> ) should be transformed to an action result |
The default implemented behaviour can be found here .
If you want to change one of the default behaviour then inherit from the class DefaultAspNetCoreResultProfile
and overwrite one of the methods. You can change the transformation logic and also the structure/dto and status code of the http response. A good example you can find here.
If you already defined your custom behaviour via a new child class of DefaultAspNetCoreResultProfile
you can register this class in the Program.cs or Startup.cs file via the following code. Then your custom behaviour is everywhere used automatically.
AspNetCoreResult.Setup(config => config.DefaultProfile = new CustomAspNetCoreResultEndpointProfile());
If you call ToActionResult()
without a parameter then the default profile which is set on global level is used. If you have multiple profiles then you can also pass the profile via calling ToActionResult(new AnotherCustomAspNetCoreResultEndpointProfile())
.