Saltar al contenido

Artículos, tutoriales, trucos, curiosidades, reflexiones y links sobre programación web ASP.NET Core, MVC, Blazor, SignalR, Entity Framework, C#, Azure, Javascript... y lo que venga ;)

17 años online

el blog de José M. Aguilar

Inicio El autor Contactar

Artículos, tutoriales, trucos, curiosidades, reflexiones y links sobre programación web
ASP.NET Core, MVC, Blazor, SignalR, Entity Framework, C#, Azure, Javascript...

¡Microsoft MVP!
martes, 19 de junio de 2018
ASP.NET Core MVC Como sabéis, por defecto las acciones MVC y Web API en ASP.NET Core retornan un IActionResult (o un Task<IActionResult> en caso de ser asíncronas). Este interfaz, cuya única misión es definir el método de ejecución asíncrona del resultado, ExecuteResultAsync(), es implementado por la gran variedad de tipos de resultado ofrecidos de serie por el framework, como ViewResult, RedirectResult o FileResult.

Sin embargo, no es esta la única opción disponible a la hora de especificar el resultado de una acción, como veremos a continuación.

¿Qué más puede retornar una acción en ASP.NET Core?

Pues además de una instancia de IActionResult, ASP.NET Core MVC ofrece otros tipos de resultado a la hora de implementar acciones.

Por ejemplo, el framework también incluye la clase abstracta ActionResult, definida como vemos más abajo:
public abstract class ActionResult : IActionResult
{
    public virtual Task ExecuteResultAsync(ActionContext context)
    {
        ExecuteResult(context);
        return Task.CompletedTask;
    }

    public virtual void ExecuteResult(ActionContext context)     {  }
}
Esta clase, que es la base de prácticamente todos los tipos de resultados que vienen de serie con el framework, establece un puente sencillo entre la ejecución asíncrona del resultado y su implementación síncrona, facilitando la construcción de IActionResult personalizados.

También existe la posibilidad de retornar directamente objetos desde las acciones, una solución bastante útil cuando implementamos una API como la siguiente:
[HttpGet("users/{id}")]
public User Get(int id)
{
    var user = _userRepository.GetById(id);
    return user;
}
Observa que la gran ventaja de este último enfoque es que la acción muestra claramente el tipo de objeto retornado, por lo que, además de aumentar su legibilidad, herramientas como Swagger podrían generar documentación y clientes muy específicos.

Y por último, hace poco hablábamos de que la llegada de ASP.NET Core 2.1 venía acompañada de un nuevo tipo de resultado para las acciones MVC: la clase ActionResult<T>. Básicamente, una acción definida con un retorno ActionResult<T> devolverá o bien una instancia de ActionResult o bien una instancia de T. Esto es muy interesante para solucionar escenarios como el siguiente, que fallarían en compilación:
// Ojo, no compila!
[HttpGet("users/{id}")]
public User Get(int id)
{
    var user = _userRepository.GetById(id);

    if (user == null)
        return NotFound(); 
    return user;
}
Obviamente, el código anterior podríamos solucionarlo muy fácilmente como se muestra a continuación, pero entonces no bastaría con observar la firma de la acción para saber qué tipo de objeto es el que podemos esperar de ella:
// Compila, pero la acción no da pistas sobre el tipo de objetos que retorna
[HttpGet("users/{id}")]
public ActionResult Get(int id)
{
    var user = _userRepository.GetById(id);
    if (user == null)
        return NotFound(); 
    return Ok(user);
}
Y ahí es donde entra en juego el nuevo ActionResult<T>. Utilizando esta clase, la cosa quedaría bastante mejor pues tenemos la posibilidad de retornar cualquier ActionResult, pero manteniendo la claridad y capacidad de autodocumentación del método de acción:
// Compila y la acción muestra el tipo de objetos que retorna
[HttpGet("users/{id}")]
public ActionResult<User> Get(int id)
{
    var user = _userRepository.GetById(id);

    if (user == null)      
        return NotFound();
    return user;           
}

¿Y cómo es posible que compile? ¿Es esto magia negra? 👹

Pues aunque a primera vista pudiera parecerlo, no lo es ;) El truco está en la utilización ingeniosa del operador implicit de C# para conseguir asimilar los tipos ActionResult y T con ActionResult<T> mediante conversores implícitos.

Para los curiosos, aquí os dejo el código fuente de ActionResult<T>, así os evito ir a Github a verlo:
public sealed class ActionResult<TValue> : IConvertToActionResult
{
    public ActionResult(TValue value)
    {
        Value = value;
    }

    public ActionResult(ActionResult result)
    {
        Result = result ?? throw new ArgumentNullException(nameof(result));
    }

    public ActionResult Result { get; }
    public TValue Value { get; }

    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);
    }

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

Publicado en Variable not found.