2010-08-07 16 views
5

Ho un po 'di tempo per capire come implementare correttamente il mio reindirizzamento 404.ASP.NET MVC - Usa Reflection per trovare se esiste un controller

Se uso il seguente

<HandleError()> _ 
Public Class BaseController : Inherits System.Web.Mvc.Controller 
''# do stuff 
End Class 

Poi ogni errore non gestito sulla pagina verrà caricata la "Error" Vista che funziona alla grande. http://example.com/user/999 (dove 999 è un ID utente non valido) genererà un errore mantenendo l'URL originale (questo è quello che voglio)

Tuttavia. Se qualcuno immette http://example.com/asdfjkl nell'URL (dove asdfjkl è un controller non valido), IIS sta lanciando la pagina 404 generica. (questo è non quello che voglio). Quello di cui ho bisogno è che la stessa cosa sopra sia applicata. L'URL originale rimane e viene caricato il controller "NotFound".

sto registrando miei percorsi come questo

Shared Sub RegisterRoutes(ByVal routes As RouteCollection) 
    routes.RouteExistingFiles = False 
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}") 
    routes.IgnoreRoute("Assets/{*pathInfo}") 
    routes.IgnoreRoute("{*robotstxt}", New With {.robotstxt = "(.*/)?robots.txt(/.*)?"}) 

    routes.AddCombresRoute("Combres") 

    routes.MapRoute("Start", "", New With {.controller = "Events", .action = "Index"}) 

    ''# MapRoute allows for a dynamic UserDetails ID 
    routes.MapRouteLowercase("UserProfile", "Users/{id}/{slug}", _ 
          New With {.controller = "Users", .action = "Details", .slug = UrlParameter.Optional}, _ 
          New With {.id = "\d+"} _ 
    ) 


    ''# Default Catch All MapRoute 
    routes.MapRouteLowercase("Default", "{controller}/{action}/{id}/{slug}", _ 
          New With {.controller = "Events", .action = "Index", .id = UrlParameter.Optional, .slug = UrlParameter.Optional}, _ 
          New With {.controller = New ControllerExistsConstraint}) 

    ''# Catch everything else cuz they're 404 errors 
    routes.MapRoute("CatchAll", "{*catchall}", _ 
        New With {.Controller = "Error", .Action = "NotFound"}) 

End Sub 

Avviso il ControllerExistsConstraint? Quello che devo fare è usare Reflection per scoprire se esiste o meno un controller.

Qualcuno può aiutarmi a compilare gli spazi vuoti?

Public Class ControllerExistsConstraint : Implements IRouteConstraint 

    Public Sub New() 
    End Sub 

    Public Function Match(ByVal httpContext As System.Web.HttpContextBase, ByVal route As System.Web.Routing.Route, ByVal parameterName As String, ByVal values As System.Web.Routing.RouteValueDictionary, ByVal routeDirection As System.Web.Routing.RouteDirection) As Boolean Implements System.Web.Routing.IRouteConstraint.Match 


     ''# Bah, I can't figure out how to find if the controller exists 


End Class 

Vorrei anche conoscere le implicazioni sulle prestazioni di questa ... come pesante performance è la riflessione? Se è troppo, c'è un modo migliore?

risposta

-1

Perché non basta catturare con gli errori personalizzati nel file web.config ed evitare un gruppo di riflessione tutti insieme?

<customErrors mode="On"> 
    <error statusCode="404" redirect="/Error/NotFound" /> 
</customErrors> 
+0

perché nella mia domanda ho detto "L'URL originale rimane e il controller" NotFound "è caricato.". ** NON desidero reindirizzare a una pagina non trovata ** –

10

Ho una soluzione C#, spero che sia d'aiuto. Ho plagiato un po 'di questo codice, anche se per la vita di me, non riesco a trovare da dove l'ho preso. Se qualcuno lo sa, per favore fatemelo sapere così posso aggiungerlo ai miei commenti.

