2012-07-15 27 views
6

Me gustaría crear babosas personalizadas para páginas en mi CMS, para que los usuarios puedan crear sus propias URL de SEO (como Wordpress).ASP.NET MVC: enrutamiento de babosas personalizadas sin afectar el rendimiento

Solía ​​hacer esto en los frameworks de Ruby on Rails y PHP "abusando" de la ruta 404. Se llamó a esta ruta cuando no se pudo encontrar el controlador solicitado, lo que me permitió enrutar al usuario a mi controlador de páginas dinámicas para analizar el slug (desde donde los redirigí al 404 real si no se encontró ninguna página). De esta forma, solo se consultó a la base de datos para verificar el slug solicitado.

Sin embargo, en MVC, la ruta catch-all solo se invoca cuando la ruta no se ajusta a la ruta predeterminada de /{controller}/{action}/{id}.

todavía ser capaz de analizar las babosas personalizados he modificado el archivo RouteConfig.cs:

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

     routes.MapHttpRoute(
      name: "DefaultApi", 
      routeTemplate: "api/{controller}/{id}", 
      defaults: new { id = RouteParameter.Optional } 
     ); 

     RegisterCustomRoutes(routes); 

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

    public static void RegisterCustomRoutes(RouteCollection routes) 
    { 
     CMSContext db = new CMSContext(); 
     List<Page> pages = db.Pages.ToList(); 
     foreach (Page p in pages) 
     { 
      routes.MapRoute(
       name: p.Title, 
       url: p.Slug, 
       defaults: new { Controller = "Pages", Action = "Show", id = p.ID } 
      ); 
     } 
     db.Dispose(); 
    } 
} 

Esto resuelve mi problema, pero requiere la tabla Pages estar plenamente consultada para cada solicitud. Debido a que un método de show sobrecargado (public ViewResult Show(Page p)) no funcionó, también tengo que recuperar la página una segunda vez porque solo puedo pasar la identificación de la página.

  1. ¿Hay una mejor manera de resolver mi problema?
  2. ¿Es posible pasar el objeto Página a mi método Show en lugar de la ID de página?
+2

¿No se inicializó solo al inicio de la aplicación? Solo en una nota al margen: 'db.Dispose();'? Editar: Lo siento, no estaba leyendo tu pregunta muy bien. ¿Quizás podría poner las páginas en la memoria caché global? – Silvermind

+0

¡Gracias por apuntar en la dirección correcta! La función solo se llama al inicio. Creo que lo estaba viendo como si fuera un lenguaje interpretado (como PHP). Considerando que el código solo se ejecuta al inicio, creo que el impacto en el rendimiento es insignificante. Sin embargo, todavía no estoy seguro de si este es el camino a seguir, o si esto ya se puede lograr mediante el uso de la funcionalidad incorporada. También me pregunto si es posible pasar el Modelo en lugar de la ID (Pregunta nº 2). – christiaanderidder

Respuesta

2

Incluso si su código de registro de rutas funciona como está, el problema será que las rutas están registrados estáticamente única en el arranque. ¿Qué sucede cuando se agrega una nueva publicación? ¿Tendría que reiniciar el grupo de aplicaciones?

Puede registrar una ruta que contenga la parte de slug de su URL y luego utilizar la barra en una búsqueda.

RouteConfig.cs

routes.MapRoute(
    name: "SeoSlugPageLookup", 
    url: "Page/{slug}", 
    defaults: new { controller = "Page", 
        action = "SlugLookup", 
        }); 

PageController.cs

public ActionResult SlugLookup (string slug) 
{ 
    // TODO: Check for null/empty slug here. 

    int? id = GetPageId (slug); 

    if (id != null) {  
     return View ("Show", new { id }); 
    } 

    // TODO: The fallback should help the user by searching your site for the slug. 
    throw new HttpException (404, "NotFound"); 
} 

private int? GetPageId (string slug) 
{ 
    int? id = GetPageIdFromCache (slug); 

    if (id == null) { 
     id = GetPageIdFromDatabase (slug); 

     if (id != null) { 
      SetPageIdInCache (slug, id); 
     } 
    } 

    return id; 
} 

private int? GetPageIdFromCache (string slug) 
{ 
    // There are many caching techniques for example: 
    // http://msdn.microsoft.com/en-us/library/dd287191.aspx 
    // http://alandjackson.wordpress.com/2012/04/17/key-based-cache-in-mvc3-5/ 
    // Depending on how advanced you want your CMS to be, 
    // caching could be done in a service layer. 
    return slugToPageIdCache.ContainsKey (slug) ? slugToPageIdCache [slug] : null; 
} 

private int? SetPageIdInCache (string slug, int id) 
{ 
    return slugToPageIdCache.GetOrAdd (slug, id); 
} 

private int? GetPageIdFromDatabase (string slug) 
{ 
    using (CMSContext db = new CMSContext()) { 
     // Assumes unique slugs. 
     Page page = db.Pages.Where (p => p.Slug == requestContext.Url).SingleOrDefault(); 

     if (page != null) { 
      return page.Id; 
     } 
    } 

    return null; 
} 

public ActionResult Show (int id) 
{ 
    // Your existing implementation. 
} 

(FYI: El código no compilado ni probado - no han conseguido mi entorno de desarrollo disponibles en este momento Tratarlo como pseudocódigo;).

Esta implementación tendrá una búsqueda para el slug por reinicio del servidor. También puede rellenar previamente la memoria caché slug-to-id de valor-clave al inicio, por lo que todas las búsquedas de página existentes serán baratas.

+0

Buena solución, pero estoy tratando de deshacerme de/Page/part. ¿Debo simplemente enrutar todas las solicitudes a un controlador y verificar si el nombre solicitado ya existe como controlador allí? Intenté evitarlo porque significa que no uso el enrutamiento integrado para los controladores. Sin embargo, si esta es la única manera de lograr mi reescritura, ¿MVC proporciona una manera de buscar los controladores existentes? – christiaanderidder

+0

@christiaanderidder: si agrega la última ruta (sí, el orden es importante), como 'url:" {slug} "', básicamente actuará como el hack habitual 404. –

+1

@christiaanderidder: si implementa un ['IRouteConstraint'] personalizado (http://msdn.microsoft.com/en-us/library/system.web.routing.irouteconstraint.aspx), puede realizar la búsqueda de slug y simplemente 'return false' si no' .Match (...) '. Me gusta esto [implementación comprobable del validador] (http://stackoverflow.com/a/9019603/). –

0

He editado mi respuesta para dar una respuesta más completa a sus preguntas:

respuesta a la pregunta 1:

rutas Registro se inicializa en el arranque. (Quizás también cuando se recicla el Application Pool, es muy probable). También creo que no hay nada de malo en su enfoque, ya que solo se produce una vez. Hago lo mismo consultando todos los idiomas admitidos desde la base de datos para registrarlos como/TwoLetterISOLanguageName (/ nl,/en,/de, etc.).

respuesta a la pregunta 2:

Esto debería funcionar pasar de un modelo: Póngalo antes de la ruta Default!

routes.MapRoute(
    name: "Contact", 
    url: "contact/{action}", 
    defaults: new { controller = "Contact", 
        action = "Index", 
        MyModel = new MyModel { Name = "hello" } }); 

El ContactController:

public ActionResult Index(MyModel mymodel) 
{ 
    return Content(mymodel.Name); 
} 

El Modelo:

public class MyModel 
{ 
    public string Name { get; set; } 
} 
Cuestiones relacionadas