136 votes

Traitement des erreurs 404 en ASP.NET MVC

Duplicata possible :
Comment puis-je traiter correctement les 404 dans ASP.NET MVC ?

J'ai fait les changements décrits à Gestionnaire d'erreur 404 Http dans Asp.Net MVC (RC 5) et je reçois toujours la page d'erreur standard 404. Dois-je modifier quelque chose dans IIS ?

1 votes

376voto

Marco Points 2652

J'ai enquêté A LOT sur la façon de gérer correctement les 404 dans MVC (spécifiquement MVC3) et ceci, à mon avis, est la meilleure solution que j'ai trouvée :

Dans global.asax :

public class MvcApplication : HttpApplication
{
    protected void Application_EndRequest()
    {
        if (Context.Response.StatusCode == 404)
        {
            Response.Clear();

            var rd = new RouteData();
            rd.DataTokens["area"] = "AreaName"; // In case controller is in another area
            rd.Values["controller"] = "Errors";
            rd.Values["action"] = "NotFound";

            IController c = new ErrorsController();
            c.Execute(new RequestContext(new HttpContextWrapper(Context), rd));
        }
    }
}

ErreursContrôleur :

public sealed class ErrorsController : Controller
{
    public ActionResult NotFound()
    {
        ActionResult result;

        object model = Request.Url.PathAndQuery;

        if (!Request.IsAjaxRequest())
            result = View(model);
        else
            result = PartialView("_NotFound", model);

        return result;
    }
}

Edit :

Si vous utilisez IoC (par exemple AutoFac), vous devez créer votre contrôleur en utilisant :

var rc = new RequestContext(new HttpContextWrapper(Context), rd);
var c = ControllerBuilder.Current.GetControllerFactory().CreateController(rc, "Errors");
c.Execute(rc);

Au lieu de

IController c = new ErrorsController();
c.Execute(new RequestContext(new HttpContextWrapper(Context), rd));

(facultatif)

Explication :

Il y a 6 scénarios auxquels je peux penser où une application ASP.NET MVC3 peut générer des 404s.

Généré par ASP.NET :

  • Scénario 1 : L'URL ne correspond pas à une route dans la table des routes.

Généré par ASP.NET MVC :

  • Scénario 2 : L'URL correspond à une route, mais spécifie un contrôleur qui n'existe pas.

  • Scénario 3 : L'URL correspond à un itinéraire, mais spécifie une action qui n'existe pas.

Généré manuellement :

  • Scénario 4 : Une action renvoie un HttpNotFoundResult en utilisant la méthode HttpNotFound().

  • Scénario 5 : Une action déclenche une HttpException avec le code d'état 404.

  • Scénario 6 : Une action modifie manuellement la propriété Response.StatusCode en 404.

Objectifs

  • (A) Afficher une page d'erreur 404 personnalisée à l'utilisateur.

  • (B) Maintenez le code d'état 404 sur la réponse du client (particulièrement important pour le référencement).

  • (C) Envoyez la réponse directement, sans impliquer une redirection 302.

Tentative de solution : Erreurs personnalisées

<system.web>
    <customErrors mode="On">
        <error statusCode="404" redirect="~/Errors/NotFound"/>
    </customErrors>
</system.web>

Problèmes avec cette solution :

  • Ne respecte pas l'objectif (A) dans les scénarios (1), (4), (6).
  • Ne répond pas automatiquement à l'objectif (B). Il doit être programmé manuellement.
  • Ne répond pas à l'objectif (C).

Tentative de solution : Erreurs HTTP

<system.webServer>
    <httpErrors errorMode="Custom">
        <remove statusCode="404"/>
        <error statusCode="404" path="App/Errors/NotFound" responseMode="ExecuteURL"/>
    </httpErrors>
</system.webServer>

Problèmes avec cette solution :

  • Ne fonctionne que sur IIS 7+.
  • Ne respecte pas l'objectif (A) dans les scénarios (2), (3), (5).
  • Ne répond pas automatiquement à l'objectif (B). Il doit être programmé manuellement.

Tentative de solution : Erreurs HTTP avec remplacement

