Pages d'erreur personnalisées sur asp.net MVC3

144

Je développe un site Web de base MVC3 et je recherche une solution pour gérer les erreurs et rendre des vues personnalisées pour chaque type d'erreur. Imaginez donc que j'ai un contrôleur "Erreur" où son action principale est "Index" (page d'erreur générique) et ce contrôleur aura quelques actions supplémentaires pour les erreurs qui peuvent apparaître à l'utilisateur comme "Handle500" ou "HandleActionNotFound".

Ainsi, chaque erreur qui peut survenir sur le site Web peut être gérée par ce contrôleur "Erreur" (exemples: "Contrôleur" ou "Action" introuvable, 500, 404, dbException, etc.).

J'utilise le fichier Sitemap pour définir les chemins du site Web (et non l'itinéraire).

Cette question a déjà été répondue, ceci est une réponse à Gweebz

Ma dernière méthode applicaiton_error est la suivante:

protected void Application_Error() {
//while my project is running in debug mode
if (HttpContext.Current.IsDebuggingEnabled && WebConfigurationManager.AppSettings["EnableCustomErrorPage"].Equals("false"))
{
    Log.Logger.Error("unhandled exception: ", Server.GetLastError());
}
else
{
    try
    {
        var exception = Server.GetLastError();

        Log.Logger.Error("unhandled exception: ", exception);

        Response.Clear();
        Server.ClearError();
        var routeData = new RouteData();
        routeData.Values["controller"] = "Errors";
        routeData.Values["action"] = "General";
        routeData.Values["exception"] = exception;

        IController errorsController = new ErrorsController();
        var rc = new RequestContext(new HttpContextWrapper(Context), routeData);
        errorsController.Execute(rc);
    }
    catch (Exception e)
    {
        //if Error controller failed for same reason, we will display static HTML error page
        Log.Logger.Fatal("failed to display error page, fallback to HTML error: ", e);
        Response.TransmitFile("~/error.html");
    }
}
}
John Louros
la source
Quels devraient être les paramètres dans le fichier web.config pour prendre en charge cela? Vous n'incluriez probablement aucun paramètre httperrors?
philbird
forums.asp.net/p/1782402/4894514.aspx/ ... a quelques bons conseils comme IE n'affichera pas votre page d'erreur si elle est inférieure à 512 octets
RickAndMSFT

Réponses:

201

Voici un exemple de la façon dont je gère les erreurs personnalisées. Je définis un ErrorsControlleravec des actions gérant différentes erreurs HTTP:

public class ErrorsController : Controller
{
    public ActionResult General(Exception exception)
    {
        return Content("General failure", "text/plain");
    }

    public ActionResult Http404()
    {
        return Content("Not found", "text/plain");
    }

    public ActionResult Http403()
    {
        return Content("Forbidden", "text/plain");
    }
}

puis je m'abonne au Application_Errorin Global.asaxet j'appelle ce contrôleur:

protected void Application_Error()
{
    var exception = Server.GetLastError();
    var httpException = exception as HttpException;
    Response.Clear();
    Server.ClearError();
    var routeData = new RouteData();
    routeData.Values["controller"] = "Errors";
    routeData.Values["action"] = "General";
    routeData.Values["exception"] = exception;
    Response.StatusCode = 500;
    if (httpException != null)
    {
        Response.StatusCode = httpException.GetHttpCode();
        switch (Response.StatusCode)
        {
            case 403:
                routeData.Values["action"] = "Http403";
                break;
            case 404:
                routeData.Values["action"] = "Http404";
                break;
        }
    }

    IController errorsController = new ErrorsController();
    var rc = new RequestContext(new HttpContextWrapper(Context), routeData);
    errorsController.Execute(rc);
}
Darin Dimitrov
la source
4
Juste un petit mot. Puisque je voulais rendre une vue dans chaque cas (404, 500, etc.) sur chaque ActionResult, j'ai renvoyé une vue. Cependant, j'ai fait un essai autour du contenu Application_Error et en cas d'échec, une page HTML statique est renvoyée. (Je peux poster le code si quelqu'un le désire)
John Louros
4
Je ne peux pas obtenir le rendu des vues rasoir à l'aide de cette solution sur MVC3. return View (modèle) par exemple n'obtient qu'un écran vide.
Extrakun
2
Ajout de TrySkipIisCustomErrors pour le corriger pour IIS7 intégré. Voir stackoverflow.com/questions/1706934/…
Pavel Savara
1
@ajbeaven, Executeest une méthode définie dans l' IControllerinterface. Cela ne peut pas être protégé. Regardez mon code plus attentivement: IController errorsController = new ErrorsController();et notez le type de errorsControllervariable sur lequel j'invoque la Executeméthode. C'est de type IControllerdonc rien ne vous empêche d'appeler cette méthode. Et d'ailleurs Executeétait protégé dans la classe Controller également dans MVC 3, il n'y a donc pas de changement à cet égard.
Darin Dimitrov
2
Correction en spécifiant explicitement le type de contenu de la réponse:Response.ContentType = "text/html";
ajbeaven
6

