Comment gérer correctement 404 dans ASP.NET MVC?

432

J'utilise RC2

Utilisation du routage URL:

routes.MapRoute(
    "Error",
     "{*url}",
     new { controller = "Errors", action = "NotFound" }  // 404s
);

Ce qui précède semble prendre en charge des requêtes comme celle-ci (en supposant que les tables de routage par défaut sont configurées par le projet MVC initial): "/ blah / blah / blah / blah"

Substitution de HandleUnknownAction () dans le contrôleur lui-même:

// 404s - handle here (bad action requested
protected override void HandleUnknownAction(string actionName) {
    ViewData["actionName"] = actionName;
    View("NotFound").ExecuteResult(this.ControllerContext);
}  

Cependant, les stratégies précédentes ne traitent pas une demande à un contrôleur incorrect / inconnu. Par exemple, je n'ai pas de "/ IDoNotExist", si je le demande, j'obtiens la page générique 404 du serveur Web et pas ma 404 si j'utilise routage + remplacement.

Enfin, ma question est: existe-t-il un moyen d'attraper ce type de demande en utilisant une route ou autre chose dans le framework MVC lui-même?

OU devrais-je utiliser par défaut Web.Config customErrors comme gestionnaire 404 et oublier tout cela? Je suppose que si je choisis CustomErrors, je devrai stocker la page générique 404 en dehors de / Views en raison des restrictions Web.Config sur l'accès direct.

Brian
la source
3
c'est une erreur 404, je ne m'en soucierais pas. laissez-le afficher 404. comme définitivement quelque chose mal tapé par l'utilisateur. ou si c'est quelque chose qui a été déplacé, votre application doit accepter cette demande et la rediriger de manière permanente. 404 appartient au serveur Web et non à l'application. vous pouvez toujours personnaliser les pages iis en cas d'erreur.
mamu
vous pouvez également consulter cette solution blog.dantup.com/2009/04/…
Développeur
ben.onfabrik.com/posts/aspnet-mvc-custom-error-pages a également de bonnes informations
Chris S
4
Il est dommage que 4 versions stables plus tard et plus de 5 ans plus tard, la situation pour la gestion des 404 dans asp.net MVC + IIS ne se soit pas vraiment améliorée et c'est toujours la question des questions et réponses pour savoir comment la gérer.
joelmdev

Réponses:

271

Le code est extrait de http://blogs.microsoft.co.il/blogs/shay/archive/2009/03/06/real-world-error-hadnling-in-asp-net-mvc-rc2.aspx et fonctionne dans ASP.net MVC 1.0 également

Voici comment je gère les exceptions http:

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

   ILogger logger = Container.Resolve<ILogger>();
   logger.Error(exception);

   Response.Clear();

   HttpException httpException = exception as HttpException;

   RouteData routeData = new RouteData();
   routeData.Values.Add("controller", "Error");

   if (httpException == null)
   {
       routeData.Values.Add("action", "Index");
   }
   else //It's an Http Exception, Let's handle it.
   {
       switch (httpException.GetHttpCode())
       {
          case 404:
              // Page not found.
              routeData.Values.Add("action", "HttpError404");
              break;
          case 500:
              // Server error.
              routeData.Values.Add("action", "HttpError500");
              break;

           // Here you can handle Views to other error codes.
           // I choose a General error template  
           default:
              routeData.Values.Add("action", "General");
              break;
      }
  }           

  // Pass exception details to the target error View.
  routeData.Values.Add("error", exception);

  // Clear the error on server.
  Server.ClearError();

  // Avoid IIS7 getting in the middle
  Response.TrySkipIisCustomErrors = true; 

  // Call target Controller and pass the routeData.
  IController errorController = new ErrorController();
  errorController.Execute(new RequestContext(    
       new HttpContextWrapper(Context), routeData));
}
Shay Jacoby
la source
23
mise à jour: la vérification d'un http 404 est définitivement requise, mais je ne sais toujours pas quand vous obtiendrez un 500. vous devez également définir explicitement le Response.StatusCode = 404 ou 500 sinon google commencera à indexer ces pages si vous retournez un code d'état 200, ce que ce code fait actuellement
Simon_Weaver
6
@Simon_Weaver: d'accord! Cela doit renvoyer 404 codes d'état. Il est essentiellement cassé en tant que solution 404 jusqu'à ce qu'il le fasse. Vérifiez ceci: codinghorror.com/blog/2007/03/…
Matt Kocaj
1
Il y a une faille sous-jacente à toute cette suggestion - au moment où l'exécution s'est propagée à Global.asax, trop de HttpContext est manquant. Vous ne pouvez pas rediriger vers vos contrôleurs comme le suggère l'exemple. Reportez-vous aux commentaires dans le lien du blog en haut.
Matt Kocaj
3
Comme certains des commentaires ci-dessus et la publication liée le mentionnent, cela ne semble pas fonctionner. Le errorcontroller est touché, mais un écran vide revient. (en utilisant mvc 3)
RyanW
4
quelque chose ne va pas bien, le but de MVC est de supprimer toute cette abstraction et pourtant, c'est à nouveau ...
Alex Nolasco
255

Exigences pour 404

Voici mes exigences pour une solution 404 et ci-dessous, je montre comment je l'implémente:

  • Je veux gérer les itinéraires correspondants avec de mauvaises actions
  • Je veux gérer les itinéraires correspondants avec de mauvais contrôleurs
  • Je veux gérer les itinéraires non appariés (URL arbitraires que mon application ne peut pas comprendre) - je ne veux pas que ces bulles remontent vers Global.asax ou IIS car je ne peux pas rediriger correctement dans mon application MVC
  • Je veux un moyen de gérer de la même manière que ci-dessus, des 404 personnalisés - comme quand un ID est soumis pour un objet qui n'existe pas (peut-être supprimé)
  • Je veux que tous mes 404 retournent une vue MVC (pas une page statique) dans laquelle je peux pomper plus de données plus tard si nécessaire ( bonnes conceptions 404 ) et ils doivent renvoyer le code d'état HTTP 404

Solution

Je pense que vous devriez enregistrer Application_Errordans Global.asax pour des choses plus élevées, comme les exceptions non gérées et la journalisation (comme le montre la réponse de Shay Jacoby ), mais pas la gestion 404. C'est pourquoi ma suggestion garde les 404 choses hors du fichier Global.asax.

Étape 1: avoir une place commune pour la logique d'erreur 404

C'est une bonne idée de maintenabilité. Utilisez un ErrorController pour que les améliorations futures de votre page 404 bien conçue puissent s'adapter facilement. Assurez-vous également que votre réponse contient le code 404 !

public class ErrorController : MyController
{
    #region Http404

    public ActionResult Http404(string url)
    {
        Response.StatusCode = (int)HttpStatusCode.NotFound;
        var model = new NotFoundViewModel();
        // If the url is relative ('NotFound' route) then replace with Requested path
        model.RequestedUrl = Request.Url.OriginalString.Contains(url) & Request.Url.OriginalString != url ?
            Request.Url.OriginalString : url;
        // Dont get the user stuck in a 'retry loop' by
        // allowing the Referrer to be the same as the Request
        model.ReferrerUrl = Request.UrlReferrer != null &&
            Request.UrlReferrer.OriginalString != model.RequestedUrl ?
            Request.UrlReferrer.OriginalString : null;

        // TODO: insert ILogger here

        return View("NotFound", model);
    }
    public class NotFoundViewModel
    {
        public string RequestedUrl { get; set; }
        public string ReferrerUrl { get; set; }
    }

