2010-08-07 12 views
5

Je passe mon temps à trouver comment implémenter correctement ma redirection 404.ASP.NET MVC - Utilisation de la réflexion pour trouver si un contrôleur existe

Si j'utilise les éléments suivants

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

Ensuite, toute erreur non gérée sur la page va charger la vue « erreur » qui fonctionne très bien. http://example.com/user/999 (où 999 est un ID utilisateur invalide) va lancer une erreur tout en conservant l'URL d'origine (c'est ce que je veux)

Cependant. Si quelqu'un entre http://example.com/asdfjkl dans l'URL (où asdfjkl est un contrôleur invalide), alors IIS lance la page générique 404. (c'est pas ce que je veux). Ce dont j'ai besoin, c'est que la même chose s'applique ci-dessus. L'URL d'origine reste et le contrôleur "NotFound" est chargé.

Je me inscris mes itinéraires comme celui-ci

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 

Notez que le ControllerExistsConstraint? Ce que j'ai besoin de faire est d'utiliser Reflection pour découvrir si oui ou non le contrôleur existe.

Quelqu'un peut-il m'aider à remplir les espaces? Je voudrais également connaître les implications de performance de ceci ... quelle est la performance de la réflexion? Si c'est trop, y a-t-il un meilleur moyen?

Répondre

-1

Pourquoi ne pas les capturer juste avec des erreurs personnalisées dans votre fichier web.config et éviter un tas de réflexion tous ensemble?

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

parce que dans ma question j'ai dit "L'URL d'origine reste, et le contrôleur" NotFound "est chargé.". ** Je ne veux PAS rediriger vers une page non trouvée ** –

10

J'ai une solution C#, j'espère que ça aide. J'ai plagié une partie de ce code, bien que pour la vie de moi, je ne peux pas trouver d'où je l'ai eu. Si quelqu'un le sait, s'il vous plaît faites le moi savoir afin que je puisse l'ajouter à mes commentaires.

Cette solution n'utilise pas de réflexion, mais elle examine toutes les erreurs d'application (exceptions) et vérifie s'il s'agit d'une erreur 404. Si c'est le cas, alors il achemine simplement la demande en cours vers un autre contrôleur. Bien que je ne sois pas un expert, je pense que cette solution pourrait être plus rapide que la réflexion. Quoi qu'il en soit, voici la solution et il va dans votre 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)); 
      } 
     } 
    } 

Ainsi, le contrôleur serait,

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

Bien que ** PAS ** une réponse à la question. Ceci ** résout ** mon problème. Je vais attribuer la prime, mais ne pas marquer comme réponse. –

+0

Vous avez probablement raison sur le bit "plus rapide que la réflexion". C'est sympa car je n'ai pas besoin d'appeler mon 'ControllerExistsConstraint' tout le temps. –

+0

Je n'ai pas remarqué que vous aviez un autre message ouvert concernant la question. J'aurais dû répondre à celui-là à la place. Peut-être que vous pouvez lier la solution de l'autre à ici. Vous êtes trop gentil au sujet des points de prime :). –

2

C'est un problème très similaire to mine, mais j'aime votre approche alternative.

Je pense que la réflexion comme un filtre dynamique est peut-être trop performances lourd, mais je pense avoir une meilleure façon - vous pouvez filtrer les actions autorisées par 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" }); 

Puis-je ajouter à cela un montant supplémentaire méthode sur ma base Controller:

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

Dans ce cas BaseController.NotFound gère l'action manquante sur un contrôleur valide.

Alors enfin:

  • {site}/invalid - trouvé par un nouveau filtre à base de réflexion
  • {site}/valid/notAnAction - trouvé par HandleUnknownAction
  • {site}/valid/action/id - trouvé par des contrôles dans le code pour l'identification (comme avant)
  • {site}/valid/action/id/extraPath - trouvé en ne correspondant à aucun itinéraire, mais le prendre tous

Je pense que c'est tous les scénarios 404 couverts :-)