Skip to content

Returning Result Objects from ASP.NET Core Controller

Michael Altmann edited this page Nov 19, 2022 · 2 revisions

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

Step-by-Step Guide

Install the package FluentResults.Extensions.AspNetCore

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

Add transformation logic to controller

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 type Person to an object of type PersonDto.
  • Second call the method ToActionResult() to transform the result object of type Result<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!

Customizing behaviour via Extension Points

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.

Setting custom behaviour on global or local level

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()).