> Manuales > Manual del framework ASP.NET MVC

La tabla de rutas en el framework ASP.NET MVC sirve para mapear las URLs de la aplicación a sus correspondientes controladores y acciones dentro de ellos.

En estos primeros cuatro capítulos de esta serie sobre ASP.NET MVC hemos visto los aspectos fundamentales del framework: controladores, acciones y vistas. En el capítulo dos, vimos que cada petición del navegador debe ser enrutada a una acción de un controlador. Dijimos también que por defecto se sigue la convención de que las URLs están en forma http://servidor/controlador/accion.

Lo que mucha gente no sabe es que eso no es realmente una convención del framework de ASP.NET MVC ni mucho menos una obligación. Que las URLs sigan por defecto esta forma se debe, ni más ni menos, al código que nos genera por defecto Visual Studio cuando creamos un proyecto ASP.NET MVC. La responsable de decidir qué acción de qué controlador se encarga de procesar cada petición es la tabla de rutas y es de lo que vamos a hablar hoy.

Hablando rápidamente podríamos decir que la tabla de rutas es la responsable de mapear cada URL a una acción de un controlador. La frase puede parecer baladí, pero no lo es en absoluto. Hay dos aspectos clave a tener en cuenta:

  1. Cada URL debe ser mapeada a una acción de un controlador en concreto.
  2. Para mapear una petición del navegador a una acción de un controlador se usa sólo la URL. Repito: sólo la URL.
Hablando en propiedad, la frase anterior no es del todo precisa. Lo que la tabla de rutas realmente hace es determinar qué valores de ruta (route values) se rellenan a partir de la URL.

Lo que nos lleva a preguntarnos, que son los valores de ruta.

Valores de ruta (route values)

Los valores de ruta no son más que un conjunto de valores (parámetros si preferís) cuyo valor se extrae a partir de la petición que realiza el navegador, concretamente a partir de la URL. Puede haber todos los valores de ruta que se quiera, pero hay dos que deben ser establecidos siempre por cada URL:
  1. controller: Debe contener el nombre del controlador que gestionará la petición
  2. action: Debe contener el nombre de la acción que gestionará la petición
Esos dos valores los espera el framework siempre, para así poder enrutar dicha petición hacia una acción de un controlador. Pero al margen de esos dos, pueden existir otros valores de ruta. Y ¿qué hace el framework con el resto de valores de ruta? Pues los guarda y los envía a la acción que gestiona la petición.

La tabla de rutas es pues la responsable de, decidir, por cada URL, que valores de ruta, y con qué valor real, se rellenan. Recordad que controller y action son obligatorios y que son usados por el framework para, precisamente, decidir qué acción de qué controlador gestiona la petición, de ahí que se diga comúnmente, que la tabla de rutas mapea URLs a acciones, aunque como hemos visto, realmente hace algo más.

Configuración de la tabla de rutas

La tabla de rutas no tiene valores por defecto. Es decir, inicialmente está vacía y eso significa que no hay ninguna petición que se pueda enrutar. Es el propio Visual Studio, quien nos genera código para configurar la tabla de rutas, con unos valores iniciales. Y esos valores son los que hacen que las URLs tengan la forma conocida de http://servidor/controlador/accion. Si abrís Visual Studio y creáis un proyecto ASP.NET MVC nuevo (incluso si elegís la opción de Empty), veréis el siguiente código en el archivo Global.asax.cs:

public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
}

Este código es el que configura la tabla de rutas. El parámetro routes que recibe este método es la propia tabla de rutas, que está en la propiedad estática Routes de la clase RouteTable.

Analicemos este código, y empecemos no por la primera línea, sino por la segunda: la que llama al método MapRoute. Este método (que es realmente un método de extensión, aunque esto no sea relevante) nos permite añadir una nueva entrada a la tabla de rutas de forma sumamente sencilla. Está sobrecargado pero en este código los parámetros que recibe son:

  1. El nombre de la ruta (un identificador de la ruta). En este caso la ruta se llama Default
  2. Las URLs que mapea esta ruta
  3. Los valores por defecto de los valores de ruta, en caso de no ser encontrados en la URL.

Patrones de URLs

Centrémonos un poco en segundo parámetro. Su valor es “{controller}/{action}/{id}”. Eso es simplemente el patrón que deben cumplir las URLs para ser procesadas por esta ruta. Lo que está entre llaves es el nombre del valor de ruta que se crea. Así pues el patrón {controller}/{action}/{id} mapeará cualquier URL que esté en la forma http://servidor/xxx/yyy/zzz. Y además asignará los siguientes valores de ruta:
  1. controller = xxx
  2. action = yyy
  3. id = zzz
Pero… que pasa con una URL que no tenga el /zzz final? Que ocurre con una URL http://servidor/xxx/yyy? Pues en principio una URL de este tipo no sería procesada por este patrón (ya que el patrón pide explícitamente que haya {controller}/{action}/{id}. Pero, para evitar que la configuración de la tabla de rutas costase horrores, existe el tercer parámetro: los valores por defecto.

Valores por defecto

El tercer parámetro de MapRoute son los valores por defecto de los valores de ruta. Es decir, si el valor de ruta no puede sacarse a partir del patrón de URL especificado, se asumirá dicho valor. Se define como un objeto anónimo cuyas propiedades tienen el nombre de los valores de ruta y el valor de dicha propiedad es el valor por defecto del valor de ruta. En nuestro caso está definido como:

new { controller = "Home", action = "Index", id = UrlParameter.Optional }

