Comment faire fonctionner les pages d'erreur personnalisées dans ASP.NET MVC 4


247

Je veux une page d'erreur personnalisée affichée pour 500, 404 et 403. Voici ce que j'ai fait:

  1. Activé les erreurs personnalisées dans le web.config comme suit:

    <customErrors mode="On" 
                  defaultRedirect="~/Views/Shared/Error.cshtml">
    
        <error statusCode="403" 
               redirect="~/Views/Shared/UnauthorizedAccess.cshtml" />
    
        <error statusCode="404" 
               redirect="~/Views/Shared/FileNotFound.cshtml" />
    
    </customErrors>
  2. Enregistré HandleErrorAttributecomme filtre d'action global dans la FilterConfigclasse comme suit:

    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
        filters.Add(new CustomHandleErrorAttribute());
        filters.Add(new AuthorizeAttribute());
    }
  3. Création d'une page d'erreur personnalisée pour chacun des messages ci-dessus. Celui par défaut pour 500 était déjà disponible dès la sortie de la boîte.

  4. Déclaré dans chaque vue de page d'erreur personnalisée que le modèle de la page est System.Web.Mvc.HandleErrorInfo

Pour 500, il affiche la page d'erreur personnalisée. Pour d'autres, ce n'est pas le cas.

Y a-t-il quelque chose qui me manque?

Il semble que ce ne soit pas tout ce qu'il faut pour afficher les erreurs personnalisées lorsque je lis le code dans la OnExceptionméthode de la HandleErrorAttributeclasse et qu'il n'en gère que 500.

Que dois-je faire pour gérer d'autres erreurs?


21
Ce qui est bizarre avec cette configuration, c'est que votre redirection vers les vues, pas les actions du contrôleur. Qui est censé rendre ces vues et passer dans un modèle, par exemple? Juste à y penser.
Oliver

2
La plupart des réponses ici ne traitent pas tous les cas ou obligent le serveur Web à répondre de manière "incorrecte", c'est-à-dire en redirigeant vers une page d'erreur plutôt qu'en renvoyant une réponse d'erreur. Si vous vous souciez que le serveur réponde d'une manière attendue des serveurs Web, il y a un article assez détaillé à ce sujet ici: benfoster.io/blog/aspnet-mvc-custom-error-pages . Soyez averti que ce n'est pas aussi simple que les réponses ici, donc si vous voulez une réponse facile, utilisez simplement l'une des réponses ci-dessous.
rdans

1
Voici un autre excellent article sur diverses techniques de gestion des erreurs asp.net dusted.codes/…
Godsayah

Réponses:


352

Ma configuration actuelle (sur MVC3, mais je pense qu'elle s'applique toujours) repose sur un ErrorController, donc j'utilise:

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

Et le contrôleur contient les éléments suivants:

public class ErrorController : Controller
{
    public ViewResult Index()
    {
        return View("Error");
    }
    public ViewResult NotFound()
    {
        Response.StatusCode = 404;  //you may want to set this to 200
        return View("NotFound");
    }
}

Et les vues comme vous les implémentez. J'ai tendance à ajouter un peu de logique cependant, pour afficher la trace de la pile et les informations d'erreur si l'application est en mode débogage. Ainsi, Error.cshtml ressemble à ceci:

@model System.Web.Mvc.HandleErrorInfo
@{
    Layout = "_Layout.cshtml";
    ViewBag.Title = "Error";
}
<div class="list-header clearfix">
    <span>Error</span>
</div>
<div class="list-sfs-holder">
    <div class="alert alert-error">
        An unexpected error has occurred. Please contact the system administrator.
    </div>
    @if (Model != null && HttpContext.Current.IsDebuggingEnabled)
    {
        <div>
            <p>
                <b>Exception:</b> @Model.Exception.Message<br />
                <b>Controller:</b> @Model.ControllerName<br />
                <b>Action:</b> @Model.ActionName
            </p>
            <div style="overflow:scroll">
                <pre>
                    @Model.Exception.StackTrace
                </pre>
            </div>
        </div>
    }