    #endregion
}

Étape 2: utilisez une classe de contrôleur de base pour pouvoir invoquer facilement votre action 404 personnalisée et câbler HandleUnknownAction

Les 404 dans ASP.NET MVC doivent être capturés à plusieurs endroits. Le premier est HandleUnknownAction.

La InvokeHttp404méthode crée un lieu commun pour le réacheminement vers la ErrorControlleret notre nouvelle Http404action. Pensez SEC !

public abstract class MyController : Controller
{
    #region Http404 handling

    protected override void HandleUnknownAction(string actionName)
    {
        // If controller is ErrorController dont 'nest' exceptions
        if (this.GetType() != typeof(ErrorController))
            this.InvokeHttp404(HttpContext);
    }

    public ActionResult InvokeHttp404(HttpContextBase httpContext)
    {
        IController errorController = ObjectFactory.GetInstance<ErrorController>();
        var errorRoute = new RouteData();
        errorRoute.Values.Add("controller", "Error");
        errorRoute.Values.Add("action", "Http404");
        errorRoute.Values.Add("url", httpContext.Request.Url.OriginalString);
        errorController.Execute(new RequestContext(
             httpContext, errorRoute));

        return new EmptyResult();
    }

    #endregion
}

Étape 3: utilisez l'injection de dépendance dans votre usine de contrôleurs et connectez 404 HttpExceptions

Comme ça (il ne doit pas nécessairement s'agir de StructureMap):

Exemple MVC1.0:

public class StructureMapControllerFactory : DefaultControllerFactory
{
    protected override IController GetControllerInstance(Type controllerType)
    {
        try
        {
            if (controllerType == null)
                return base.GetControllerInstance(controllerType);
        }
        catch (HttpException ex)
        {
            if (ex.GetHttpCode() == (int)HttpStatusCode.NotFound)
            {
                IController errorController = ObjectFactory.GetInstance<ErrorController>();
                ((ErrorController)errorController).InvokeHttp404(RequestContext.HttpContext);

                return errorController;
            }
            else
                throw ex;
        }

        return ObjectFactory.GetInstance(controllerType) as Controller;
    }
}

Exemple MVC2.0:

    protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
    {
        try
        {
            if (controllerType == null)
                return base.GetControllerInstance(requestContext, controllerType);
        }
        catch (HttpException ex)
        {
            if (ex.GetHttpCode() == 404)
            {
                IController errorController = ObjectFactory.GetInstance<ErrorController>();
                ((ErrorController)errorController).InvokeHttp404(requestContext.HttpContext);

                return errorController;
            }
            else
                throw ex;
        }

        return ObjectFactory.GetInstance(controllerType) as Controller;
    }

Je pense qu'il vaut mieux attraper les erreurs plus près de leur origine. C'est pourquoi je préfère ce qui précède au Application_Errorgestionnaire.

C'est le deuxième endroit pour attraper 404s.

Étape 4: ajoutez une route NotFound à Global.asax pour les URL qui ne sont pas analysées dans votre application

Cette route devrait indiquer notre Http404action. Remarquez que le urlparamètre sera une URL relative, car le moteur de routage supprime la partie de domaine ici? C'est pourquoi nous avons toute cette logique d'URL conditionnelle à l'étape 1.

        routes.MapRoute("NotFound", "{*url}", 
            new { controller = "Error", action = "Http404" });

C'est le troisième et dernier endroit pour attraper 404 dans une application MVC que vous n'invoquez pas vous-même. Si vous n'attrapez pas d'itinéraires inégalés ici, MVC passera le problème à ASP.NET (Global.asax) et vous ne le voulez pas vraiment dans cette situation.

Étape 5: Enfin, appelez les 404 lorsque votre application ne trouve rien

Comme lorsqu'un mauvais ID est soumis à mon contrôleur de prêts (dérive de MyController):

    //
    // GET: /Detail/ID

    public ActionResult Detail(int ID)
    {
        Loan loan = this._svc.GetLoans().WithID(ID);
        if (loan == null)
            return this.InvokeHttp404(HttpContext);
        else
            return View(loan);
    }

Ce serait bien si tout cela pouvait être connecté en moins d'endroits avec moins de code mais je pense que cette solution est plus maintenable, plus testable et assez pragmatique.

Merci pour le retour jusqu'à présent. J'adorerais en avoir plus.

REMARQUE: cela a été modifié de manière significative à partir de ma réponse d'origine, mais le but / les exigences sont les mêmes - c'est pourquoi je n'ai pas ajouté de nouvelle réponse

Matt Kocaj
la source
12
Merci pour la rédaction complète. Un ajout est que lors de l'exécution sous IIS7, vous devez ajouter définir la propriété "TrySkipIisCustomErrors" sur true. Sinon, IIS renverra toujours la page 404 par défaut. Nous avons ajouté Response.TrySkipIiisCustomErrors = true; après la ligne de l'étape 5 qui définit le code d'état. msdn.microsoft.com/en-us/library/…
Rick
1
@Ryan La customErrorssection du web.config définit les pages de redirection statique qui sont gérées à un niveau élevé dans aspnet sinon IIS. Ce n'est pas ce que je voulais car j'avais besoin que le résultat soit MVC Views (donc je peux avoir des données dedans, etc.). Je ne dirais pas catégoriquement que " customErrorsest obsolète dans MVC" mais pour moi et cette solution 404, ils le sont certainement.
Matt Kocaj
1
De plus, quelqu'un peut-il mettre à jour l'étape 3 afin que StructureMap ne soit pas utilisé? Peut-être juste un ControllerFactory générique qui sera facile à implémenter si vous n'utilisez pas déjà un ControllerFactory.
David Murdoch
7
Cela fonctionne bien pour MVC3. Je suis passé ObjectFactory.GetInstanceà MVC3 à la DependencyResolver.Current.GetServiceplace, donc c'est plus générique. J'utilise Ninject.
kamranicus
122
Quelqu'un d'autre trouve-t-il manifestement fou qu'une chose aussi courante que les 404 dans un cadre Web soit si compliquée et sanglante.
quentin-star du
235

ASP.NET MVC ne prend pas très bien en charge les pages 404 personnalisées. Usine de contrôleur personnalisée, itinéraire fourre-tout, classe de contrôleur de base avec HandleUnknownAction- argh!

Les pages d'erreur personnalisées IIS sont une meilleure alternative jusqu'à présent:

web.config

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

ErrorController

public class ErrorController : Controller
{
    public ActionResult PageNotFound()
    {
        Response.StatusCode = 404;
        return View();
    }
}

Exemple de projet

Pavel Chuchuva
la source
38
CELA DEVRAIT ÊTRE LA RÉPONSE ACCEPTÉE !!! Fonctionne parfaitement sur ASP.NET MVC 3 avec IIS Express.
Andrei Rînea
7
Si vous utilisez IIS7 +, c'est certainement la voie à suivre. +1!
elo80ka
3
est-il possible de renvoyer uniquement un état 404 lorsque vous travaillez en JSON dans le même projet?
VinnyG
6
Cela fonctionne très bien dans iis express, mais dès que je déploie le site en production IIS 7.5, tout ce que j'obtiens est une page blanche au lieu de la vue d'erreur.
Moulde
2
D'après mes tests (avec MVC3) cela rompt customErrors mode="On"avec le HandleErrorAttributefonctionnel. Les pages d'erreur personnalisées pour les exceptions non gérées dans les actions du contrôleur ne sont plus servies.
Slauma
153

Réponse rapide / TL; DR

entrez la description de l'image ici

Pour les paresseux:

Install-Package MagicalUnicornMvcErrorToolkit -Version 1.0

Supprimez ensuite cette ligne de global.asax

GlobalFilters.Filters.Add(new HandleErrorAttribute());

Et ce n'est que pour IIS7 + et IIS Express.

Si vous utilisez Cassini ... eh bien ... euh ... euh ... maladroit ... gênant


Réponse longue et expliquée

Je sais que cela a été répondu. Mais la réponse est VRAIMENT SIMPLE (bravo à David Fowler et Damian Edwards pour avoir vraiment répondu à cela).

Il n'est pas nécessaire de faire quoi que ce soit de personnalisé .

Car ASP.NET MVC3, tous les morceaux sont là.

Étape 1 -> Mettez à jour votre web.config en DEUX spots.

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

et

<system.webServer>
    <httpErrors errorMode="Custom">
      <remove statusCode="404" subStatusCode="-1" />
      <error statusCode="404" path="/NotFound" responseMode="ExecuteURL" />
      <remove statusCode="500" subStatusCode="-1" />
      <error statusCode="500" path="/ServerError" responseMode="ExecuteURL" />
    </httpErrors>    

...
<system.webServer>
...
</system.web>

Maintenant, prenez bien note des ITINÉRAIRES que j'ai décidé d'utiliser. Vous pouvez utiliser n'importe quoi, mais mes itinéraires sont

  • /NotFound <- pour un 404 introuvable, page d'erreur.
  • /ServerError<- pour toute autre erreur, incluez les erreurs qui se produisent dans mon code. il s'agit d'une erreur de 500 serveurs internes

Voyez comment la première section <system.web>ne contient qu'une seule entrée personnalisée? L' statusCode="404"entrée? Je n'ai répertorié qu'un seul code d'état, car toutes les autres erreurs, y compris 500 Server Error(c.-à-d. Ces erreurs embêtantes qui se produisent lorsque votre code a un bogue et plante la demande de l'utilisateur) .. toutes les autres erreurs sont gérées par le paramètre defaultRedirect="/ServerError".. qui dit , si vous n'êtes pas une page 404 introuvable, alors veuillez vous rendre sur l'itinéraire /ServerError.

