ASP.NET MVC Custom Error Handling Application Error Global.asax?

j'ai un code de base pour déterminer les erreurs dans mon application MVC. Actuellement dans mon projet j'ai un contrôleur appelé Error avec les méthodes d'action HTTPError404() , HTTPError500() , et General() . Ils acceptent tous le paramètre error . Utiliser ou modifier le code ci-dessous. Quelle est la meilleure/bonne façon de transmettre les données au contrôleur des erreurs pour le traitement? Je voudrais avoir une solution robuste que possible.

protected void Application_Error(object sender, EventArgs e)
{
    Exception exception = Server.GetLastError();
    Response.Clear();

    HttpException httpException = exception as HttpException;
    if (httpException != null)
    {
        RouteData routeData = new RouteData();
        routeData.Values.Add("controller", "Error");
        switch (httpException.GetHttpCode())
        {
            case 404:
                // page not found
                routeData.Values.Add("action", "HttpError404");
                break;
            case 500:
                // server error
                routeData.Values.Add("action", "HttpError500");
                break;
            default:
                routeData.Values.Add("action", "General");
                break;
        }
        routeData.Values.Add("error", exception);
        // clear error on server
        Server.ClearError();

        // at this point how to properly pass route data to error controller?
    }
}
95
demandé sur Oualid KTATA 2009-07-23 15:07:04

10 réponses

au lieu de créer une nouvelle route pour cela, vous pouvez simplement rediriger vers votre controller/action et passer l'information via querystring. Par exemple:

protected void Application_Error(object sender, EventArgs e) {
  Exception exception = Server.GetLastError();
  Response.Clear();

  HttpException httpException = exception as HttpException;

  if (httpException != null) {
    string action;

    switch (httpException.GetHttpCode()) {
      case 404:
        // page not found
        action = "HttpError404";
        break;
      case 500:
        // server error
        action = "HttpError500";
        break;
      default:
        action = "General";
        break;
      }

      // clear error on server
      Server.ClearError();

      Response.Redirect(String.Format("~/Error/{0}/?message={1}", action, exception.Message));
    }

alors votre contrôleur recevra ce que vous voulez:

// GET: /Error/HttpError404
public ActionResult HttpError404(string message) {
   return View("SomeView", message);
}

Il y a des compromis avec votre approche. Soyez très prudent avec la boucle dans ce genre de manipulation d'erreur. L'autre chose est que puisque vous traversez le asp.net pipeline pour manipuler un 404, vous allez créer un objet session pour tous ces résultats. Cela peut être un problème (performance) pour les systèmes fortement utilisés.

97
répondu andrecarlucci 2013-03-05 18:01:49

pour répondre à la question initiale" comment passer correctement routedata au contrôleur d'erreur?":

IController errorController = new ErrorController();
errorController.Execute(new RequestContext(new HttpContextWrapper(Context), routeData));

alors dans votre classe ErrorController, implémentez une fonction comme celle-ci:

[AcceptVerbs(HttpVerbs.Get)]
public ViewResult Error(Exception exception)
{
    return View("Error", exception);
}

cela pousse l'exception dans la vue. La page de vue doit être déclarée comme suit:

<%@ Page Language="C#" Inherits="System.Web.Mvc.ViewPage<System.Exception>" %>

et le code pour afficher l'erreur:

<% if(Model != null) { %>  <p><b>Detailed error:</b><br />  <span class="error"><%= Helpers.General.GetErrorMessage((Exception)Model, false) %></span></p> <% } %>

Voici la fonction qui rassemble le tout messages d'exception de l'arborescence des exceptions:

    public static string GetErrorMessage(Exception ex, bool includeStackTrace)
    {
        StringBuilder msg = new StringBuilder();
        BuildErrorMessage(ex, ref msg);
        if (includeStackTrace)
        {
            msg.Append("\n");
            msg.Append(ex.StackTrace);
        }
        return msg.ToString();
    }

    private static void BuildErrorMessage(Exception ex, ref StringBuilder msg)
    {
        if (ex != null)
        {
            msg.Append(ex.Message);
            msg.Append("\n");
            if (ex.InnerException != null)
            {
                BuildErrorMessage(ex.InnerException, ref msg);
            }
        }
    }
26
répondu Tim Cooper 2013-03-05 18:10:27

j'ai trouvé une solution pour la question ajax noté par Lion_cl.

global.asax:

protected void Application_Error()
    {           
        if (HttpContext.Current.Request.IsAjaxRequest())
        {
            HttpContext ctx = HttpContext.Current;
            ctx.Response.Clear();
            RequestContext rc = ((MvcHandler)ctx.CurrentHandler).RequestContext;
            rc.RouteData.Values["action"] = "AjaxGlobalError";

            // TODO: distinguish between 404 and other errors if needed
            rc.RouteData.Values["newActionName"] = "WrongRequest";

            rc.RouteData.Values["controller"] = "ErrorPages";
            IControllerFactory factory = ControllerBuilder.Current.GetControllerFactory();
            IController controller = factory.CreateController(rc, "ErrorPages");
            controller.Execute(rc);
            ctx.Server.ClearError();
        }
    }

ErrorPagesController

public ActionResult AjaxGlobalError(string newActionName)
    {
        return new AjaxRedirectResult(Url.Action(newActionName), this.ControllerContext);
    }

AjaxRedirectResult

public class AjaxRedirectResult : RedirectResult
{
    public AjaxRedirectResult(string url, ControllerContext controllerContext)
        : base(url)
    {
        ExecuteResult(controllerContext);
    }

    public override void ExecuteResult(ControllerContext context)
    {
        if (context.RequestContext.HttpContext.Request.IsAjaxRequest())
        {
            JavaScriptResult result = new JavaScriptResult()
            {
                Script = "try{history.pushState(null,null,window.location.href);}catch(err){}window.location.replace('" + UrlHelper.GenerateContentUrl(this.Url, context.HttpContext) + "');"
            };

            result.ExecuteResult(context);
        }
        else
        {
            base.ExecuteResult(context);
        }
    }
}

AjaxRequestExtension

public static class AjaxRequestExtension
{
    public static bool IsAjaxRequest(this HttpRequest request)
    {
        return (request.Headers["X-Requested-With"] != null && request.Headers["X-Requested-With"] == "XMLHttpRequest");
    }
}
9
répondu Jozef Krchňavý 2011-05-10 15:45:39

je me suis déjà battu avec l'idée de centraliser une routine globale de gestion des erreurs dans une application MVC. J'ai un post sur le ASP.NET forums .

il traite essentiellement toutes vos erreurs d'application dans le global.asax sans avoir besoin d'un contrôleur d'erreur, décorer avec l'attribut [HandlerError] , ou jouer avec le noeud customErrors dans le web.config.

9
répondu Jack Hsu 2014-01-03 19:06:12

peut-être qu'une meilleure façon de gérer les erreurs dans MVC est d'appliquer L'attribut HandleError à votre controller ou action et de mettre à jour le Shared/Error.fichier aspx faire ce que vous voulez. L'objet Model sur cette page inclut une propriété D'Exception ainsi que ControllerName et Actioname.

6
répondu Brian 2009-08-12 21:30:54

Application_Error ayant des problèmes avec les requêtes Ajax. Si l'erreur traitée dans L'Action qui a appelé par Ajax-il affichera votre vue D'erreur à l'intérieur du conteneur résultant.

4
répondu Victor Gelmutdinov 2009-08-18 21:35:57

Brian, Cette approche fonctionne très bien pour les requêtes non-Ajax, mais comme Lion_cl l'a indiqué, si vous avez une erreur lors d'un appel Ajax, votre part/erreur.aspx view (ou votre page d'erreur personnalisée) sera retourné à L'appelant Ajax--l'utilisateur ne sera pas redirigé vers la page d'erreur.

3
répondu coderob 2011-12-13 00:07:25

ce n'est peut-être pas la meilleure façon pour MVC ( https://stackoverflow.com/a/9461386/5869805 )

ci-dessous est la façon de rendre une vue dans Application_Error et de l'écrire dans la réponse http. Vous n'avez pas besoin d'utiliser la redirection. Cela empêchera une deuxième requête au serveur, de sorte que le lien dans la barre d'adresse du navigateur restera le même. Cela peut être bon ou mauvais, cela dépend de ce que vous voulez.

Global.asax.cs

protected void Application_Error()
{
    var exception = Server.GetLastError();
    // TODO do whatever you want with exception, such as logging, set errorMessage, etc.
    var errorMessage = "SOME FRIENDLY MESSAGE";

    // TODO: UPDATE BELOW FOUR PARAMETERS ACCORDING TO YOUR ERROR HANDLING ACTION
    var errorArea = "AREA";
    var errorController = "CONTROLLER";
    var errorAction = "ACTION";
    var pathToViewFile = $"~/Areas/{errorArea}/Views/{errorController}/{errorAction}.cshtml"; // THIS SHOULD BE THE PATH IN FILESYSTEM RELATIVE TO WHERE YOUR CSPROJ FILE IS!

    var requestControllerName = Convert.ToString(HttpContext.Current.Request.RequestContext?.RouteData?.Values["controller"]);
    var requestActionName = Convert.ToString(HttpContext.Current.Request.RequestContext?.RouteData?.Values["action"]);

    var controller = new BaseController(); // REPLACE THIS WITH YOUR BASE CONTROLLER CLASS
    var routeData = new RouteData { DataTokens = { { "area", errorArea } }, Values = { { "controller", errorController }, {"action", errorAction} } };
    var controllerContext = new ControllerContext(new HttpContextWrapper(HttpContext.Current), routeData, controller);
    controller.ControllerContext = controllerContext;

    var sw = new StringWriter();
    var razorView = new RazorView(controller.ControllerContext, pathToViewFile, "", false, null);
    var model = new ViewDataDictionary(new HandleErrorInfo(exception, requestControllerName, requestActionName));
    var viewContext = new ViewContext(controller.ControllerContext, razorView, model, new TempDataDictionary(), sw);
    viewContext.ViewBag.ErrorMessage = errorMessage;
    //TODO: add to ViewBag what you need
    razorView.Render(viewContext, sw);
    HttpContext.Current.Response.Write(sw);
    Server.ClearError();
    HttpContext.Current.Response.End(); // No more processing needed (ex: by default controller/action routing), flush the response out and raise EndRequest event.
}

Vue

@model HandleErrorInfo
@{
    ViewBag.Title = "Error";
    // TODO: SET YOUR LAYOUT
}
<div class="">
    ViewBag.ErrorMessage
</div>
@if(Model != null && HttpContext.Current.IsDebuggingEnabled)
{
    <div class="" style="background:khaki">
        <p>
            <b>Exception:</b> @Model.Exception.Message <br/>
            <b>Controller:</b> @Model.ControllerName <br/>
            <b>Action:</b> @Model.ActionName <br/>
        </p>
        <div>
            <pre>
                @Model.Exception.StackTrace
            </pre>
        </div>
    </div>
}
2
répondu burkay 2017-05-23 12:17:46

utilisez le code suivant pour rediriger sur la page route. L'utilisation d'exception.Message instide d'exception. La chaîne de requête d'exception Coz donne l'erreur si elle étend la longueur de querystring.

routeData.Values.Add("error", exception.Message);
// clear error on server
Server.ClearError();
Response.RedirectToRoute(routeData.Values);
0
répondu Swapnil Malap 2016-05-11 06:53:05

j'ai un problème avec cette approche de gestion des erreurs: Dans le cas du web.config:

<customErrors mode="On"/>

le gestionnaire d'erreurs recherche L'erreur de vue.shtml et l'étape du flux de contrôle vers Application_Error global.asax seulement après exception

Système

.InvalidOperationException: la vue 'Error' ou son maître était pas trouvé ou pas de moteur de vue supporte les emplacements recherchés. Le emplacements suivants ont été recherchés: ~/Views/home/Erreur.aspx ~/Views/home/Erreur.ascx ~ / Views/Shared / Error.aspx ~ / Views / Shared / Error.ascx ~/Views/home/Erreur.cshtml ~/Views/home/Erreur.vbhtml ~/Views/Shared/Erreur.cshtml ~ / Views / Shared / Error.vbhtml à Système.Web.Mvc.ViewResult.FindView (controllercontext context)) ....................

Donc

 Exception exception = Server.GetLastError();
  Response.Clear();
  HttpException httpException = exception as HttpException;

httpException est toujours null alors customErrors mode= " On" :( Il est trompeur Puis <customErrors mode="Off"/> ou <customErrors mode="RemoteOnly"/> les utilisateurs voir customErrors html, Alors customErrors mode=" On "ce code est erroné aussi


un autre problème de ce code que

Response.Redirect(String.Format("~/Error/{0}/?message={1}", action, exception.Message));

page de retour avec le code 302 à la place du code d'erreur réel (402,403 etc)

-1
répondu Александр Шмыков 2017-08-24 09:18:09