</div>

7
Avez-vous dû mettre quelque chose dans votre Application_Error dans votre Global.asax pour ce Pablo?
Alicia

12
Le code dans le contrôleur ne semble pas s'exécuter d'après mon expérience. MVC4 - lever une System.Exception dans un autre contrôleur rendra le fichier Error.cshtml, mais pas via le ErrorController. Quelqu'un d'autre vit ça?
Nilzor

53
Pour toute autre personne qui a trouvé cela utile, mais avait besoin de plus de contexte; La balise <customErrors> va dans <system.web> dans web.config.
gooberverse

7
Mise à jour pour les autres - apparemment, mon problème se produisait parce que j'avais redirectMode="ResponseRewrite"sur l' CustomerErrorsélément
KyleMit

42
S'il vous plaît, pour l'amour de Dieu, ignorez le commentaire disant //you may want to set this to 200dans le code. NE FAITES PAS CELA!
Dementic

40

J'ai fait la solution pablo et j'ai toujours eu l'erreur (MVC4)

La vue «Erreur» ou son maître est introuvable ou aucun moteur de vue ne prend en charge l'emplacement recherché.

Pour vous en débarrasser, supprimez la ligne

 filters.Add(new HandleErrorAttribute());

dans FilterConfig.cs


J'ai cherché partout pour résoudre ce problème. Cela avait finalement la réponse. Je savais pourquoi il le faisait, mais pour moi, je ne pouvais pas, sans penser radicalement à ce que les autres ont dit. J'imagine que je partage la douleur de 360Airwalk quand je dis merci d'avoir signalé cela. Légende!
Adam

Il s'agit d'une option et le contrôleur d'erreur fonctionne correctement. Mais il semble que lorsque vous enregistrez des filtres dans FilterConfig.cs, il recherche Error.cshtml dans les dossiers de vue des contrôleurs partagés et originaux. Lorsque vous modifiez le fichier Error.cshtml en autre chose que cela fonctionne, notre ErrorController personnalisé. Mais il y a un endroit où vous pouvez ajouter cet enregistrement et c'est global.asax.cs. Si vous ajoutez la ligne mentionnée dans la fonction RegisterGlobalFilters (filtres GlobalFilterCollection) dans global.asax.cs et que vous la supprimez de FilterConfig.cs, cela fonctionne.
isaolmez

Je pense que cela est lié à l'ordre d'enregistrement des filtres. Conservez le contrôleur d'erreur et déplacez l'enregistrement du filtre vers global.asax.cs. public static void RegisterGlobalFilters (filtres GlobalFilterCollection) {filters.Add (new HandleErrorAttribute ()); }
isaolmez

24

Je fais quelque chose qui nécessite moins de codage que les autres solutions publiées.

Tout d'abord, dans mon web.config, j'ai les éléments suivants:

<customErrors mode="On" defaultRedirect="~/ErrorPage/Oops">
   <error redirect="~/ErrorPage/Oops/404" statusCode="404" />
   <error redirect="~/ErrorPage/Oops/500" statusCode="500" />
</customErrors>

Et le contrôleur (/Controllers/ErrorPageController.cs) contient les éléments suivants:

public class ErrorPageController : Controller
{
    public ActionResult Oops(int id)
    {
        Response.StatusCode = id;

        return View();
    }
}