D'accord. c'est à l'écart .. maintenant à mes itinéraires répertoriés dansglobal.asax

Étape 2 - Création des itinéraires dans Global.asax

Voici ma section complète de l'itinéraire ..

public static void RegisterRoutes(RouteCollection routes)
{
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
    routes.IgnoreRoute("{*favicon}", new {favicon = @"(.*/)?favicon.ico(/.*)?"});

    routes.MapRoute(
        "Error - 404",
        "NotFound",
        new { controller = "Error", action = "NotFound" }
        );

    routes.MapRoute(
        "Error - 500",
        "ServerError",
        new { controller = "Error", action = "ServerError"}
        );

    routes.MapRoute(
        "Default", // Route name
        "{controller}/{action}/{id}", // URL with parameters
        new {controller = "Home", action = "Index", id = UrlParameter.Optional}
        );
}

Cela répertorie deux routes ignorées -> axd'set favicons(ooo! Bonus ignore route, pour vous!) Ensuite (et la commande est IMPERATIVE ICI), j'ai mes deux routes explicites de gestion des erreurs .. suivies de toutes les autres routes. Dans ce cas, celui par défaut. Bien sûr, j'en ai plus, mais c'est spécial pour mon site Web. Assurez-vous simplement que les routes d'erreur sont en haut de la liste. L'ordre est impératif .

Enfin, pendant que nous sommes à l'intérieur de notre global.asaxfichier, nous n'enregistrons PAS globalement l'attribut HandleError. Non, non, non monsieur. Nadda. Nan. Nien. Négatif. Noooooooooo ...

Supprimer cette ligne de global.asax

GlobalFilters.Filters.Add(new HandleErrorAttribute());

Étape 3 - Créez le contrôleur avec les méthodes d'action

Maintenant .. nous ajoutons un contrôleur avec deux méthodes d'action ...

public class ErrorController : Controller
{
    public ActionResult NotFound()
    {
        Response.StatusCode = (int)HttpStatusCode.NotFound;
        return View();
    }

    public ActionResult ServerError()
    {
        Response.StatusCode = (int)HttpStatusCode.InternalServerError;

        // Todo: Pass the exception into the view model, which you can make.
        //       That's an exercise, dear reader, for -you-.
        //       In case u want to pass it to the view, if you're admin, etc.
        // if (User.IsAdmin) // <-- I just made that up :) U get the idea...
        // {
        //     var exception = Server.GetLastError();
        //     // etc..
        // }

        return View();
    }

    // Shhh .. secret test method .. ooOOooOooOOOooohhhhhhhh
    public ActionResult ThrowError()
    {
        throw new NotImplementedException("Pew ^ Pew");
    }
}

Ok, voyons ça. Tout d'abord, il n'y a AUCUN [HandleError] attribut ici. Pourquoi? Parce que le ASP.NETframework intégré gère déjà les erreurs ET nous avons spécifié toute la merde que nous devons faire pour gérer une erreur :) C'est dans cette méthode!

Ensuite, j'ai les deux méthodes d'action. Rien de difficile là-bas. Si vous souhaitez afficher des informations d'exception, vous pouvez les utiliser Server.GetLastError()pour obtenir ces informations.

Bonus WTF: Oui, j'ai fait une troisième méthode d'action, pour tester la gestion des erreurs.

Étape 4 - Créez les vues

Et enfin, créez deux vues. Mettez-les dans la vue normale, pour ce contrôleur.

entrez la description de l'image ici

Commentaires bonus

  • Vous n'avez pas besoin d'un Application_Error(object sender, EventArgs e)
  • Les étapes ci-dessus fonctionnent toutes à 100% parfaitement avec Elmah . Elmah bat des wrox!

Et ça, mes amis, ça devrait être ça.

Maintenant, félicitations pour avoir lu autant et avoir une licorne comme prix!

entrez la description de l'image ici

