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, 1 de abril de 2014
ASP.NET MVCEn la entrega anterior de la serie hicimos una pequeña introducción al attribute routing introducido en ASP.NET MVC 5 y Web API 2, explicando cómo podíamos activar esta característica y las reglas básicas para la definición de rutas sobre acciones y controladores de ambos frameworks.

En esta ocasión vamos a ver cómo podemos incluir restricciones o constraints en dichas rutas, algo que podíamos conseguir también utilizando el rutado por convenciones pero de forma más farragosa y que ahora con attribute routing y otras mejoras incluidas en las últimas versiones de MVC y Web API se convierte en trivial.

Y ya de paso, repasaremos lo que son las restricciones de ruta y cómo podíamos usarlas en versiones anteriores de MVC y Web API, para que aquellos que aún no hayáis tenido oportunidad de trabajar con ellas.

1. ¿Qué son las restricciones de ruta?

Como sabemos, cuando una petición llega a nuestro sitio web, el sistema de routing analiza la URL entrante y la contrasta con el contenido de la tabla de rutas. Cuando en dicha tabla encuentra una regla cuyo patrón de ruta coincide con el path de la petición entrante, se utilizan sus datos para determinar a qué acción se delega el proceso de la petición, así como los parámetros a suministrarle.

Las restricciones de ruta, o route constraints, permiten añadir lógica adicional al proceso de matching entre la petición entrante y las reglas registradas en la tabla de rutas, de forma que no sólo se atienda a la coincidencia ambas URLs sino también a cualquier otro tipo de criterio. En la práctica, los constraints son básicamente funciones que retornan cierto o falso, por lo que podemos introducir en ellos cualquier lógica de comprobación.

Así, al entrar una petición, y una vez el sistema de routing ha determinado qué regla de la tabla de rutas encaja con la URL de dicha petición, se ejecutarán sucesivamente todos los constraints definidos en la misma. En cuanto uno de ellos retorne false, se descartará la ruta actual y se seguirá buscando en la tabla de rutas una entrada que encaje con la petición entrante; si todos retornan true, la ruta actual será la elegida para gestionar la petición.

2. Route constraints en versiones anteriores de MVC y Web API

En versiones de ASP.NET MVC anteriores a la 5 y Web API 2, las restricciones se introducían en las entradas de la tabla de rutas, cuanto menos, de forma algo “extraña”. Era necesario asignar al parámetro constraints de los métodos MapRoute() o MapHttpRoute() –de MVC y Web API, respectivamente-, un objeto anónimo en el que se definían a modo de diccionario clave/valor las restricciones a emplear por cada parámetro. Observad el siguiente ejemplo:
routes.MapRoute(
    name: "ProductById",
    url: "product/{id}",
    defaults: new {controller = "product", action = "details"},
    constraints: new { id = new MustBeInteger() }
);
En este código indicábamos al sistema de routing que teníamos una restricción sobre el parámetro de ruta id, cuya lógica estaba implementada en la clase MustBeInteger . La verdad es que es una forma realmente oscura y poco intuitiva para expresar algo que probablemente podría haberse solucionado de forma sencilla mediante lambdas y predicados, pero bueno…

Pero aún peor, sin duda, es que ni MVC ni Web API venían acompañados de restricciones de serie (exceptuando una, basada en expresiones regulares), por lo que todas ellas debíamos desarrollarlas nosotros, y esto ha provocado que muy poca gente se anime a usar constraints en las rutas de sus aplicaciones, aunque resulte bastante sencillo hacerlo.

Las restricciones son simplemente clases personalizadas que implementan el interfaz System.Web.Routing.IRouteConstraint, y en cuyo único método Match() se introduce la lógica de comprobación. Siguiendo con el ejemplo anterior, la siguiente restricción permitiría asegurar que el valor de un parámetro es de tipo int:
public class MustBeInteger : IRouteConstraint
{
    public bool Match(HttpContextBase httpContext, Route route, 
                      string parameterName, RouteValueDictionary values,
                      RouteDirection routeDirection)
    {
        int value;
        return int.TryParse(values[parameterName].ToString(), out value);
    }
}
Observad que este método recibe parámetros a través de los cuales puede acceder al contexto completo de la petición, la ruta actual, al nombre del parámetro evaluado (“id” en el caso anterior), la colección de parámetros de ruta, y la dirección de la conversión, que indica si la restricción se está evaluando durante el proceso de matching en una petición entrante, o durante la generación de una URL mediante los helpers que todos conocemos.

Y este era el mecanismo que teníamos hasta ahora para crear restricciones en las rutas de nuestras aplicaciones MVC y Web API. Aunque no era muy complicado, sí era bastante farragoso e incómodo, así que veamos cómo mejorar esto.

3. Restricciones incluidas “de serie” en MVC 5 y Web API 2

Independientemente de las novedades relativas a attribute routing que vienen ya de serie con ASP.NET MVC 5 y Web API 2, las últimas versiones de estos frameworks han venido acompañadas de un buen número de constraints predefinidas que permiten solucionar de forma muy rápida los escenarios más comunes.