Vous pouvez également le faire dans le fichier Web.Config. Voici un exemple qui fonctionne dans IIS 7.5.

     <system.webServer>
          <httpErrors errorMode="DetailedLocalOnly" defaultResponseMode="File">
                <remove statusCode="502" subStatusCode="-1" />
                <remove statusCode="501" subStatusCode="-1" />
                <remove statusCode="412" subStatusCode="-1" />
                <remove statusCode="406" subStatusCode="-1" />
                <remove statusCode="405" subStatusCode="-1" />
                <remove statusCode="404" subStatusCode="-1" />
                <remove statusCode="403" subStatusCode="-1" />
                <remove statusCode="401" subStatusCode="-1" />
                <remove statusCode="500" subStatusCode="-1" />
                <error statusCode="500" path="/notfound.html" responseMode="ExecuteURL" />
                <error statusCode="401" prefixLanguageFilePath="" path="/500.html" responseMode="ExecuteURL" />
                <error statusCode="403" prefixLanguageFilePath="" path="/403.html" responseMode="ExecuteURL" />
                <error statusCode="404" prefixLanguageFilePath="" path="/404.html" responseMode="ExecuteURL" />
                <error statusCode="405" prefixLanguageFilePath="" path="/405.html" responseMode="ExecuteURL" />
                <error statusCode="406" prefixLanguageFilePath="" path="/406.html" responseMode="ExecuteURL" />
                <error statusCode="412" prefixLanguageFilePath="" path="/412.html" responseMode="ExecuteURL" />
                <error statusCode="501" prefixLanguageFilePath="" path="/501.html" responseMode="ExecuteURL" />
                <error statusCode="502" prefixLanguageFilePath="" path="/genericerror.html" responseMode="ExecuteURL" />
           </httpErrors>
</system.webServer>
Brett Allred
la source
3

Je vois que vous avez ajouté une valeur de configuration pour EnableCustomErrorPageet que vous vérifiez également IsDebuggingEnabledsi vous souhaitez exécuter ou non votre gestion des erreurs.

Puisqu'il existe déjà une <customErrors/>configuration dans ASP.NET (qui est destinée exactement à cet effet), il est plus facile de dire simplement:

    protected void Application_Error()
    {
        if (HttpContext.Current == null) 
        {
                // errors in Application_Start will end up here                
        }
        else if (HttpContext.Current.IsCustomErrorEnabled)
        {
                // custom exception handling
        }
    }

Ensuite, dans la configuration, vous mettez <customErrors mode="RemoteOnly" />ce qui est sûr à déployer comme ça, et lorsque vous avez besoin de tester votre page d'erreur personnalisée, vous la définissez sur<customErrors mode="On" /> que vous puissiez vérifier qu'elle fonctionne.

Notez que vous devez également vérifier si HttpContext.Currentest null car une exception dans Application_Startcette méthode sera toujours sa cette méthode bien qu'il n'y ait pas de contexte actif.

Simon_Weaver
la source
2

Vous pouvez afficher une page d'erreur conviviale avec le code d'état http correct en implémentant le module User Friendly Exception Handling de Jeff Atwood avec une légère modification du code d'état http. Cela fonctionne sans aucune redirection. Bien que le code date de 2004 (!), Il fonctionne bien avec MVC. Il peut être entièrement configuré dans votre web.config, sans aucune modification du code source du projet MVC.

La modification requise pour renvoyer le statut HTTP d'origine plutôt qu'un 200statut est décrite dans cet article de forum connexe .

Fondamentalement, dans Handler.vb, vous pouvez ajouter quelque chose comme:

' In the header...
Private _exHttpEx As HttpException = Nothing

' At the top of Public Sub HandleException(ByVal ex As Exception)...
HttpContext.Current.Response.StatusCode = 500
If TypeOf ex Is HttpException Then
    _exHttpEx = CType(ex, HttpException)
    HttpContext.Current.Response.StatusCode = _exHttpEx.GetHttpCode()
End If
Martin_W
la source
0

J'utilise MVC 4.5 et j'avais des problèmes avec la solution de Darin. Remarque: la solution de Darin est excellente et je l'ai utilisée pour trouver ma solution. Voici ma solution modifiée:

protected void Application_Error(object sender, EventArgs e)
{           
var exception = Server.GetLastError();
var httpException = exception as HttpException;
Response.StatusCode = httpException.GetHttpCode();

Response.Clear();
Server.ClearError();


if (httpException != null)
{
    var httpContext = HttpContext.Current;

    httpContext.RewritePath("/Errors/InternalError", false);

    // MVC 3 running on IIS 7+
    if (HttpRuntime.UsingIntegratedPipeline)
    {
        switch (Response.StatusCode)
        {
            case 403:
                httpContext.Server.TransferRequest("/Errors/Http403", true);
                break;
            case 404:
                httpContext.Server.TransferRequest("/Errors/Http404", true);
                break;
            default:
                httpContext.Server.TransferRequest("/Errors/InternalError", true);
                break;
        }
    }
    else
    {
        switch (Response.StatusCode)
        {
            case 403:
                httpContext.RewritePath(string.Format("/Errors/Http403", true));
                break;
            case 404:
                httpContext.RewritePath(string.Format("/Errors/Http404", true));
                break;
            default:
                httpContext.RewritePath(string.Format("/Errors/InternalError", true));
                break;
        }

        IHttpHandler httpHandler = new MvcHttpHandler();
        httpHandler.ProcessRequest(httpContext);
    }
}
}
MVCdragon
la source
2
Quels problèmes aviez-vous avec la solution de Darin?
Kenny Evitt
Vous n'avez pas décrit le problème que vous avez rencontré qui a suscité une réponse concurrente.
ivanjonas