Pure.Krome
la source
J'ai donc essayé de l'implémenter, mais quelques problèmes ... d'abord, vous avez besoin d'un ~ avant le chemin dans weeb.config ou cela ne fonctionne pas pour les répertoires virtuels. 2-Si les erreurs personnalisées IIS se déclenchent et que la vue utilise une mise en page, elle ne s'affiche pas du tout, juste une page blanche. J'ai résolu cela en ajoutant cette ligne dans le contrôleur "Response.TrySkipIisCustomErrors = true;" . Cependant, cela ne fonctionne toujours pas si vous accédez à une URL qui est un fichier mais 404 .. comme mysite / que ce soit / fake.html obtient une page blanche.
Robert Noack
3
-1, désolé, pour moi, toute solution qui change l'URL pour 404 est fausse. et avec webconfig, il n'y a aucun moyen dans MVC que vous puissiez le gérer sans changer d'URL, ou vous devez créer des fichiers html statiques ou aspx (oui, de vieux fichiers aspx simples) pour pouvoir le faire. votre solution est très bien si vous aimez ?aspxerrorpath=/er/not/foundavoir dans les URL.
Gutek
7
Cela pourrait sonne vraiment bizarre - mais ma réponse a été donnée il y a longtemps et je suis d' accord avec votre @Gutek, je ne aime pas faire une redirection vers une page d'erreur plus . J'avais l'habitude de (ref à ma réponse: P). Si l'erreur s'est produite sur / some / resource .. alors CETTE ressource devrait renvoyer un 404 ou 500, etc. Implications SEO massives sinon. Ahh .. comme les temps changent :)
Pure.Krome
@Gutek Connaissez-vous customErrors redirectMode = "ResponseRewrite"? Et le retour des 404 n'est pas idéal du point de vue de la sécurité
Jowen
1
@Chris <insérez votre divinité préférée ici> bon sang. Je ne me souviens même plus de ce que c'était maintenant. Eh bien, ma collection de mèmes à la rescousse ... et .. corrigée.
Pure.Krome
86

J'ai beaucoup étudié la façon de gérer correctement les 404 dans MVC (en particulier MVC3) , et cela, à , 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));
        }
    }
}

ErrorsController:

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;
    }
}

(Optionnel)

Explication:

AFAIK, il y a 6 cas différents qu'une application ASP.NET MVC3 peut générer 404s.

(Généré automatiquement par ASP.NET Framework :)

(1) Une URL ne trouve pas de correspondance dans la table de routage.

(Généré automatiquement par ASP.NET MVC Framework :)

(2) Une URL trouve une correspondance dans la table de routage, mais spécifie un contrôleur inexistant.

(3) Une URL trouve une correspondance dans la table de routage, mais spécifie une action inexistante.

(Généré manuellement :)

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

(5) Une action lève une HttpException avec le code d'état 404.

(6) Une action modifie manuellement la propriété Response.StatusCode en 404.

Normalement, vous souhaitez atteindre 3 objectifs:

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

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

(3) Envoyez la réponse directement, sans impliquer de redirection 302.

Il existe différentes manières d’effectuer cette opération:

(1)

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

Problèmes avec cette solution:

  1. Ne respecte pas l'objectif (1) dans les cas (1), (4), (6).
  2. Ne respecte pas automatiquement l'objectif (2). Il doit être programmé manuellement.
  3. Non conforme à l'objectif (3).

(2)

<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:

  1. Fonctionne uniquement sur IIS 7+.
  2. Ne respecte pas l'objectif (1) dans les cas (2), (3), (5).
  3. Ne respecte pas automatiquement l'objectif (2). Il doit être programmé manuellement.

(3)

<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:

  1. Fonctionne uniquement sur IIS 7+.
  2. Ne respecte pas automatiquement l'objectif (2). Il doit être programmé manuellement.
  3. Il masque les exceptions http au niveau de l'application. Par exemple, ne peut pas utiliser la section customErrors, System.Web.Mvc.HandleErrorAttribute, etc. Il ne peut pas uniquement afficher les pages d'erreur génériques.

(4)

<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:

  1. Fonctionne uniquement sur IIS 7+.
  2. Ne respecte pas automatiquement l'objectif (2). Il doit être programmé manuellement.
  3. Non conforme à l'objectif (3) dans les cas (2), (3), (5).

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

Marco
la source
Très bonne réponse. Digne de beaucoup plus de votes positifs. Pourquoi votre code global.asax ne fonctionne-t-il pas / n'appartient-il pas à Application_Error?
NinjaNye
7
Merci! Cela ne peut pas être fait sous Application_Error, car les 404 explicites lancés depuis un contrôleur ne sont pas considérés comme des erreurs sur ASP.NET. Si vous renvoyez un HttpNotFound () à partir d'un contrôleur, l'événement Application_Error ne se déclenchera jamais.
Marco
1
Je pense que vous avez oublié public ActionResult NotFound() {}dans votre ErrorsController. Pouvez-vous également expliquer à quoi _NotFoundressemblerait votre partiel pour les demandes AJAX?
d4n3
2
Avec MVC 4, je suis toujours MissingMethodException: Cannot create an abstract classsur la ligne. c.Execute(new RequestContext(new HttpContextWrapper(Context), rd)); Des idées?
µBio
1
Si l'URL "non trouvée" inclut un point dans le chemin (par exemple example.com/hi.bob ), alors Application_EndRequest ne se déclenche pas du tout, et j'obtiens la page générique 404 d'IE.
Bob.at.Indigo.Health
13

J'aime vraiment la solution cottsaks et je pense que son explication est très claire. mon seul ajout était de modifier l'étape 2 comme suit

public abstract class MyController : Controller
{

    #region Http404 handling

    protected override void HandleUnknownAction(string actionName)
    {
        //if controller is ErrorController dont 'nest' exceptions
        if(this.GetType() != typeof(ErrorController))
        this.InvokeHttp404(HttpContext);
    }

    public ActionResult InvokeHttp404(HttpContextBase httpContext)
    {
        IController errorController = ObjectFactory.GetInstance<ErrorController>();
        var errorRoute = new RouteData();
        errorRoute.Values.Add("controller", "Error");
        errorRoute.Values.Add("action", "Http404");
        errorRoute.Values.Add("url", httpContext.Request.Url.OriginalString);
        errorController.Execute(new RequestContext(
             httpContext, errorRoute));

        return new EmptyResult();
    }

    #endregion
}

Fondamentalement, cela empêche les URL contenant des actions non valides ET les contrôleurs de déclencher la routine d'exception deux fois. par exemple pour les URL telles que asdfsdf / dfgdfgd

Dave Lowe
la source
4
C'est excellent. Ces cas "deux fois" commençaient à me déranger. a mis à jour ma réponse
Matt Kocaj
la solution ci-dessus fonctionne-t-elle du tout si l'utilisateur entre un mauvais contrôleur et un mauvais nom d'action?
Monojit Sarkar
6

La seule façon de faire fonctionner la méthode de @ cottsak pour des contrôleurs non valides était de modifier la demande d'itinéraire existante dans CustomControllerFactory, comme ceci:

public class CustomControllerFactory : DefaultControllerFactory
{
    protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
    {
        try
        {
            if (controllerType == null)
                return base.GetControllerInstance(requestContext, controllerType); 
            else
                return ObjectFactory.GetInstance(controllerType) as Controller;
        }
        catch (HttpException ex)
        {
            if (ex.GetHttpCode() == (int)HttpStatusCode.NotFound)
            {
                requestContext.RouteData.Values["controller"] = "Error";
                requestContext.RouteData.Values["action"] = "Http404";
                requestContext.RouteData.Values.Add("url", requestContext.HttpContext.Request.Url.OriginalString);

                return ObjectFactory.GetInstance<ErrorController>();
            }
            else
                throw ex;
        }
    }
}

Je dois mentionner que j'utilise MVC 2.0.

Dave K
la source
Est-ce que tu sais pourquoi? (Spécifique MVC2?)
Matt Kocaj
Je pense que la clé était de modifier la demande existante plutôt que d'en faire une nouvelle, mais je l'ai fait il y a quelque temps, donc je ne suis pas sûr que ce soit le cas. Le "InvokeHttp404" ne fonctionnait pas depuis l'usine du contrôleur.
Dave K
J'ai mis à jour ma réponse aujourd'hui avec quelques spécificités MVC2. Pouvez-vous s'il vous plaît me dire si ma solution détaillée ci-dessus ne fonctionne toujours pas pour vous?
Matt Kocaj
4