Estas clases vienen definidas en System.Web.Mvc.Routing.Constraints en ASP.NET MVC y en System.Web.Http.Routing.Constraints en el caso de Web API y, como ya sabemos, todas ellas son clases que implementan el interfaz IRouteConstraint. Las disponibles son:
  • AlphaRouteConstraints, para permitir valores alfabéticos.
  • BoolRouteContraint, permite valores booleanos.
  • CompoundRouteConstraint, útil cuando queremos aplicar varias restricciones al mismo parámetro, pues actúa como contenedor de otras constraints que serán aplicadas de forma conjunta al mismo.
  • DateTimeRouteConstraint, para permitir valores de tipo fecha.
  • DecimalRouteConstraint, permitir valores decimales.
  • FloatRouteConstraint, permitir valores de tipo float.
  • GuidRouteConstraint, permitir exclusivamente valores Guid.
  • IntRouteConstraint, permitir valores enteros.
  • LengthRouteConstraint, para permitir parámetros con longitud comprendida entre los valores mínimo y máximo especificados, o de un tamaño exacto.
  • LongRouteConstraint, para permitir valores de tipo long .
  • MaxRouteConstraint, permite valores numéricos hasta el máximo indicado.
  • MaxLengthRouteConstraint, para permitir valores cuya longitud no supere la indicada.
  • MinLengthRouteConstraint, ídem, con valores de una longitud mínima.
  • MinRouteConstraint, permite valores numéricos mayores o iguales al indicado.
  • OptionalRouteConstraint, permite indicar una restricción que se superará si el valor suministrado coincide con el valor por defecto UrlParameter.Optional.
  • RangeRouteConstraint, permite valores numéricos que se encuentren en un rango determinado.
  • RegexRouteConstraint, los valores deben cumplir una expresión regular.
Así, a partir de este momento podríamos utilizar cualquiera de estas restricciones directamente en nuestro código para asegurar que a las acciones llegan los parámetros que nos interesan. En el ejemplo anterior quedaría algo así:
using System.Web.Mvc.Routing.Constraints;
[...]
routes.MapRoute(
    name: "ProductById",
    url: "product/{id}",
    defaults: new {controller = "product", action = "details"},
    constraints: new { id = new IntRouteConstraint() }
);
De nuevo, recordad que a la hora de aplicar estas restricciones debéis tener en cuenta el espacio de nombres usado, pues si incluis restricciones Web API en una ruta MVC no funcionarán, ni viceversa.

4. Especificación de restricciones en attribute routing

Attribute routing incluye su propia sintaxis para incluir restricciones en el interior de las definiciones de ruta que encontramos junto a las acciones o controladores. Lo vemos rápidamente con un ejemplo:
[Route("product/edit/{id:int}"] 
public ActionResult GetProductById(int id) { ... } 
Pues sí, basta con incluir la restricción tras el nombre del parámetro, separándolo por los dos puntos “:”. Sencillo, ¿eh?

Los tipos de restricción son más simples y rápidos de escribir que las clases donde se implementa, que son las que hemos citado algo más arriba. En este caso debemos usar las siguientes denominaciones:

Constraint Descripción Ejemplo de uso
alpha Secuencia de caracteres alfabéticos latinos (a-z, A-Z) {x:alpha}
bool Valor booleanos (true o false) {x:bool}
datetime Valor DateTime (en formato aaaa-mm-dd) {x:datetime}
decimal Valor decimal (usando el punto como separador decimal) {x:decimal}
double Valor en punto flotante de 64-bits {x:double}
float Valor en punto flotante de 64-bits {x:float}
guid GUID {x:guid}
int Valor entero de 32 bits {x:int}
length Cadena de caracteres con una longitud específica, o en un rango {x:length(6)}

{x:length(1,20)}
long Valor entero de 64 bits {x:long}
max Entero, como máximo el valor indicado {x:max(10)}
maxlength Cadena de caracteres con un tamaño máximo {x:maxlength(10)}
min Entero, como mínimo el valor indicado {x:min(10)}
minlength Cadena de caracteres con un tamaño mínimo {x:minlength(10)}
range Entero que se encuentre en un rango de valores {x:range(10,50)}
regex Valor que cumpla la expresión regular indicada {x:regex(^\d{3}-\d{3}-\d{4}$)}

Si un parámetro está sujeto a más de una restricción, podemos especificarlas de forma consecutiva, siempre usando el mismo separador:
[Route("user/confirm/{pin:alpha:length(4}"] // 4 alphabet chars (a-z)
public ActionResult Confirm(string pin) 
{ 
    ... 
}
Por otra parte, si el parámetro tuviera un valor por defecto, se indicará tras las constraints:
[Route("~/test/{data:alpha:length(3)=car}")] 
public ActionResult Test(string data) 
{ 
    ... 
} 
Y de momento vamos a dejarlo aquí, que creo que ya me estoy alargando demasiado. En la próxima entrega, la última  de la serie, veremos la extensibilidad de attribute routing, que permite crear nuevos tipos de restricciones e integrarlas en su sintaxis de forma muy limpia y elegante.

Publicado en Variable not found.

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