Et enfin, la vue contient les éléments suivants (supprimés pour plus de simplicité, mais ils peuvent conta:

@{ ViewBag.Title = "Oops! Error Encountered"; }

<section id="Page">
  <div class="col-xs-12 well">
    <table cellspacing="5" cellpadding="3" style="background-color:#fff;width:100%;" class="table-responsive">
      <tbody>
        <tr>
          <td valign="top" align="left" id="tableProps">
            <img width="25" height="33" src="~/Images/PageError.gif" id="pagerrorImg">
          </td>
          <td width="360" valign="middle" align="left" id="tableProps2">
            <h1 style="COLOR: black; FONT: 13pt/15pt verdana" id="errortype"><span id="errorText">@Response.Status</span></h1>
          </td>
        </tr>
        <tr>
          <td width="400" colspan="2" id="tablePropsWidth"><font style="COLOR: black; FONT: 8pt/11pt verdana">Possible causes:</font>
          </td>
        </tr>
        <tr>
          <td width="400" colspan="2" id="tablePropsWidth2">
            <font style="COLOR: black; FONT: 8pt/11pt verdana" id="LID1">
                            <hr>
                            <ul>
                                <li id="list1">
                                    <span class="infotext">
                                        <strong>Baptist explanation: </strong>There
                                        must be sin in your life. Everyone else opened it fine.<br>
                                    </span>
                                </li>
                                <li>
                                    <span class="infotext">
                                        <strong>Presbyterian explanation: </strong>It's
                                        not God's will for you to open this link.<br>
                                    </span>
                                </li>
                                <li>
                                    <span class="infotext">
                                        <strong> Word of Faith explanation:</strong>
                                        You lack the faith to open this link. Your negative words have prevented
                                        you from realizing this link's fulfillment.<br>
                                    </span>
                                </li>
                                <li>
                                    <span class="infotext">
                                        <strong>Charismatic explanation: </strong>Thou
                                        art loosed! Be commanded to OPEN!<br>
                                    </span>
                                </li>
                                <li>
                                    <span class="infotext">
                                        <strong>Unitarian explanation:</strong> All
                                        links are equal, so if this link doesn't work for you, feel free to
                                        experiment with other links that might bring you joy and fulfillment.<br>
                                    </span>
                                </li>
                                <li>
                                    <span class="infotext">
                                        <strong>Buddhist explanation:</strong> .........................<br>
                                    </span>
                                </li>
                                <li>
                                    <span class="infotext">
                                        <strong>Episcopalian explanation:</strong>
                                        Are you saying you have something against homosexuals?<br>
                                    </span>
                                </li>
                                <li>
                                    <span class="infotext">
                                        <strong>Christian Science explanation: </strong>There
                                        really is no link.<br>
                                    </span>
                                </li>
                                <li>
                                    <span class="infotext">
                                        <strong>Atheist explanation: </strong>The only
                                        reason you think this link exists is because you needed to invent it.<br>
                                    </span>
                                </li>
                                <li>
                                    <span class="infotext">
                                        <strong>Church counselor's explanation:</strong>
                                        And what did you feel when the link would not open?
                                    </span>
                                </li>
                            </ul>
                            <p>
                                <br>
                            </p>
                            <h2 style="font:8pt/11pt verdana; color:black" id="ietext">
                                <img width="16" height="16" align="top" src="~/Images/Search.gif">
                                HTTP @Response.StatusCode - @Response.StatusDescription <br>
                            </h2>
                        </font>
          </td>
        </tr>
      </tbody>
    </table>
  </div>
</section>

C'est aussi simple que ça. Il pourrait être facilement étendu pour offrir des informations d'erreur plus détaillées, mais ELMAH gère cela pour moi et le statusCode & statusDescription est tout ce dont j'ai habituellement besoin.


Je pense que la redirection dans le fichier .config de "~ / ErrorPage / Oops / 404" devrait probablement être "~ / ErrorPage / Oops? 404" non? C'est du moins ce qui a fonctionné pour moi. Peut-être que cela dépend simplement du routage.
Josh Sutterfield

Comment simuler une erreur levée par IIS. Que ce soit 500 ou 504. Que faire dans ASP.Net MVC - 5 code pour simuler l'exception d'IIS afin que je puisse tester ma page d'erreur personnalisée
Incassable

12

Il semble y avoir un certain nombre d'étapes confondues ici. Je présenterai ce que j'ai fait à partir de zéro.

  1. Créer le ErrorPagecontrôleur

    public class ErrorPageController : Controller
    {
        public ActionResult Index()
        {
            return View();
        }
    
        public ActionResult Oops(int id)
        {
            Response.StatusCode = id;
            return View();
        }
    }
  2. Ajoutez des vues pour ces deux actions (clic droit -> Ajouter une vue). Ceux-ci devraient apparaître dans un dossier appelé ErrorPage.

  3. À l'intérieur, App_Startouvrez FilterConfig.cset commentez le filtre de gestion des erreurs.

    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
        // Remove this filter because we want to handle errors ourselves via the ErrorPage controller
        //filters.Add(new HandleErrorAttribute());
    }
  4. Dans web.config, ajoutez les <customerErrors>entrées suivantes , sousSystem.Web

    <customErrors mode="On" defaultRedirect="~/ErrorPage/Oops">
        <error redirect="~/ErrorPage/Oops/404" statusCode="404" />
        <error redirect="~/ErrorPage/Oops/500" statusCode="500" />
    </customErrors>
  5. Test (bien sûr). Lancez une exception non gérée dans votre code et voyez-la aller à la page avec l'ID 500, puis utilisez une URL vers une page qui n'existe pas pour voir 404.


J'obtenais cette erreur An exception occurred while processing your request. Additionally, another exception occurred while executing the custom error page for the first exception. The request has been terminated.Tout ce que j'ai récupéré de votre code est dans le fichier web.config, j'ai ajouté <error redirect = "~/ControllerName/ActionName" statusCode="404"/>et cela a bien fonctionné :) Le reste du code provenait de la réponse de @ Pablo. J'utilise MVC 5 et l'entité framework 6. Je n'ai pas supprimé filters.Add(new HandleErrorAttribute())deFilterConfig.cs
sumedha

Comment simuler une erreur levée par IIS. Que ce soit 500 ou 504. Que faire dans ASP.Net MVC - 5 code pour simuler l'exception d'IIS afin que je puisse tester ma page d'erreur personnalisée
Incassable

Aussi, comment lever une exception non gérée (étape 5). Je suis nouveau dans le codage, veuillez guider.
Incassable le

Ça ne marche toujours pas pour moi? Et le routage? Dois-je également ajouter la page Routage pour erreur? Si je clique sur la page: localhost: 84 / Enforcer / blah, je suis redirigé vers: localhost: 84 / Enforcer / Enforcer / Error / NotFound? Aspxerrorpath = /… La page d'erreur ressemble à une page d'erreur standard fournie par Asp.NET. Des idées?
Radek Strugalski

L'élément customerrors dans le webconfig devrait être habndling cela. Votre code d'itinéraire par défaut (créé par le projet) devrait fonctionner correctement.
VictorySaber

11

Je recommanderais d'utiliser le fichier Global.asax.cs.

 protected void Application_Error(Object sender, EventArgs e)
{
    var exception = Server.GetLastError();
    if (exception is HttpUnhandledException)
    {
        Server.Transfer("~/Error.aspx");
    }
    if (exception != null)
    {
        Server.Transfer("~/Error.aspx");
    }
    try
    {
        // This is to stop a problem where we were seeing "gibberish" in the
        // chrome and firefox browsers
        HttpApplication app = sender as HttpApplication;
        app.Response.Filter = null;
    }
    catch
    {
    }
}

1
Je ne pensais pas que vous pourriez faire un Server.Transfer () dans MVC. Pensez-vous que le PO a un site mixte?
Rap

1
pourquoi devrions-nous utiliser Application_Error en mvc? Nous avons des options comme l'attribut [handleerror] avec des options d'URL de redirection. Y a-t-il un avantage spécifique avec application_error à cela?
Kurkula

Nous devrions utiliser HandleErrorAttribute dans MVC et en remplaçant la méthode OnException, nous pouvons les gérer d'une bien meilleure façon
Kumar Lachhani

7

En s'appuyant sur la réponse publiée par maxspan, j'ai mis en place un exemple de projet minimal sur GitHub montrant toutes les parties fonctionnelles.