Voici une autre méthode utilisant des outils MVC que vous pouvez gérer les demandes de mauvais noms de contrôleur, de mauvais noms de route et tout autre critère que vous jugez approprié à l'intérieur d'une méthode Action. Personnellement, je préfère éviter autant de paramètres web.config que possible, car ils font la redirection 302/200 et ne prennent pas en charge ResponseRewrite ( Server.Transfer) en utilisant les vues Razor. Je préfère renvoyer un 404 avec une page d'erreur personnalisée pour des raisons de référencement.

Une partie de cela est une nouvelle approche de la technique de cottsak ci-dessus.

Cette solution utilise également des paramètres web.config minimaux favorisant les filtres d'erreur MVC 3 à la place.

Usage

Il suffit de lever une HttpException à partir d'une action ou d'un ActionFilterAttribute personnalisé.

Throw New HttpException(HttpStatusCode.NotFound, "[Custom Exception Message Here]")

Étape 1

Ajoutez le paramètre suivant à votre web.config. Cela est nécessaire pour utiliser HandleErrorAttribute de MVC.

<customErrors mode="On" redirectMode="ResponseRedirect" />

Étape 2

Ajoutez un HandleHttpErrorAttribute personnalisé similaire au HandleErrorAttribute du framework MVC, sauf pour les erreurs HTTP:

<AttributeUsage(AttributeTargets.All, AllowMultiple:=True)>
Public Class HandleHttpErrorAttribute
    Inherits FilterAttribute
    Implements IExceptionFilter

    Private Const m_DefaultViewFormat As String = "ErrorHttp{0}"

    Private m_HttpCode As HttpStatusCode
    Private m_Master As String
    Private m_View As String

    Public Property HttpCode As HttpStatusCode
        Get
            If m_HttpCode = 0 Then
                Return HttpStatusCode.NotFound
            End If
            Return m_HttpCode
        End Get
        Set(value As HttpStatusCode)
            m_HttpCode = value
        End Set
    End Property

    Public Property Master As String
        Get
            Return If(m_Master, String.Empty)
        End Get
        Set(value As String)
            m_Master = value
        End Set
    End Property

    Public Property View As String
        Get
            If String.IsNullOrEmpty(m_View) Then
                Return String.Format(m_DefaultViewFormat, Me.HttpCode)
            End If
            Return m_View
        End Get
        Set(value As String)
            m_View = value
        End Set
    End Property

    Public Sub OnException(filterContext As System.Web.Mvc.ExceptionContext) Implements System.Web.Mvc.IExceptionFilter.OnException
        If filterContext Is Nothing Then Throw New ArgumentException("filterContext")

        If filterContext.IsChildAction Then
            Return
        End If

        If filterContext.ExceptionHandled OrElse Not filterContext.HttpContext.IsCustomErrorEnabled Then
            Return
        End If

        Dim ex As HttpException = TryCast(filterContext.Exception, HttpException)
        If ex Is Nothing OrElse ex.GetHttpCode = HttpStatusCode.InternalServerError Then
            Return
        End If

        If ex.GetHttpCode <> Me.HttpCode Then
            Return
        End If

        Dim controllerName As String = filterContext.RouteData.Values("controller")
        Dim actionName As String = filterContext.RouteData.Values("action")
        Dim model As New HandleErrorInfo(filterContext.Exception, controllerName, actionName)

        filterContext.Result = New ViewResult With {
            .ViewName = Me.View,
            .MasterName = Me.Master,
            .ViewData = New ViewDataDictionary(Of HandleErrorInfo)(model),
            .TempData = filterContext.Controller.TempData
        }
        filterContext.ExceptionHandled = True
        filterContext.HttpContext.Response.Clear()
        filterContext.HttpContext.Response.StatusCode = Me.HttpCode
        filterContext.HttpContext.Response.TrySkipIisCustomErrors = True
    End Sub
End Class

Étape 3

Ajoutez des filtres à GlobalFilterCollection ( GlobalFilters.Filters) dans Global.asax. Cet exemple achemine toutes les erreurs InternalServerError (500) vers la vue partagée Error ( Views/Shared/Error.vbhtml). Les erreurs NotFound (404) seront également envoyées à ErrorHttp404.vbhtml dans les vues partagées. J'ai ajouté une erreur 401 ici pour vous montrer comment cela peut être étendu pour des codes d'erreur HTTP supplémentaires. Notez que ces vues doivent être partagées et qu'elles utilisent toutes l' System.Web.Mvc.HandleErrorInfoobjet comme modèle.

filters.Add(New HandleHttpErrorAttribute With {.View = "ErrorHttp401", .HttpCode = HttpStatusCode.Unauthorized})
filters.Add(New HandleHttpErrorAttribute With {.View = "ErrorHttp404", .HttpCode = HttpStatusCode.NotFound})
filters.Add(New HandleErrorAttribute With {.View = "Error"})

Étape 4

Créez une classe de contrôleur de base et héritez-en dans vos contrôleurs. Cette étape nous permet de gérer les noms d'actions inconnus et de déclencher l'erreur HTTP 404 à notre HandleHttpErrorAttribute.

Public Class BaseController
    Inherits System.Web.Mvc.Controller

    Protected Overrides Sub HandleUnknownAction(actionName As String)
        Me.ActionInvoker.InvokeAction(Me.ControllerContext, "Unknown")
    End Sub

    Public Function Unknown() As ActionResult
        Throw New HttpException(HttpStatusCode.NotFound, "The specified controller or action does not exist.")
        Return New EmptyResult
    End Function
End Class

Étape 5

Créez un remplacement ControllerFactory et remplacez-le dans votre fichier Global.asax dans Application_Start. Cette étape nous permet de lever l'exception HTTP 404 lorsqu'un nom de contrôleur non valide a été spécifié.

Public Class MyControllerFactory
    Inherits DefaultControllerFactory

    Protected Overrides Function GetControllerInstance(requestContext As System.Web.Routing.RequestContext, controllerType As System.Type) As System.Web.Mvc.IController
        Try
            Return MyBase.GetControllerInstance(requestContext, controllerType)
        Catch ex As HttpException
            Return DependencyResolver.Current.GetService(Of BaseController)()
        End Try
    End Function
End Class

'In Global.asax.vb Application_Start:

controllerBuilder.Current.SetControllerFactory(New MyControllerFactory)

Étape 6

Incluez un itinéraire spécial dans votre RoutTable.Routes pour l'action BaseController Unknown. Cela nous aidera à générer un 404 dans le cas où un utilisateur accède à un contrôleur inconnu ou à une action inconnue.

'BaseController
routes.MapRoute( _
    "Unknown", "BaseController/{action}/{id}", _
    New With {.controller = "BaseController", .action = "Unknown", .id = UrlParameter.Optional} _
)

Sommaire

Cet exemple montre comment utiliser le framework MVC pour renvoyer 404 codes d'erreur Http au navigateur sans redirection à l'aide d'attributs de filtre et de vues d'erreur partagées. Il montre également l'affichage de la même page d'erreur personnalisée lorsque des noms de contrôleur et des noms d'action non valides sont spécifiés.

J'ajouterai une capture d'écran d'un nom de contrôleur non valide, d'un nom d'action et d'un 404 personnalisé généré à partir de l'action Home / TriggerNotFound si j'obtiens suffisamment de votes pour en poster un =). Fiddler renvoie un message 404 lorsque j'accède aux URL suivantes à l'aide de cette solution:

/InvalidController
/Home/InvalidRoute
/InvalidController/InvalidRoute
/Home/TriggerNotFound

le post de cottsak ci-dessus et ces articles étaient de bonnes références.

sky-dev
la source
Hmm, je n'ai pas pu faire fonctionner ça: The IControllerFactory 'aaa.bbb.CustomControllerFactory' did not return a controller for the name '123'.- des idées pourquoi j'aurais ça?
enashnash
redirectMode = "ResponseRedirect". Cela retournera un 302 Found + un 200 OK, ce qui n'est pas bon pour le référencement!
PussInBoots
4

Ma solution raccourcie qui fonctionne avec les zones non contrôlées, les contrôleurs et les actions:

  1. Créez une vue 404.cshtml.

  2. Créez une classe de base pour vos contrôleurs:

    public class Controller : System.Web.Mvc.Controller
    {
        protected override void HandleUnknownAction(string actionName)
        {
            Http404().ExecuteResult(ControllerContext);
        }
    
        protected virtual ViewResult Http404()
        {
            Response.StatusCode = (int)HttpStatusCode.NotFound;
            return View("404");
        }
    }
  3. Créez une fabrique de contrôleurs personnalisés renvoyant votre contrôleur de base comme solution de rechange:

    public class ControllerFactory : DefaultControllerFactory
    {
        protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
        {
            if (controllerType != null)
                return base.GetControllerInstance(requestContext, controllerType);
    
            return new Controller();
        }
    }
  4. Ajoutez à Application_Start()la ligne suivante:

    ControllerBuilder.Current.SetControllerFactory(typeof(ControllerFactory));
Herman Kan
la source
3

Dans MVC4 WebAPI 404 peut être géré de la manière suivante,

COURS APICONTROLLER

    // GET /api/courses/5
    public HttpResponseMessage<Courses> Get(int id)
    {
        HttpResponseMessage<Courses> resp = null;

        var aCourse = _courses.Where(c => c.Id == id).FirstOrDefault();

        resp = aCourse == null ? new HttpResponseMessage<Courses>(System.Net.HttpStatusCode.NotFound) : new HttpResponseMessage<Courses>(aCourse);

        return resp;
    }

CONTRÔLEUR À DOMICILE

public ActionResult Course(int id)
{
    return View(id);
}

VUE

<div id="course"></div>
<script type="text/javascript">
    var id = @Model;
    var course = $('#course');
    $.ajax({    
        url: '/api/courses/' + id,
        success: function (data) {
            course.text(data.Name);
        },
        statusCode: {
            404: function() 
            {
                course.text('Course not available!');    
            }
        }
    });
</script>

GLOBAL

public static void RegisterRoutes(RouteCollection routes)
{
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

    routes.MapHttpRoute(
        name: "DefaultApi",
        routeTemplate: "api/{controller}/{id}",
        defaults: new { id = RouteParameter.Optional }
    );

    routes.MapRoute(
        name: "Default",
        url: "{controller}/{action}/{id}",
        defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
    );
}

RÉSULTATS

entrez la description de l'image ici

Diganta Kumar
la source
2

Essayez NotFoundMVC sur nuget. Cela fonctionne, aucune configuration.

Dark Vador
la source
http://localhost/Views/Shared/NotFound.cshtmln'entraîne pas une page 404 personnalisée.
Dan Friedman
C'est très facile à personnaliser. Vous avez accès à l'URL demandée et au référent, vous pouvez donc faire ce que vous voulez. J'utilise ce package, et cela fonctionne vraiment bien.
Avrohom Yisroel
Il s'agit d'un package génial, à condition que vous n'utilisiez pas les actions asynchrones de la tâche <ActionResult> (ou d'autres actions asynchrones similaires). Sur MVC 5, c'est un scénario cassé. Il y a une fourchette sur GitHub pour contourner cela, mais pour moi, c'est un non, non.
Stargazer
2

Ma solution, au cas où quelqu'un la trouverait utile.

Dans Web.config:

<system.web>
    <customErrors mode="On" defaultRedirect="Error" >
      <error statusCode="404" redirect="~/Error/PageNotFound"/>
    </customErrors>
    ...
</system.web>

Dans Controllers/ErrorController.cs:

public class ErrorController : Controller
{
    public ActionResult PageNotFound()
    {
        if(Request.IsAjaxRequest()) {
            Response.StatusCode = (int)HttpStatusCode.NotFound;
            return Content("Not Found", "text/plain");
        }

        return View();
    }
}

Ajoutez un PageNotFound.cshtmldans le Shareddossier, et c'est tout.

Konamiman
la source
2
Ce problème ne génère-t-il pas une redirection 302 puis un état 200 (OK) vers le client? Ne devraient-ils pas encore obtenir le statut 404?
Sam
@Konamiman Êtes-vous sûr que la ligne de votre code doit lire model.RequestedUrl = Request.Url.OriginalString.Contains(url) & Request.Url.OriginalString != url ? Request.Url.OriginalString : url;et non model.RequestedUrl = Request.Url.OriginalString.Contains(url) && Request.Url.OriginalString != url ? Request.Url.OriginalString : url;(& au lieu de &&)?
Jean-François Beauchamp
2

Il me semble que la CustomErrorsconfiguration standard devrait tout de même fonctionner , car Server.Transferil semble que l'implémentation interne de ResponseRewritene soit pas compatible avec MVC.

Cela me semble être un trou de fonctionnalité flagrant, j'ai donc décidé de réimplémenter cette fonctionnalité à l'aide d'un module HTTP. La solution ci-dessous vous permet de gérer n'importe quel code d'état HTTP (y compris 404) en redirigeant vers n'importe quelle route MVC valide comme vous le feriez normalement.

<customErrors mode="RemoteOnly" redirectMode="ResponseRewrite">
    <error statusCode="404" redirect="404.aspx" />
    <error statusCode="500" redirect="~/MVCErrorPage" />
</customErrors>

Cela a été testé sur les plates-formes suivantes;

  • MVC4 en mode pipeline intégré (IIS Express 8)
  • MVC4 en mode classique (VS Development Server, Cassini)
  • MVC4 en mode classique (IIS6)

Avantages

  • Solution générique pouvant être intégrée dans n'importe quel projet MVC
  • Permet la prise en charge de la configuration traditionnelle des erreurs personnalisées
  • Fonctionne dans les modes Pipeline intégré et Classique

La solution

namespace Foo.Bar.Modules {

    /// <summary>
    /// Enables support for CustomErrors ResponseRewrite mode in MVC.
    /// </summary>
    public class ErrorHandler : IHttpModule {

        private HttpContext HttpContext { get { return HttpContext.Current; } }
        private CustomErrorsSection CustomErrors { get; set; }

        public void Init(HttpApplication application) {
            System.Configuration.Configuration configuration = WebConfigurationManager.OpenWebConfiguration("~");
            CustomErrors = (CustomErrorsSection)configuration.GetSection("system.web/customErrors");

            application.EndRequest += Application_EndRequest;
        }

