web-dev-qa-db-fra.com

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 un paramètre de chaîne error. Utilisation ou modification du code ci-dessous . Quel est le meilleur moyen/approprié de transmettre les données au contrôleur d’erreur pour 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?
    }
}
97
aherrick

Au lieu de créer un nouvel itinéraire pour cela, vous pouvez simplement rediriger vers votre contrôleur/action et transmettre les informations via une chaîne de requête. 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));
    }

Ensuite, votre contrôleur recevra ce que vous voulez:

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

Votre approche présente des inconvénients. Soyez très prudent avec les boucles dans ce type de traitement des erreurs. Autre chose, puisque vous passez par le pipeline asp.net pour gérer un 404, vous allez créer un objet de session pour tous ces hits. Cela peut être un problème (performance) pour les systèmes très utilisés.

99
andrecarlucci

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

Puis 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 regroupe tous les 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);
            }
        }
    }
27
user248913

J'ai trouvé une solution au problème ajax notée 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
Jozef Krchňavý

Je me suis déjà heurté à l'idée de centraliser une routine globale de traitement des erreurs dans une application MVC. J'ai un post sur les forums ASP.NET .

En gros, il gère toutes vos erreurs d’application dans le fichier global.asax sans avoir besoin d’un contrôleur d’erreur, ni de décorer avec l’attribut [HandlerError], ni de manipuler le noeud customErrors dans le fichier web.config. 

9
Jack Hsu

Un meilleur moyen de gérer les erreurs dans MVC consiste peut-être à appliquer l'attribut HandleError à votre contrôleur ou à votre action et à mettre à jour le fichier Shared/Error.aspx pour faire ce que vous voulez. L'objet Model de cette page comprend une propriété Exception, ainsi que ControllerName et ActionName.

6
Brian

Application_Error ayant un problème avec les demandes Ajax. Si erreur gérée dans Action qui a appelé par Ajax - cela affichera votre vue d'erreur dans le conteneur résultant.

4
Victor Gelmutdinov

Brian, Cette approche fonctionne très bien pour les requêtes non-Ajax, mais comme l'a indiqué Lion_cl, si vous rencontrez une erreur lors d'un appel Ajax, votre vue Share/Error.aspx (ou votre vue de page d'erreur personnalisée) sera renvoyée à Appelant Ajax - l'utilisateur ne sera PAS redirigé vers la page d'erreur.

3
coderob

Ce n'est peut-être pas la meilleure solution pour MVC ( https://stackoverflow.com/a/9461386/5869805 )

Vous trouverez ci-dessous comment rendre une vue dans Application_Error et l'écrire dans la réponse http. Vous n'avez pas besoin d'utiliser la redirection. Cela empêchera une deuxième demande au serveur, ainsi 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
burkay

Utilisez le code suivant pour rediriger sur la page de route . Utilisez exception.Message instide of exception. La chaîne de requête d'exception Coz donne une erreur si elle étend la longueur de la chaîne de requête.

routeData.Values.Add("error", exception.Message);
// clear error on server
Server.ClearError();
Response.RedirectToRoute(routeData.Values);
0
Swapnil Malap