Fondamentalement, nous ajoutons simplement une Application_Errorméthode à global.asax.cs pour intercepter l'exception et nous donner la possibilité de rediriger (ou plus correctement, de transférer la demande ) vers une page d'erreur personnalisée.

    protected void Application_Error(Object sender, EventArgs e)
    {
        // See http://stackoverflow.com/questions/13905164/how-to-make-custom-error-pages-work-in-asp-net-mvc-4
        // for additional context on use of this technique

        var exception = Server.GetLastError();
        if (exception != null)
        {
            // This would be a good place to log any relevant details about the exception.
            // Since we are going to pass exception information to our error page via querystring,
            // it will only be practical to issue a short message. Further detail would have to be logged somewhere.

            // This will invoke our error page, passing the exception message via querystring parameter
            // Note that we chose to use Server.TransferRequest, which is only supported in IIS 7 and above.
            // As an alternative, Response.Redirect could be used instead.
            // Server.Transfer does not work (see https://support.microsoft.com/en-us/kb/320439 )
            Server.TransferRequest("~/Error?Message=" + exception.Message);
        }

    }

Contrôleur d'erreur:

/// <summary>
/// This controller exists to provide the error page
/// </summary>
public class ErrorController : Controller
{
    /// <summary>
    /// This action represents the error page
    /// </summary>
    /// <param name="Message">Error message to be displayed (provided via querystring parameter - a design choice)</param>
    /// <returns></returns>
    public ActionResult Index(string Message)
    {
        // We choose to use the ViewBag to communicate the error message to the view
        ViewBag.Message = Message;
        return View();
    }

}

Affichage de la page d'erreur:

<!DOCTYPE html>

<html>
<head>
    <title>Error</title>
</head>
<body>

    <h2>My Error</h2>
    <p>@ViewBag.Message</p>
</body>
</html>

Rien d'autre n'est impliqué, à part la désactivation / suppression filters.Add(new HandleErrorAttribute())dans FilterConfig.cs

public class FilterConfig
{
    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
        //filters.Add(new HandleErrorAttribute()); // <== disable/remove
    }
}

Bien que très simple à implémenter, le seul inconvénient que je vois dans cette approche est d'utiliser la chaîne de requête pour fournir des informations d'exception à la page d'erreur cible.


3

J'avais tout configuré, mais je ne pouvais toujours pas voir les pages d'erreur appropriées pour le code d'état 500 sur notre serveur de transfert, malgré le fait que tout fonctionnait bien sur les serveurs de développement local.

J'ai trouvé cet article de blog de Rick Strahl qui m'a aidé.

J'avais besoin d'ajouter Response.TrySkipIisCustomErrors = true;à mon code de gestion des erreurs personnalisé.


@ Shaun314 Vous voulez dire où mettre ce code? Dans l'action qui gère la demande. Vous pouvez voir des exemples dans ce billet de blog.
DCShannon

2

Voici ma solution. Utilisez [ExportModelStateToTempData] / [ImportModelStateFromTempData] est inconfortable à mon avis.

~ / Vues / Accueil / Error.cshtml:

@{
    ViewBag.Title = "Error";
    Layout = "~/Views/Shared/_Layout.cshtml";
}

<h2>Error</h2>
<hr/>

<div style="min-height: 400px;">

    @Html.ValidationMessage("Error")

    <br />
    <br />

    <button onclick="Error_goBack()" class="k-button">Go Back</button>
    <script>
        function Error_goBack() {
            window.history.back()
        }
    </script>

</div>

~ / Contrôleurs / HomeController.sc:

public class HomeController : BaseController
{
    public ActionResult Index()
    {
        return View();
    }

    public ActionResult Error()
    {
        return this.View();
    }

    ...
}

~ / Contrôleurs / BaseController.sc:

public class BaseController : Controller
{
    public BaseController() { }

