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, 26 de abril de 2022
ASP.NET Core

Imaginad que tenemos un controlador MVC como el siguiente:

public class TestController : Controller
{
    public IActionResult Add(int a, int b)
    {
        return Content($"Result: {a + b}");
    }
}

Claramente, la acción Add() retornará la suma de los enteros a y b que le llegarán como parámetros de la query string:

GET https://localhost:7182/test/add?a=1&b=2 HTTP/1.1

HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8

Result: 3

Pero, como sabemos, podríamos llamar a la acción sin indicar alguno de esos parámetros, o incluso ninguno de ellos:

Petición Respuesta
GET /test/add?a=1&b=2 Result: 3
GET /test/add?a=0&b=0 Result: 0
GET /test/add?a=1 Result: 1
GET /test/add Result: 0

Esto es así porque el binder será capaz de poblar correctamente los parámetros a y b cuando estén presentes en la cadena de la petición y sean correctos, o les asignará su valor por defecto (0) cuando no hayan sido suministrados.

Pero dado que el cero es un valor de entrada válido, a priori desde nuestro código no tendríamos forma de distinguir cuándo el parámetro ha sido omitido y cuándo se ha establecido expresamente.

¿Cómo podríamos hacerlo?

Exigir parámetros no nulos, o usar [Required]

En parámetros de tipo valor como los usados en el ejemplo anterior, bastaría con hacerlos anulables y comprobar si sus valores son nulos, lo cual indicaría que en la petición no se incluyó un valor válido para ellos:

public IActionResult Add(int? a, int? b)
{
    if (a == null || b == null)
        return BadRequest();

    return Content($"Result: {a + b}");
}

Otra forma, muy sencilla, legible y totalmente alineada con las buenas prácticas MVC, consiste simplemente en utilizar el atributo de validación [Required] en ambos parámetros. Así, el binder establecerá el ModelState como inválido si no existen valores correctos para ellos, y podremos actuar en consecuencia, como en el siguiente código:

public IActionResult Add([Required]int a, [Required]int b)
{
    if (!ModelState.IsValid)
        return BadRequest();

    return Content($"Result: {a + b}");
}

De esta forma, en ambos casos se retornará un resultado Bad request (HTTP 400) para peticiones que no incluyan valores apropiados para los parámetros a y b.

Obviamente, en este caso podríamos mejorarlo usando el filtro [ApiController], pues sus convenciones se encargarían de verificar la validez de los datos de entrada incluso antes de ejecutar la acción.

¿Y los parámetros de tipo referencia, como string?

Lo visto anteriormente funcionará bien con parámetros enteros o cualquier otro tipo valor (long, byte, bool, decimal...), pero con los tipos referencia como string la cosa cambia un poco en función de la configuración del proyecto.

Si tenemos habilitados los tipos referencia anulables en el proyecto ASP.NET Core 6 (por ejemplo, introduciendo la propiedad <Nullable>enable</Nullable> en el .csproj, que es la configuración por defecto), los parámetros string no permitirán la entrada de nulos durante el binding. Por tanto, el siguiente código funcionará de forma directa sin necesidad de utilizar atributos como [Required] explícitamente, porque el framework lo habrá inferido automáticamente:

public class TestController: Controller
{
    public IActionResult Concat(string a, string b)
    {
        if (!ModelState.IsValid)
            return BadRequest();
        return Content($"Result: '{a + b}'");
    }
}

Una petición como GET /test/concat retornará un error 400 (Bad request), mientras que algo como GET /test/concat?a=12&b=34 retornará "Result: '1234'".

Esto tiene bastante sentido, pues, cuando los tipos referencia anulables están activados, la propia firma de la acción ya está indicando que esos parámetros nunca deberían recibir nulos. Por esta razón, el binder establecerá el ModelState como inválido. Si quisiésemos permitir la entrada de nulos, deberíamos haber utilizado string? en la definición de los parámetros.

En cambio, si hubiéramos desactivado esa característica (<Nullable>disable</Nullable>), los parámetros string sí podrían contener nulos, por lo que deberíamos comprobarlos manualmente o bien utilizando [Required], igual que con los tipos valor:

public IActionResult Concat([Required] string a, [Required] string b)
{
    if (!ModelState.IsValid) 
        return BadRequest();
    return Content($"Result: '{a + b}'");
}

Publicado en Variable not found.

Aún no hay comentarios, ¡sé el primero!