Questa soluzione non utilizza il riflesso, ma esamina tutti gli errori dell'applicazione (eccezioni) e verifica se si tratta di un errore 404. Se lo è, inoltra semplicemente la richiesta corrente a un altro controller. Anche se non sono un esperto in alcun modo, penso che questa soluzione potrebbe essere più veloce della riflessione. Comunque, ecco la soluzione e va nella vostra Global.asax.cs,

protected void Application_Error(object sender, EventArgs e) 
    { 
     Exception exception = Server.GetLastError(); 

     // A good location for any error logging, otherwise, do it inside of the error controller. 

     Response.Clear(); 
     HttpException httpException = exception as HttpException; 
     RouteData routeData = new RouteData(); 
     routeData.Values.Add("controller", "YourErrorController"); 

     if (httpException != null) 
     { 
      if (httpException.GetHttpCode() == 404) 
      { 
       routeData.Values.Add("action", "YourErrorAction"); 

       // We can pass the exception to the Action as well, something like 
       // routeData.Values.Add("error", exception); 

       // Clear the error, otherwise, we will always get the default error page. 
       Server.ClearError(); 

       // Call the controller with the route 
       IController errorController = new ApplicationName.Controllers.YourErrorController(); 
       errorController.Execute(new RequestContext(new HttpContextWrapper(Context), routeData)); 
      } 
     } 
    } 

Quindi il controller sarebbe,

public class YourErrorController : Controller 
{ 
    public ActionResult YourErrorAction() 
    { 
     return View(); 
    } 
} 
+0

Sebbene ** NON ** una risposta alla domanda. Questo ** FA ** risolve il mio problema. Assegnerò la taglia, ma non segnerò come risposta. –

+0

Probabilmente hai ragione riguardo al bit "più veloce del riflesso". Questo è bello perché non devo chiamare il mio 'ControllerExistsConstraint 'tutto il tempo. –

+0

Non ho notato che hai aperto un altro post relativo alla domanda. Avrei dovuto rispondere a quello invece. Forse puoi collegare la soluzione dell'altro a qui. Sei troppo gentile con i punti di merito :). –

2

Questo è un problema molto simile to mine, ma mi piace il tuo approccio alternativo.

credo che la riflessione da un filtro dinamico potrebbe essere troppo prestazioni pesante, ma penso di avere un modo migliore - è possibile filtrare le azioni consentite da un Regex:

// build up a list of known controllers, so that we don't let users hit ones that don't exist 
var allMvcControllers = 
    from t in typeof(Global).Assembly.GetTypes() 
    where t != null && 
     t.IsPublic && 
     !t.IsAbstract && 
     t.Name.EndsWith("Controller", StringComparison.OrdinalIgnoreCase) && 
     typeof(IController).IsAssignableFrom(t) 
    select t.Name.Substring(0, t.Name.Length - 10); 

// create a route constraint that requires the controller to be one of the reflected class names 
var controllerConstraint = new 
{ 
    controller = "(" + string.Join("|", allMvcControllers.ToArray()) + ")" 
}; 

// default MVC route 
routes.MapRoute(
    "MVC", 
    "{controller}/{action}/{id}", 
    new { action = "Index", id = UrlParameter.Optional }, 
    controllerConstraint); 

// fall back route for unmatched patterns or invalid controller names 
routes.MapRoute(
    "Catch All", 
    "{*url}", 
    new { controller = "System", action = "NotFound" }); 

Poi aggiungere a questo un ulteriore metodo su mia base Controller:

protected override void HandleUnknownAction(string actionName) 
{ 
    this.NotFound(actionName).ExecuteResult(this.ControllerContext); 
} 

In questo caso BaseController.NotFound gestisce l'azione mancante su un controller valido.

Così alla fine:

  • {site}/invalid - trovata da nuovo filtro basato riflessione
  • {site}/valid/notAnAction - trovati dai HandleUnknownAction
  • {site}/valid/action/id - trovati dai controlli nel codice per l'ID (come prima)
  • {site}/valid/action/id/extraPath - trovato non corrispondente a nessuna rotta ma cattura tutto

Penso che siano tutti gli scenari 404 coperti :-)

Problemi correlati