    protected override void OnActionExecuted(ActionExecutedContext filterContext)
    {
        if (filterContext.Result is ViewResult)
        {
            if (filterContext.Controller.TempData.ContainsKey("Error"))
            {
                var modelState = filterContext.Controller.TempData["Error"] as ModelState;
                filterContext.Controller.ViewData.ModelState.Merge(new ModelStateDictionary() { new KeyValuePair<string, ModelState>("Error", modelState) });
                filterContext.Controller.TempData.Remove("Error");
            }
        }
        if ((filterContext.Result is RedirectResult) || (filterContext.Result is RedirectToRouteResult))
        {
            if (filterContext.Controller.ViewData.ModelState.ContainsKey("Error"))
            {
                filterContext.Controller.TempData["Error"] = filterContext.Controller.ViewData.ModelState["Error"];
            }
        }

        base.OnActionExecuted(filterContext);
    }
}

~ / Contrôleurs / MyController.sc:

public class MyController : BaseController
{
    public ActionResult Index()
    {
        return View();
    }

    public ActionResult Details(int id)
    {
        if (id != 5)
        {
            ModelState.AddModelError("Error", "Specified row does not exist.");
            return RedirectToAction("Error", "Home");
        }
        else
        {
            return View("Specified row exists.");
        }
    }
}

Je vous souhaite des projets réussis ;-)


2

Vous pouvez obtenir des erreurs qui fonctionnent correctement sans pirater global.cs, jouer avec HandleErrorAttribute, faire Response.TrySkipIisCustomErrors, brancher Application_Error, ou autre:

Dans system.web (juste l'habituel, on / off)

<customErrors mode="On">
  <error redirect="/error/401" statusCode="401" />
  <error redirect="/error/500" statusCode="500" />
</customErrors>

et dans system.webServer

<httpErrors existingResponse="PassThrough" />

Maintenant, les choses devraient se comporter comme prévu, et vous pouvez utiliser votre ErrorController pour afficher tout ce dont vous avez besoin.


Comment simuler une erreur levée par IIS. Que ce soit 500 ou 504. Que faire dans ASP.Net MVC - 5 code pour simuler l'exception d'IIS afin que je puisse tester ma page d'erreur personnalisée
Incassable

@ Incassable modifiez temporairement votre code pour lever une exception.
theycallmemorty

Ça n'a pas fait de différence pour moi. Je ne suis pas redirigé vers ma page d'erreur personnalisée lors d'une exception ou d'une erreur 404 introuvable.
pnizzle

0

Il semble que je sois arrivé en retard à la fête, mais vous devriez aussi vérifier cela.

Donc, system.webpour mettre en cache des exceptions dans l'application telles que return HttpNotFound ()

  <system.web>
    <customErrors mode="RemoteOnly">
      <error statusCode="404" redirect="/page-not-found" />
      <error statusCode="500" redirect="/internal-server-error" />
    </customErrors>
  </system.web>

et system.webServerpour les erreurs de rattrapage qui ont été interceptées par IIS et qui n'ont pas fait leur chemin vers le cadre asp.net

 <system.webServer>
    <httpErrors errorMode="DetailedLocalOnly">
      <remove statusCode="404"/>
      <error statusCode="404" path="/page-not-found" responseMode="Redirect"/>
      <remove statusCode="500"/>
      <error statusCode="500" path="/internal-server-error" responseMode="Redirect"/>
  </system.webServer>

Dans le dernier si vous vous inquiétez au sujet de la réponse du client puis modifiez le responseMode="Redirect"pour responseMode="File"et servir un fichier html statique, puisque celui - ci affiche une page amicale avec un code 200 de réponse.


0

Dans web.config, ajoutez ceci sous la balise system.webserver comme ci-dessous,

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

et ajouter un contrôleur comme,

public class ErrorController : Controller
{
    //
    // GET: /Error/
    [GET("/Error/NotFound")]
    public ActionResult NotFound()
    {
        Response.StatusCode = 404;

        return View();
    }

    [GET("/Error/ErrorPage")]
    public ActionResult ErrorPage()
    {
        Response.StatusCode = 500;

        return View();
    }
}

et ajouter leurs opinions respectées, cela fonctionnera certainement, je suppose pour tous.

Cette solution, je l'ai trouvée à partir de: Neptune Century

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.