web-dev-qa-db-fra.com

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

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

  1. Activation des erreurs personnalisées dans le fichier 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é HandleErrorAttribute en tant que filtre d'action global dans la classe FilterConfig 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 immédiatement.

  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 n’est pas tout ce qu’il ya à afficher des erreurs personnalisées lorsque je lis le code dans la méthode OnException de la classe HandleErrorAttribute et qu’il n’en traite que 500.

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

236
Water Cooler v2

Ma configuration actuelle (sur MVC3, mais je pense que cela s'applique toujours) repose sur le fait d'avoir un ErrorController, aussi 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 exactement comme vous les implémentez. J'ai tendance cependant à ajouter un peu de logique pour afficher la trace de la pile et les informations d'erreur si l'application est en mode débogage. Donc, 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>
343
Pablo Romeo

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

La vue 'Erreur' ou son maître n'a pas été trouvé ou aucun moteur de vue ne prend en charge l'emplacement recherché.

Pour se débarrasser de cela, enlever la ligne

 filters.Add(new HandleErrorAttribute());

dans FilterConfig.cs

39
Machinegon

Je fais quelque chose qui nécessite moins de codage que les autres solutions affiché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 (simplifiés, mais ils peuvent contenir:

@{ 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 besoin habituellement.

20
coderpro

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
    {
    }
}
12
maxspan

Il semble y avoir un certain nombre d'étapes mélangées. Je vais mettre en avant ce que j'ai fait à partir de zéro.

  1. Créer le contrôleur ErrorPage

    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. Dans App_Start, ouvrez FilterConfig.cs et commentez le filtre de traitement 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 entrées <customerErrors> suivantes sous System.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 afficher 404.

9
VictorySaber

En me basant sur la réponse publiée par maxspan, j'ai mis en place un minimum exemple de projet sur GitHub montrant toutes les parties actives.

Fondamentalement, nous ajoutons simplement une méthode Application_Error à global.asax.cs pour intercepter l’exception et nous donner l’occasion de rediriger (ou plus). correctement, demande de transfert ) 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();
    }

}

Error page View:

<!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 de 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 de cette approche est que l’utilisation de la chaîne de requête permet de fournir des informations sur les exceptions à la page d’erreur cible.

6
user3380909

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 correctement sur les serveurs de développement locaux.

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 traitement d'erreur personnalisé.

2
DCShannon

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

~/Views/Home/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
ADM-IT

Vous pouvez obtenir des erreurs qui fonctionnent correctement sans pirater le fichier global.cs, sans manipuler HandleErrorAttribute, en effectuant Response.TrySkipIisCustomErrors, en connectant Application_Error, ou peu importe:

Dans system.web (comme d'habitude, 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 doivent se comporter comme prévu et vous pouvez utiliser votre ErrorController pour afficher tout ce dont vous avez besoin.

1
Robert Hoffmann

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

Donc, dans system.web pour la mise 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 dans system.webServer pour avoir corrigé les erreurs qui ont été capturées par IIS et ne sont pas parvenues au 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 cas, si vous vous inquiétez de la réponse du client, modifiez le responseMode="Redirect" en responseMode="File" et publiez un fichier HTML statique, car celui-ci affichera une page conviviale avec un code de 200 réponses.

0
OrElse

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 en tant que,

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 points de vue respectés, cela fonctionnera certainement je suppose pour tous.

Cette solution, je l'ai trouvée dans: Neptune Century

0
Dpk-Kumar