        protected void Application_EndRequest(object sender, EventArgs e) {

            // only handle rewrite mode, ignore redirect configuration (if it ain't broke don't re-implement it)
            if (CustomErrors.RedirectMode == CustomErrorsRedirectMode.ResponseRewrite && HttpContext.IsCustomErrorEnabled) {

                int statusCode = HttpContext.Response.StatusCode;

                // if this request has thrown an exception then find the real status code
                Exception exception = HttpContext.Error;
                if (exception != null) {
                    // set default error status code for application exceptions
                    statusCode = (int)HttpStatusCode.InternalServerError;
                }

                HttpException httpException = exception as HttpException;
                if (httpException != null) {
                    statusCode = httpException.GetHttpCode();
                }

                if ((HttpStatusCode)statusCode != HttpStatusCode.OK) {

                    Dictionary<int, string> errorPaths = new Dictionary<int, string>();

                    foreach (CustomError error in CustomErrors.Errors) {
                        errorPaths.Add(error.StatusCode, error.Redirect);
                    }

                    // find a custom error path for this status code
                    if (errorPaths.Keys.Contains(statusCode)) {
                        string url = errorPaths[statusCode];

                        // avoid circular redirects
                        if (!HttpContext.Request.Url.AbsolutePath.Equals(VirtualPathUtility.ToAbsolute(url))) {

                            HttpContext.Response.Clear();
                            HttpContext.Response.TrySkipIisCustomErrors = true;

                            HttpContext.Server.ClearError();

                            // do the redirect here
                            if (HttpRuntime.UsingIntegratedPipeline) {
                                HttpContext.Server.TransferRequest(url, true);
                            }
                            else {
                                HttpContext.RewritePath(url, false);

                                IHttpHandler httpHandler = new MvcHttpHandler();
                                httpHandler.ProcessRequest(HttpContext);
                            }

                            // return the original status code to the client
                            // (this won't work in integrated pipleline mode)
                            HttpContext.Response.StatusCode = statusCode;

                        }
                    }

                }

            }

        }

        public void Dispose() {

        }


    }

}

Usage

Incluez-le comme module HTTP final dans votre web.config

  <system.web>
    <httpModules>
      <add name="ErrorHandler" type="Foo.Bar.Modules.ErrorHandler" />
    </httpModules>
  </system.web>

  <!-- IIS7+ -->
  <system.webServer>
    <modules>
      <add name="ErrorHandler" type="Foo.Bar.Modules.ErrorHandler" />
    </modules>
  </system.webServer>

Pour ceux d'entre vous qui font attention, vous remarquerez qu'en mode Pipeline intégré, cela répondra toujours avec HTTP 200 en raison de la façon dont cela Server.TransferRequestfonctionne. Pour renvoyer le code d'erreur approprié, j'utilise le contrôleur d'erreur suivant.

public class ErrorController : Controller {

    public ErrorController() { }

    public ActionResult Index(int id) {
        // pass real error code to client
        HttpContext.Response.StatusCode = id;
        HttpContext.Response.TrySkipIisCustomErrors = true;

        return View("Errors/" + id.ToString());
    }

}
Red Taz
la source
2

Traiter les erreurs dans ASP.NET MVC est juste une douleur dans le cul. J'ai essayé beaucoup de suggestions sur cette page et sur d'autres questions et sites et rien ne fonctionne bien. Une suggestion a été de gérer les erreurs sur web.config à l' intérieur de system.webserver mais cela ne fait que renvoyer des pages blanches .

Mon objectif en proposant cette solution était de:

  • PAS REDIRIGÉ
  • Retourne les CODES D'ÉTAT APPROPRIÉS pas 200 / Ok comme la gestion des erreurs par défaut

Voici ma solution.

1 .Ajoutez ce qui suit à la section system.web

   <system.web>
     <customErrors mode="On" redirectMode="ResponseRewrite">
      <error statusCode="404"  redirect="~/Error/404.aspx" />
      <error statusCode="500" redirect="~/Error/500.aspx" />
     </customErrors>
    <system.web>

Ce qui précède gère toutes les URL non gérées par routes.config et les exceptions non gérées, en particulier celles rencontrées sur les vues. Remarquez que j'ai utilisé aspx pas html . C'est pour que je puisse ajouter un code de réponse sur le code derrière.

2 . Créez un dossier appelé Erreur (ou ce que vous préférez) à la racine de votre projet et ajoutez les deux formulaires Web. Voici ma page 404;

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="404.aspx.cs" Inherits="Myapp.Error._404" %>

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title >Page Not found</title>
    <link href="<%=ResolveUrl("~/Content/myapp.css")%>" rel="stylesheet" />
</head>
<body>
    <div class="top-nav">
      <a runat="server" class="company-logo" href="~/"></a>
    </div>
    <div>
        <h1>404 - Page Not found</h1>
        <p>The page you are looking for cannot be found.</p>
        <hr />
        <footer></footer>
    </div>
</body>
</html>

Et sur le code derrière j'ai mis le code de réponse

protected void Page_Load(object sender, EventArgs e)
{
    Response.StatusCode = 404;
}

Faites de même pour la page 500

3 Pour gérer les erreurs dans les contrôleurs. Il y a plusieurs façons de le faire. C'est ce qui a fonctionné pour moi. Tous mes contrôleurs héritent d'un contrôleur de base. Dans le contrôleur de base, j'ai les méthodes suivantes

protected ActionResult ShowNotFound()
{
    return ShowNotFound("Page not found....");
}

protected ActionResult ShowNotFound(string message)
{
    return ShowCustomError(HttpStatusCode.NotFound, message);
}

protected ActionResult ShowServerError()
{
    return ShowServerError("Application error....");
}

protected ActionResult ShowServerError(string message)
{
    return ShowCustomError(HttpStatusCode.InternalServerError, message);
}

protected ActionResult ShowNotAuthorized()
{
    return ShowNotAuthorized("You are not allowed ....");

}

protected ActionResult ShowNotAuthorized(string message)
{
    return ShowCustomError(HttpStatusCode.Forbidden, message);
}

protected ActionResult ShowCustomError(HttpStatusCode statusCode, string message)
{
    Response.StatusCode = (int)statusCode;
    string title = "";
    switch (statusCode)
    {
        case HttpStatusCode.NotFound:
            title = "404 - Not found";
            break;
        case HttpStatusCode.Forbidden:
            title = "403 - Access Denied";
            break;
        default:
            title = "500 - Application Error";
            break;
    }
    ViewBag.Title = title;
    ViewBag.Message = message;
    return View("CustomError");
}

4. Ajoutez le CustomError.cshtml à votre dossier Vues partagées . Ci-dessous est le mien;

<h1>@ViewBag.Title</h1>
<br />
<p>@ViewBag.Message</p>

Maintenant, dans votre contrôleur d'application, vous pouvez faire quelque chose comme ça;

public class WidgetsController : ControllerBase
{
  [HttpGet]
  public ActionResult Edit(int id)
  {
    Try
    {
       var widget = db.getWidgetById(id);
       if(widget == null)
          return ShowNotFound();
          //or return ShowNotFound("Invalid widget!");
       return View(widget);
    }
    catch(Exception ex)
    {
       //log error
       logger.Error(ex)
       return ShowServerError();
    }
  }
}

