2012-07-15 17 views
6

Vorrei creare slug personalizzati per le pagine nel mio CMS, in modo che gli utenti possano creare i propri URL SEO (come Wordpress).ASP.NET MVC: instradamento di slug personalizzati senza influire sulle prestazioni

Ho usato per fare questo in Ruby su Rails e framework PHP "abusando" della route 404. Questa route è stata chiamata quando non è stato possibile trovare il controller richiesto, consentendo di indirizzare l'utente al mio controller di pagine dinamiche per analizzare lo slug (da dove li ho reindirizzati al 404 reale se non è stata trovata alcuna pagina). In questo modo il database è stato interrogato solo per verificare lo slug richiesto.

Tuttavia, in MVC il percorso catch-all viene chiamato solo quando la rotta non si adatta alla route predefinita di /{controller}/{action}/{id}.

di essere ancora in grado di analizzare le lumache personalizzati ho modificato il file 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(); 
    } 
} 

Questo risolve il mio problema, ma richiede la tabella Pages di essere completamente interrogato per ogni richiesta. Poiché un metodo di visualizzazione sovraccarico (public ViewResult Show(Page p)) non ha funzionato, devo anche recuperare la pagina una seconda volta perché posso solo passare l'ID della pagina.

  1. C'è un modo migliore per risolvere il mio problema?
  2. È possibile passare l'oggetto Pagina al metodo Show anziché all'ID pagina?
+2

Non è inizializzato solo all'avvio dell'applicazione? Solo una nota a margine: 'db.Dispose();'? Edit: Scusa non stavo leggendo la tua domanda molto bene. Forse potresti mettere le pagine nella Global Cache? – Silvermind

+0

Grazie per aver puntato nella giusta direzione! La funzione è infatti solo richiamata all'avvio. Immagino che lo stavo guardando come se fosse un linguaggio interpretato (come PHP). Considerando che questo codice viene eseguito solo all'avvio, suppongo che l'impatto sulle prestazioni sia trascurabile. Tuttavia, non sono ancora sicuro se questa è la strada da percorrere, o se questo è già realizzabile utilizzando la funzionalità integrata. Mi sto anche chiedendo se è possibile passare il modello invece dell'ID (domanda n.2). – christiaanderidder

risposta

2

Anche se il vostro codice di registrazione percorso funziona come è, il problema sarà che i percorsi sono registrati staticamente solo all'avvio. Cosa succede quando viene aggiunto un nuovo post: dovresti riavviare il pool di app?

È possibile registrare un percorso che contiene la parte slug SEO del proprio URL e quindi utilizzare lo slug in una ricerca.

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: codice non compilato né testato - non hanno avuto il mio ambiente dev disponibile in questo momento Trattarlo come pseudocodice;.)

Questa implementazione avrà una ricerca per il riavvio di slug per server. Puoi anche precompilare la cache slug-to-id dei valori-chiave all'avvio, in modo che tutte le ricerche di pagina esistenti siano a buon mercato.

+0

Bella soluzione, ma sto cercando di eliminare la/Page/part. Se devo solo indirizzare tutte le richieste a un controller e controllare se il nome richiesto esiste già come controller, ho cercato di evitare in questo modo perché significa che non utilizzo alcun routing integrato nei controller. Tuttavia, se questo è l'unico modo per ottenere la mia riscrittura, MVC fornisce un modo per cercare i controller esistenti? – christiaanderidder

+0

@christiaanderidder: se si aggiunge il percorso per ultimo (sì, l'ordine conta), come 'url: "{slug}" ', agirà fondamentalmente come il solito 404 hack. –

+1

@christiaanderidder: se si implementa un custom ['IRouteConstraint'] (http://msdn.microsoft.com/en-us/library/system.web.routing.irouteconstraint.aspx), invece, è possibile eseguire la ricerca slug e solo 'return false' se non' '.Match (...)'. Mi piace questa [implementazione validabile testabile] (http://stackoverflow.com/a/9019603/). –

0

Ho modificato la mia risposta per dare una risposta più completa alle vostre domande:

risposta alla domanda 1:

percorsi Registrarsi è inizializzate all'avvio. (Forse anche quando il Application Pool ricicla, è altamente probabile.) Penso anche che non ci sia nulla di sbagliato nel tuo approccio poiché si verifica solo una volta. Faccio la stessa cosa interrogando tutte le lingue supportate dal database per registrarle come/TwoLetterISOLanguageName (/ nl,/en,/de, ecc.).

risposta alla domanda 2:

questo dovrebbe funzionare il superamento di un modello: da mettere prima di percorso Default!

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

Il ContactController:

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

Il Modello:

public class MyModel 
{ 
    public string Name { get; set; } 
} 
Problemi correlati