One of the cool things coming in 2.1 is ActionResult<T>.

It allows some nice things for API actions. Actually using it in MVC actions seems pretty pointless.

I was curious how it works, so I decided to check the source code on GitHub.

It's a pretty short class, and easy to understand. But what is the point?

What is ActionResult<T>

The main point is to allow you to return either an object or an ActionResult from your action.

It means you can do this:

[Route("api/employee")]
public class EmployeeApiController : ControllerBase
{
    [HttpGet("{id}")]
    public ActionResult<Employee> Get(long id)
    {
        if(id <= 0)
        {
            return BadRequest();
        }

        var employee = GetEmployee(id);

        if(employee == null)
        {
            return NotFound();
        }

        return employee;
    }
}

But why does it matter?

There are two reasons that I can think of:

  1. Slightly less code
  2. The method signature now says what the type of the returned object is

The second point is pretty great for auto-generated API documentation and/or API clients.

OpenAPI (formerly known as Swagger)

If the API does not use ActionResult<T>, any generated API spec (like OpenAPI) cannot know from the action method signature what the type of the returned object is.

It means you have to add a [Produces] attribute on the action:

[Produces(typeof(Employee))]
public IActionResult Get(long id)
{
    var employee = GetEmployee(id);
    return Ok(employee);
}

But with ActionResult<T>, you don't need to do that. The method signature already contains the info.

public ActionResult<Employee> Get(long id)
{
    var employee = GetEmployee(id);
    return employee;
}

So you get API documentation like this that tells what kind of object will be returned:

Screenshot of OpenAPI UI with auto-generated type information

How does it work: Implicit operators

ActionResult<T> implements two implicit operators which do most of the work:

public static implicit operator ActionResult<TValue>(TValue value)
{
    return new ActionResult<TValue>(value);
}

public static implicit operator ActionResult<TValue>(ActionResult result)
{
    return new ActionResult<TValue>(result);
}

What is an implicit operator you might ask? It is effectively an automatic type conversion function.

The first function takes a TValue and converts it to ActionResult<TValue>. The second does the same thing for action result types.

This means when your action return type is ActionResult<T> and you return an object, the compiler will automatically insert a call to the first function to convert the object before returning it.

The reason it is called implicit is that you do not need a cast operator to trigger it.

Conversion to IActionResult

Weirdly enough the class does not implement IActionResult.

The class instead implements an interesting interface: IConvertToActionResult.

By returning a result that implements this from an action, the framework (ActionMethodExecutor actually) will call Convert() on the result to get the actual result.

By the way, if you return anything other than IActionResult or IConvertToActionResult from your action, it gets wrapped in an ObjectResult.

I'm curious what other things we could do with this deferred action result conversion :)

Here is the ActionResult<T> implementation for the conversion:

IActionResult IConvertToActionResult.Convert()
{
    return Result ?? new ObjectResult(Value)
    {
        DeclaredType = typeof(TValue),
    };
}

So if the Result property contains an ActionResult, we return that. Otherwise we create an ObjectResult containing the object returned. This is actually what has happened automatically since 1.x if you make an action like:

[HttpPost]
public MyClass DoSomething()
{
    //Will be converted to ObjectResult
    return new MyClass();
}

Summary and links

Overall ActionResult<T> is just one of the great things coming in ASP.NET Core 2.1.

It allows us to reduce the boilerplate code in API actions. Be sure to also check out the [ApiController] attribute coming in 2.1 for things like automatic 400 Bad Requests when model validation fails, which means even less boilerplate!

Links: