Las rutas que utilizo en este blog MVC5


Publicado el sábado, 25 de febrero de 2017


Uno de los retos que tenía al empezar a programar el blog era el de conseguir crear unas rutas basadas en la fecha y seguidas de palabras clave (slug).

Por ejemplo: http://albertcapdevila.net/21/1/2017/subir-imagenes-mvc5

A priori parece sencillo pero a mí en su momento no me lo pareció.

En esta entrada explicaré como configuré las rutas del blog.

Routing por defecto en MVC5

La plantilla MVC por defecto del Visual Studio tiene la siguiente estructura de carpetas:

En el archivo Global.asax, que es el punto de inicio de la aplicación, se llama a un método estático "RegisterRoutes". Este método es el encargado de configurar las rutas de la aplicación:

    public class MvcApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            BundleConfig.RegisterBundles(BundleTable.Bundles);
        }
    }

La implementación de "RegisterRoutes" se encuentra dentro de la carpeta App_Start > RouteConfig.cs

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

            routes.MapRoute(
                name: "Default",
                url: "{controller}/{action}/{id}",
                defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
            );
        }

Esta función define que cualquier ruta de la web debe tener una estructura del tipo "{controller}/{action}/{id}".

Por ejemplo: http://albertcapdevila.net/Posts/Edit/11

Donde "Posts" es el nombre del controlador, "Edit" es el nombre de la acción y "11" corresponde al parámetro "Id" que recibe la acción.

Los valores del parámetro "defaults:" indican qué controlador y qué acción se debe utilizar por defecto en caso de que no se indique en la ruta.

Así, cuando alguien entra al blog por primera vez http://albertcapdevila.net/, dado que no se indica ni el controlador ni la acción, se ejecuta la acción "Index" de controlador "HomeController".

Utilizando el Routing

Para completar el Routing que MVC5 trae por defecto faltan dos ingredientes más:

1. Crear links de navegación en las páginas 

2. Crear acciones en controladores que reciban la solicitud

Para crear links en una página lo más habitual es utilizar helpers. Por ejemplo, en una vista html podemos crear un menú con diferentes links:

Estas cuatro maneras diferentes de crear un link generan el mismo resultado:

Todas generarán una petición (request) de tipo Get que será recogida por la siguiente acción:

    public class HomeController : Controller
    {
        public ActionResult Contact()
        {
            ViewBag.Message = "Your contact page.";

            return View();
        }
    }

Configuración de rutas del blog

Con las rutas por defecto que trae la plantilla de MVC5 no es posible generar rutas basadas en la fecha de publicación de cada entrada.

Por ello, hay que Mapear un nuevo tipo de ruta:

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

            routes.MapRoute(
              name: "BlogPost",
              url: "{dia}/{mes}/{anyo}/{urlSlug}",
              defaults: new { controller = "Blog", action = "Detalles" },
              constraints: new { dia = @"\d{1,2}", mes = @"\d{1,2}", anyo = @"\d{4}" }
          );


            routes.MapRoute(
                name: "Default",
                url: "{controller}/{action}/{id}",
                defaults: new { controller = "Blog", action = "Index", id = UrlParameter.Optional }
            );
        }

Es necesario situar la ruta "BlogPost" antes que la "Default" puesto que el sistema de detección de rutas busca una ruta válida siguiendo un orden. La primera ruta que cumpla con las especificaciones es la que devuelve como válida.

Por lo tanto siempre hay que poner la configuración de rutas más específicas primero.

Los cuatro parámetros de la configuración de la ruta son importantes:

  1. name: el nombre de la ruta. Lo utilizaré mas tarde para crear links que usen esta ruta.
  2. url: estructura de la ruta. 
  3. defaults: indica el controlador y la acción por defecto de la ruta. Así este tipo de rutas siempre irán a parar a la misma acción "Detalles" del controlador "Blog"
  4. constraintsson reglas de validación para los parámetros de la ruta. En este caso indico que el día y el mes sean números enteros de uno o dos dígitos, mientras que el año debe ser un número de cuatro dígitos. Cualquier ruta que no cumpla estas condiciones no se considerará válida.

Utilizando el Routing del blog

La página inicial de este blog contiene una listado "resumen" de entradas. Al hacer clic sobre una entrada se navega utilizando el sistema configurado previamente:

Para ello, he creado un helper con una llamada estática RutaUrlBlogPost. Esta llamada recibe la fecha del Post y el Slug:


namespace Blog.Web.Helpers
{
    public static class UrlHelperExtension
    {
        public static string RutaUrlBlogPost(this UrlHelper url, DateTime fechaPost, string urlSlug)
        {
            var result = url.RouteUrl("BlogPost",
                new
                {
                    dia = fechaPost.Day,
                    mes = fechaPost.Month,
                    anyo = fechaPost.Year,
                    urlSlug
                }, url.RequestContext.HttpContext.Request.Url.Scheme);

            return result;
        }
      }
}

Finalmente, todas las rutas que utilizen esta estructura irán a parar a la misma acción de Controllers > BlogController y mostrarán la pantalla Detalle de la entrada.


        public async Task<ActionResult>Detalles(int dia, int mes, int anyo, string urlSlug)
        {
            if (string.IsNullOrEmpty(urlSlug))
                return new HttpStatusCodeResult(HttpStatusCode.BadRequest);

            var fechaPost = GenerarFecha(dia, mes, anyo);

            if(fechaPost == null)
                return new HttpStatusCodeResult(HttpStatusCode.BadRequest);

            var post = await RecuperarPost(fechaPost.Value, urlSlug);

            if (post == null)
            {
                return HttpNotFound();
            }
            return View(post);
        }

       

Esto que he mostrado es todo lo que utilizo para la configuración de las rutas en este blog. Por ahora no he tenido la necesidad de particularizar otras rutas. En MVC5 existe otra posibilidad para configurar rutas: el RouteAttribute pero por ahora nunca lo he necesitado.