<system.webServer>
    <httpErrors errorMode="Custom" existingResponse="Replace">
        <remove statusCode="404"/>
        <error statusCode="404" path="App/Errors/NotFound" responseMode="ExecuteURL"/>
    </httpErrors>
</system.webServer>

Problèmes avec cette solution :

  • Ne fonctionne que sur IIS 7+.
  • Ne répond pas automatiquement à l'objectif (B). Il doit être programmé manuellement.
  • Il masque les exceptions http au niveau de l'application. Par exemple, on ne peut pas utiliser la section customErrors, System.Web.Mvc.HandleErrorAttribute, etc. Il ne peut pas afficher uniquement les pages d'erreur génériques.

Solution Attempt customErrors et HTTP Errors

<system.web>
    <customErrors mode="On">
        <error statusCode="404" redirect="~/Errors/NotFound"/>
    </customError>
</system.web>

et

<system.webServer>
    <httpErrors errorMode="Custom">
        <remove statusCode="404"/>
        <error statusCode="404" path="App/Errors/NotFound" responseMode="ExecuteURL"/>
    </httpErrors>
</system.webServer>

Problèmes avec cette solution :

  • Ne fonctionne que sur IIS 7+.
  • Ne répond pas automatiquement à l'objectif (B). Il doit être programmé manuellement.
  • Ne respecte pas l'objectif (C) dans les scénarios (2), (3), (5).

Les personnes qui ont déjà eu des problèmes avec cela ont même essayé de créer leurs propres bibliothèques (cf. http://aboutcode.net/2011/02/26/handling-not-found-with-asp-net-mvc3.html ). Mais la solution précédente semble couvrir tous les scénarios sans la complexité de l'utilisation d'une bibliothèque externe.

0 votes

Associée à l'attribut HandleErrorAttribute que le modèle MVC configure automatiquement lorsque vous créez un nouveau projet, cette solution est certainement la meilleure et la plus simple.

8 votes

J'aime votre analyse mais pas votre solution :) les problèmes liés à l'attente jusqu'à la fin de la demande sont qu'une partie du contexte de la demande a déjà été éliminée, comme l'état de la session.

1 votes

Response.Clear et MVC ne font pas bon ménage, n'utilisez pas cette approche. blogs.msdn.com/b/rickandy/archive/2012/03/01/

144voto

Mike Chaliy Points 8694

Encore une autre solution.

Ajoutez des ErrorControllers ou une page statique contenant les informations relatives à l'erreur 404.

Modifiez votre web.config (dans le cas d'un contrôleur).

<system.web>
    <customErrors mode="On" >
       <error statusCode="404" redirect="~/Errors/Error404" />
    </customErrors>
</system.web>

Ou dans le cas d'une page statique

<system.web>
    <customErrors mode="On" >
        <error statusCode="404" redirect="~/Static404.html" />
    </customErrors>
</system.web>

Cela permettra de gérer à la fois les itinéraires manqués et les actions manquées.

2 votes

Joli ! :) ErrorsController pourrait hériter de la même base que tous les autres contrôleurs et ainsi avoir accès à une certaine fonctionnalité. De plus, la vue Error404 pourrait être enveloppée dans le maître, ce qui donnerait à l'utilisateur l'aspect général du reste du site sans travail supplémentaire.

7 votes

Utilisez <customErrors mode="RemoteOnly"> pour voir la page d'erreur réelle pendant le développement.

1 votes

C'est correct. N'appelez pas Response.Clear() ; comme suggéré par Mike Chaliy voir blogs.msdn.com/b/rickandy/archive/2012/03/01/

5voto

Yousi Points 249

La réponse de Marco est la MEILLEURE solution. J'avais besoin de contrôler ma gestion des erreurs, et je veux dire vraiment la contrôler. Bien sûr, j'ai étendu un peu la solution et créé un système complet de gestion des erreurs qui gère tout. J'ai également lu des articles sur cette solution dans d'autres blogs et elle semble très acceptable pour la plupart des développeurs avancés.

Voici le code final que j'utilise :

protected void Application_EndRequest()
    {
        if (Context.Response.StatusCode == 404)
        {
            var exception = Server.GetLastError();
            var httpException = exception as HttpException;
            Response.Clear();
            Server.ClearError();
            var routeData = new RouteData();
            routeData.Values["controller"] = "ErrorManager";
            routeData.Values["action"] = "Fire404Error";
            routeData.Values["exception"] = exception;
            Response.StatusCode = 500;

            if (httpException != null)
            {
                Response.StatusCode = httpException.GetHttpCode();
                switch (Response.StatusCode)
                {
                    case 404:
                        routeData.Values["action"] = "Fire404Error";
                        break;
                }
            }
            // Avoid IIS7 getting in the middle
            Response.TrySkipIisCustomErrors = true;
            IController errormanagerController = new ErrorManagerController();
            HttpContextWrapper wrapper = new HttpContextWrapper(Context);
            var rc = new RequestContext(wrapper, routeData);
            errormanagerController.Execute(rc);
        }
    }

et dans mon ErrorManagerController :

        public void Fire404Error(HttpException exception)
    {
        //you can place any other error handling code here
        throw new PageNotFoundException("page or resource");
    }

Maintenant, dans mon action, je lance une exception personnalisée que j'ai créée. Et mon contrôleur hérite d'une classe de contrôleur de base personnalisée que j'ai créée. Le contrôleur de base personnalisé a été créé pour remplacer la gestion des erreurs. Voici ma classe de contrôleur de base personnalisée :

public class MyBasePageController : Controller
{
    protected override void OnException(ExceptionContext filterContext)
    {
        filterContext.GetType();
        filterContext.ExceptionHandled = true;
        this.View("ErrorManager", filterContext).ExecuteResult(this.ControllerContext);
        base.OnException(filterContext);
    }
}

Le "ErrorManager" dans le code ci-dessus est juste une vue qui utilise un modèle basé sur l'ExceptionContext.

Ma solution fonctionne parfaitement et je suis en mesure de traiter N'IMPORTE QUELLE erreur sur mon site web et d'afficher différents messages en fonction de N'IMPORTE quel type d'exception.

2 votes

Je ne suis pas d'accord avec vous pour dire que c'est la MEILLEURE solution. Une tâche courante comme celle-ci ne devrait pas être aussi compliquée à mettre en place. La réponse de Marcos est excellente, mais vous n'avez vraiment pas besoin d'autant de code pour des choses simples.

4voto

Clearly Points 369

On dirait que c'est la meilleure façon de tout attraper.

Comment puis-je traiter correctement les 404 dans ASP.NET MVC ?

0 votes

Oui, cela a fonctionné pour mon projet MVC3. Merci

1voto

Mike Chaliy Points 8694

Ce que je peux vous recommander, c'est de regarder du côté de FilterAttribute. Par exemple, MVC possède déjà l'attribut HandleErrorAttribute. Vous pouvez le personnaliser pour qu'il ne traite que les erreurs 404. Répondez si vous êtes intéressé, je regarderai l'exemple.

BTW

La solution (avec la dernière route) que vous avez acceptée dans la question précédente ne fonctionne pas dans la plupart des situations. Deuxième solution avec HandleUnknownAction fonctionnera mais nécessitera d'effectuer ce changement dans chaque contrôleur ou d'avoir un seul contrôleur de base.

Mon choix se porte sur une solution avec HandleUnknownAction.

0 votes

Il semble que le problème soit que la route standard par défaut de "{controller}/{action}/{id}" capture tout et n'arrive pas à la dernière route. Je pensais que si un contrôleur ne pouvait être trouvé, la route suivante serait évaluée.

0 votes

HandleUnknownAction ne fonctionne qu'avec les actions qui ne sont pas trouvées. Que se passe-t-il si un itinéraire est trouvé mais qu'un contrôleur résultant ne peut être trouvé ? Quelle est la meilleure façon de gérer cela ?

0 votes

Oui, c'est correct, uniquement lorsque l'action est introuvable. Vous pouvez essayer de combiner les deux solutions. HandleUnknownAction pour les actions manquées et route pour les contrôleurs manqués. Une autre solution possible est un RouteHandler personnalisé.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X