Por lo tanto tenemos que:

Visto esto, ahora podemos ver que:
  1. http://servidor/Books/View/10 se procesará y los valores de ruta serán
    1. controller = Books
    2. action = View
    3. id = 10
  2. http://servidor/Books se procesará y los valores de ruta serán
    1. controller = Books
    2. action = Index (valor por defecto)
    3. id = No existirá el valor de ruta “id”
  3. http://servidor se procesará y los valores de ruta serán
    1. controller = Home (valor por defecto)
    2. action = Index (valor por defecto)
    3. id = No existirá el valor de ruta “id”
  4. http://servidor/Books/View/10/20 No será procesada por la tabla de rutas. Esta URL no puede mapearse al patrón {controller}/{action}/{id}
Si una URL no puede ser procesada por la tabla de rutas, el framework devuelve un error http 404 (página no encontrada).

Múltiples patrones

La tabla de rutas se llama precisamente tabla porque puede contener varias entradas (es decir, varios patrones de URL, con sus parámetros por defecto, etc). Para añadir más entradas (rutas, cada entrada se conoce como ruta), lo más sencillo es añadir llamadas a MapRoute. P.ej. si quisiéramos procesar la URL anterior http://servidor/Books/View/10/20 podríamos añadir una entrada adicional:

routes.MapRoute(
"DosIds", // Route name
"{controller}/{action}/{id}/{id2}"
);

Ahora la URL http://servidor/Books/View/10/20 ya puede ser procesada, y será procesada por esa entrada nueva en la tabla de rutas. Los valores de ruta creados serán: Un tema importante es que el orden de las rutaas en la tabla de rutas importa. Por cada URL, el framework evaluará las distintas entradas de la tabla de rutas, una a una, en el orden en que estas se encuentren y tan buen punto una URL pueda ser procesada, se eligirá esa entrada de la tabla de rutas. P.ej. supongamos que queremos mapear las URLs de la forma http://servidor/Ver/Edu a la acción View del controlador Profile con un valor de ruta llamado user cuyo valor sea lo que hay después de Ver (Edu en este caso). Eso lo podemos conseguir con una entrada en la tabla de rutas:

routes.MapRoute(
"ViewProfile", // Nombre de la ruta
"Ver/{author}", // URL with parameters
new { controller = "Profile", action = "View" }
);

Un detallito a tener en cuenta de esta nueva entrada es que dado que no hay lugar en el patrón de URL para los valores de ruta de controller y action, al ser esos obligatorios, deben especificarse como valores por defecto.

Esta entrada mapea una URL del tipo: http://servidor/Ver/Edu con los valores de ruta:

Pero, podéis comprobar que si añadís esa línea después del routes.MapRoute que ya había, si entráis una URL del tipo http://servidor/Ver/Edu el framework os devolverá un 404, incluso aunque tengáis el controlador Profile con una acción View definida.

¿Por qué?

Pues simplemente porque la tabla de rutas se evalúa en orden. Y ¿puede mapear la primera entrada (la ruta llamada Default) una URL del tipo http://servidor/Ver/Edu? La respuesta es que sí, y los valores de ruta quedan establecidos a:

Por lo tanto, a no ser que tengáis un controlador llamado Ver con una acción llamada Edu (cosa poco probable, no nos vamos a engañar :p) el framework os devolverá un 404. Para que todo funcione, la entrada “Default” debe estar después de la nueva entrada:

public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

routes.MapRoute(
"ViewProfile", // Nombre de la ruta
"Ver/{author}", // URL with parameters
new { controller = "Profile", action = "View" }
);
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new {controller = "Home", action = "Index", id = UrlParameter.Optional} // Parameter defaults
);
}

Ignorar patrones de URL

A veces es necesario que ciertas peticiones con una determinada URL, no sean gestionadas por el framework de ASP.NET sino por el propio motor de ASP.NET o bien el propio IIS. Un ejemplo podría ser una petición a una imagen. En este caso, no vamos a querer un controlador que nos devuelva la imagen, es mucho mejor dejar que sea el propio IIS quien lo haga (será mucho más eficiente). Por ello el framework permite definir rutas de exclusión, es decir rutas que si mapean una determinada URL, dicha petición será ignorada por ASP.NET MVC.

Para crear esas rutas especiales, se usa el método IgnoreRoute. Se le suele pasar un solo argumento, que es el patrón de URL a ignorar. Un ejemplo lo tenemos en el propio código que genera Visual Studio:

routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

Este patrón de URL se mapeará a todas aquellas URLs que tengan la forma http://servidor/xxx.axd/yyy
Dónde:

  1. xxx es cualquier nombre
  2. yyy es cualquier cosa, incluyendo barras separadoras (/). El hecho de que yyy pueda incluir barras separadoras es porque se usa la forma {*nombre_valor_ruta} (con un asterisco) que es lo que se conoce como catch-all y significa literalmente: captura todos los caracteres de la URL que vengan a partir de ahora.
Es decir la URL http://servidor/trace.axd/foo/bar/baz será enrutada por esta ruta. Al ser declarada con IgnoreRoute, lo que hará es que dicha petición sea ignorada por ASP.NET MVC y será procesada por alguien más (en este caso el propio motor de ASP.NET, pero eso ya depende de cada caso).

En el siguiente artículo vamos a ver los valores de ruta y los controladores.

Eduard Tomàs

Apasionado de la informática, los videojuegos, rol y... la cerveza. Key Consulta...

Manual