Maintenant pour la mise en garde . Il ne gère pas les erreurs de fichiers statiques. Donc, si vous avez un itinéraire tel que example.com/widgets et que l'utilisateur le change en example.com/widgets.html , ils obtiendront la page d'erreur par défaut IIS, vous devrez donc gérer les erreurs de niveau IIS d'une autre manière.

Moses Machua
la source
1

Publier une réponse car mon commentaire était trop long ...

C'est à la fois un commentaire et des questions au post licorne / réponse:

https://stackoverflow.com/a/7499406/687549

Je préfère cette réponse aux autres pour sa simplicité et le fait qu'apparemment certaines personnes chez Microsoft ont été consultées. J'ai cependant trois questions et si elles peuvent être répondues, j'appellerai cette réponse le Saint Graal de toutes les réponses d'erreur 404/500 sur les interwebs pour une application ASP.NET MVC (x).

@ Pure.Krome

  1. Pouvez-vous mettre à jour votre réponse avec les éléments SEO des commentaires soulignés par GWB (il n'y a jamais eu de mention de cela dans votre réponse) - <customErrors mode="On" redirectMode="ResponseRewrite">et <httpErrors errorMode="Custom" existingResponse="Replace">?

  2. Pouvez-vous demander à vos amis de l'équipe ASP.NET s'il est correct de le faire comme ça - ce serait bien d'avoir une confirmation - peut-être que c'est un grand non-non à changer redirectModeet existingResponsede cette façon être en mesure de bien jouer avec le référencement?!

  3. Pouvez - vous ajouter quelques précisions entourant ce genre de choses ( customErrors redirectMode="ResponseRewrite", customErrors redirectMode="ResponseRedirect", httpErrors errorMode="Custom" existingResponse="Replace", RETIRER customErrorsCOMPLÈTEMENT comme quelqu'un a suggéré) après avoir parlé à vos amis chez Microsoft?

Comme je le disais; ce serait supernice si nous pouvions rendre votre réponse plus complète car cela semble être une question assez populaire avec 54 000+ vues.

Mise à jour : la réponse Licorne fait un 302 trouvé et un 200 OK et ne peut pas être modifiée pour ne renvoyer que 404 en utilisant un itinéraire. Ce doit être un fichier physique qui n'est pas très MVC: ish. Passons donc à une autre solution. Dommage car cela semblait être le MVC ultime: c'est la réponse jusqu'ici.

Chat Botté
la source
1

Ajout de ma solution, qui est presque identique à celle d'Herman Kan, avec une petite ride pour lui permettre de fonctionner pour mon projet.

Créez un contrôleur d'erreur personnalisé:

public class Error404Controller : BaseController
{
    [HttpGet]
    public ActionResult PageNotFound()
    {
        Response.StatusCode = 404;
        return View("404");
    }
}

Créez ensuite une fabrique de contrôleurs personnalisés:

public class CustomControllerFactory : DefaultControllerFactory
{
    protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
    {
        return controllerType == null ? new Error404Controller() : base.GetControllerInstance(requestContext, controllerType);
    }
}

Enfin, ajoutez un remplacement au contrôleur d'erreur personnalisé:

protected override void HandleUnknownAction(string actionName)
{
    var errorRoute = new RouteData();
    errorRoute.Values.Add("controller", "Error404");
    errorRoute.Values.Add("action", "PageNotFound");
    new Error404Controller().Execute(new RequestContext(HttpContext, errorRoute));
}

Et c'est tout. Pas besoin de modifications de Web.config.

Rob Lyndon
la source
1

1) Créez une classe de contrôleur abstraite.

public abstract class MyController:Controller
{
    public ActionResult NotFound()
    {
        Response.StatusCode = 404;
        return View("NotFound");
    }

    protected override void HandleUnknownAction(string actionName)
    {
        this.ActionInvoker.InvokeAction(this.ControllerContext, "NotFound");
    }
    protected override void OnAuthorization(AuthorizationContext filterContext) { }
}  

2) Faites l'héritage de cette classe abstraite dans tous vos contrôleurs

public class HomeController : MyController
{}  

3) Et ajoutez une vue nommée "NotFound" dans votre dossier View-Shared.

Mehmet
la source
0

J'ai parcouru la plupart des solutions publiées sur ce fil. Bien que cette question puisse être ancienne, elle est toujours très applicable aux nouveaux projets, même maintenant, j'ai donc passé beaucoup de temps à lire les réponses présentées ici et ailleurs.

Comme @Marco a souligné les différents cas dans lesquels un 404 peut se produire, j'ai vérifié la solution que j'ai compilée ensemble par rapport à cette liste. En plus de sa liste d'exigences, j'en ai également ajouté une de plus.

  • La solution doit être capable de gérer les appels MVC et AJAX / WebAPI de la manière la plus appropriée. (c'est-à-dire que si 404 se produit dans MVC, il devrait afficher la page Introuvable et si 404 se produit dans WebAPI, il ne devrait pas détourner la réponse XML / JSON pour que le Javascript consommateur puisse l'analyser facilement).

Cette solution est double:

La première partie vient de @Guillaume à https://stackoverflow.com/a/27354140/2310818 . Leur solution prend en charge tous les 404 causés par un itinéraire non valide, un contrôleur non valide et une action non valide.

L'idée est de créer un WebForm, puis de le faire appeler l'action NotFound de votre contrôleur d'erreurs MVC. Il fait tout cela sans redirection, vous ne verrez donc pas un seul 302 dans Fiddler. L'URL d'origine est également conservée, ce qui rend cette solution fantastique!


La deuxième partie vient de @ Germán à https://stackoverflow.com/a/5536676/2310818 . Leur solution prend en charge tout 404 retourné par vos actions sous la forme de HttpNotFoundResult () ou lance une nouvelle HttpException ()!

L'idée est d'avoir un filtre qui regarde la réponse ainsi que l'exception levée par vos contrôleurs MVC et d'appeler l'action appropriée dans votre contrôleur d'erreurs. Encore une fois, cette solution fonctionne sans redirection et l'URL d'origine est préservée!


Comme vous pouvez le voir, ces deux solutions offrent ensemble un mécanisme de gestion des erreurs très robuste et elles répondent à toutes les exigences répertoriées par @Marco ainsi qu'à mes exigences. Si vous souhaitez voir un échantillon de travail ou une démonstration de cette solution, veuillez laisser dans les commentaires et je serais heureux de le mettre ensemble.

Parth Shah
la source
0

J'ai parcouru tous les articles, mais rien ne fonctionne pour moi: mon utilisateur requis saisit quoi que ce soit dans votre page d'URL 404 personnalisée.J'ai pensé que c'était très simple.Mais vous devez comprendre la gestion de 404 correctement:

 <system.web>
    <customErrors mode="On" redirectMode="ResponseRewrite">
      <error statusCode="404" redirect="~/PageNotFound.aspx"/>
    </customErrors>
  </system.web>
<system.webServer>
    <httpErrors errorMode="Custom">
      <remove statusCode="404"/>
      <error statusCode="404" path="/PageNotFound.html" responseMode="ExecuteURL"/>
    </httpErrors>
</system.webServer>

J'ai trouvé cet article très utile. Doit être lu immédiatement. Page d'erreur personnalisée-Ben Foster

Thakur Rock
la source