2010-08-07 14 views
5

Me está costando mucho tiempo encontrar la manera de implementar correctamente mi redireccionamiento 404.ASP.NET MVC - Use Reflection para encontrar si existe un controlador

Si utilizo el siguiente

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

Entonces, cualquier error no controlado en la página se carga hasta la vista "error", que funciona muy bien. http://example.com/user/999 (donde 999 es un usuario no válido de identificación) generará un error mientras se mantiene la URL original (esto es lo que quiero)

Sin embargo. Si alguien ingresa http://example.com/asdfjkl en la url (donde asdfjkl es un controlador inválido), entonces IIS lanza la página 404 genérica. (esto es no lo que quiero). Lo que necesito es lo mismo que se aplica arriba. La URL original permanece y se carga el controlador "NotFound".

estoy registrar mis rutas como esta

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 

Aviso del ControllerExistsConstraint? Lo que tengo que hacer es usar Reflection para descubrir si existe o no un controlador.

¿Alguien me puede ayudar a llenar los espacios en blanco?

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 

También me gustaría saber las implicaciones de rendimiento de este ... cómo el rendimiento es pesado ¿Reflexión? Si es demasiado, ¿hay una mejor manera?

Respuesta

-1

¿Por qué no se les capture con errores personalizados en el archivo web.config y evitar un montón de reflexión total?

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

porque en mi pregunta dije "La URL original permanece, y el controlador" NotFound "está cargado.". ** NO deseo redireccionar a una página no encontrada ** –

10

Tengo una solución C#, espero que ayude. Hice plagio de parte de este código, aunque por mi vida, no puedo encontrar de dónde lo saqué. Si alguien sabe, házmelo saber para que pueda agregarlo a mis comentarios.

Esta solución no utiliza el reflejo, pero revisa todos los errores de la aplicación (excepciones) y comprueba si se trata de un error 404. Si es así, simplemente enruta la solicitud actual a un controlador diferente. Aunque no soy un experto de ninguna manera, creo que esta solución podría ser más rápida que la reflexión. De todos modos, aquí está la solución y esto va a su 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)); 
      } 
     } 
    } 

Entonces el controlador sería,

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

Aunque ** NO ** sea una respuesta a la pregunta. Esto ** SÍ ** resuelve mi problema. Voy a otorgar la recompensa, pero no marque como respuesta. –

+0

Probablemente tenga razón acerca del bit "más rápido que la reflexión". Esto es bueno porque no tengo que llamar mi 'ControllerExistsConstraint' todo el tiempo. –

+0

No noté que tenía otra publicación abierta con respecto a la pregunta. Debería haber respondido ese en su lugar. Quizás puedas vincular la solución del otro aquí. Eres demasiado agradable con los puntos de recompensa :). –

2

Eso es un problema muy similar to mine, pero me gusta su enfoque alternativo.

creo que la reflexión como un filtro dinámico podría ser demasiado rendimiento pesado, pero creo que tengo una mejor manera - puede filtrar las acciones permitidas por una expresión regular:

// 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" }); 

Luego añadir a esto un adicional método en mi base Controller:

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

en este caso BaseController.NotFound maneja la acción que falta en un controlador válido.

Así que finalmente:

  • {site}/invalid - encontrado por el nuevo filtro basado en la reflexión
  • {site}/valid/notAnAction - encontrado por HandleUnknownAction
  • {site}/valid/action/id - encontrado por los cheques en código para la identificación (como antes)
  • {site}/valid/action/id/extraPath - encontrado por que no coincida con ninguna ruta, pero la captura de todo

Creo que son todos los escenarios 404 cubiertos :-)

Cuestiones relacionadas