Archives May 2022

Directory structure – Model-View-Controller-2

The advantage of using a helper method is leveraging the ASP.NET Core MVC mechanism, making our life easier. However, you could manually manage the HTTP response using lower-level APIs like HttpContext or create custom classes that implement the IActionResult interface to hook your custom response classes into the MVC pipeline.Now let’s look at the multiple ways we can use to return data to the client:

Return typeDescription
voidWe can return void and manually manage the HTTP response using the HttpContext class. This is the most low-level and complex way.
TModelWe can directly return the model, which ASP.NET Core will serialize. The problem with this approach is that we don’t control the status code, nor can we return multiple different results from the action.
ActionResult IActionResultWe can return one of those two abstractions. The concrete result can take many forms depending on the implementation that the action method returns. However, doing this makes our API less auto-discoverable by tools like SwaggerGen.
ActionResult<TModel>We can return the TModel directly and other results like a NotFoundResult or a BadRequestResult. This is the most flexible way that makes the API the most discoverable by the ApiExplorer.

 Table 6.2: multiple ways to return data

We start with an example where the actions return an instance of the Model class by leveraging the Ok method (highlighted code):

using Microsoft.AspNetCore.Mvc;
namespace MVC.API.Controllers;
[Route(“api/[controller]”)]
[ApiController]
public class ValuesController : ControllerBase
{
    [HttpGet(“IActionResult”)]
    public IActionResult InterfaceAction()
        => Ok(new Model(nameof(InterfaceAction)));
    [HttpGet(“ActionResult”)]
    public ActionResult ClassAction()
        => Ok(new Model(nameof(ClassAction)));
    // …
   
public record class Model(string Name);
}

The problem with the preceding code is API discoverability. The ApiExplorer can’t know what the endpoints return. The ApiExplorer describes the actions as returning 200 OK but doesn’t know about the Model class.To overcome this limitation, we can decorate our actions with the ProducesResponseType attribute, effectively circumventing the limitation as shown below:

[ProducesResponseType(typeof(Model), StatusCodes.Status200OK)]
public IActionResult InterfaceAction() { …
}

In the preceding code, we specify the return type as the first argument and the status code as the second. Using the constants of the StatusCodes class is a convenient way to reference standard status codes. We can decorate each action with multiple ProducesResponseType attributes to define alternate states, such as 404 and 400.

With ASP.NET Core MVC, we can also define conventions that apply broad rules to our controllers, allowing us to define those conventions once and reuse them throughout our application. I left a link in the Further reading section.

Next, We explore how we can return a Model instance directly. The ApiExplorer can discover the return value of the method this way, so we do not need to use the ProducesResponseType attribute:

[HttpGet(“DirectModel”)]
public Model DirectModel()
    => new Model(nameof(DirectModel));

Next, thanks to class conversion operators (see Appendix A for more info), we can do the same with ActionResult<T>, like this:

[HttpGet(“ActionResultT”)]
public ActionResult<Model> ActionResultT()
    => new Model(nameof(ActionResultT));

The main benefit of using ActionResult<T> is to return other types of results. Here is an example showing this where the method returns either Ok or NotFound:

[HttpGet(“MultipleResults”)]
public ActionResult<Model> MultipleResults()
{
    var condition = Random.Shared
        .GetItems(new[] { true, false }, 1)
        .First();
    return condition
        ?
Ok(new Model(nameof(MultipleResults)))
        : NotFound();
}

However, the ApiExplorer does not know about the 404 Not Found, so we must document it using the ProducesResponseType attribute.

We can return a Task<T> or a ValueTask<T> from the action method when the method body is asynchronous. Doing so lets you write the async/await code from the controller.

I highly recommend returning a Task<T> or a ValueTask<T> whenever possible because it allows your REST API to handle more requests using the same resources without effort. Nowadays, non-Task-based methods in libraries are infrequent, so you will most likely have little choice.

We learned multiple ways to return values from an action. The ActionResult<T> class is the most flexible regarding feature support. On the other hand, IActionResult is the most abstract one.Next, we look at routing requests to those action methods.