ASP.NET MVC Пользовательская обработка ошибок Application_Error Global.asax?

У меня есть базовый код для определения ошибок в моем приложении MVC. В настоящее время в моем проекте у меня есть контроллер под названием Error с методами действия HTTPError404(), HTTPError500() и General(). Все они принимают строковый параметр error. Использование или изменение кода ниже. Каков наилучший/правильный способ передачи данных в контроллер ошибок для обработки? Мне бы хотелось, чтобы решение было как можно более надежным.

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?
    }
}

Ответ 1

Вместо того, чтобы создавать новый маршрут для этого, вы можете просто перенаправить на свой контроллер/действие и передать информацию через querystring. Например:

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

Затем ваш контроллер получит все, что вы хотите:

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

Есть некоторые компромиссы с вашим подходом. Будьте очень осторожны с циклом обработки ошибок. Другое дело, что, поскольку вы проходите через конвейер asp.net для обработки 404, вы создадите объект сеанса для всех этих ударов. Это может быть проблемой (производительностью) для сильно используемых систем.

Ответ 2

Чтобы ответить на начальный вопрос "как правильно передать routedata в контроллер ошибок?":

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

Затем в вашем классе ErrorController выполните такую ​​функцию:

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

Это вызывает исключение в представлении. Страница просмотра должна быть объявлена ​​следующим образом:

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

И код для отображения ошибки:

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

Вот функция, которая собирает все сообщения исключения из дерева исключений:

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

Ответ 3

Я боролся с идеей централизации глобальной процедуры обработки ошибок в приложении MVC раньше. У меня есть сообщение на форумах ASP.NET.

В основном он обрабатывает все ваши ошибки приложения в global.asax без необходимости в контроллере ошибок, украшая атрибутом [HandlerError] или возиться с customErrors node в web.config.

Ответ 4

Я нашел решение для проблемы ajax, отмеченное 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");
    }
}

Ответ 5

Возможно, лучший способ обработки ошибок в MVC - применить атрибут HandleError к вашему контроллеру или действию и обновить файл Shared/Error.aspx, чтобы делать то, что вы хотите. Объект Model на этой странице содержит свойство Exception, а также имя_контроллера и имя_замены.

Ответ 6

Ошибка Application_Error с запросами Ajax. Если в Action Action произошла ошибка, вызванная Ajax - она ​​отобразит ваш Error View внутри результирующего контейнера.

Ответ 7

Брайан, Этот подход отлично подходит для запросов, отличных от Ajax, но, как сказал Lion_cl, если у вас есть ошибка во время вызова Ajax, ваше представление Share/Error.aspx(или ваше пользовательское представление страницы ошибки) будет возвращено абоненту Ajax - пользователь НЕ будет перенаправлен на страницу с ошибкой.

Ответ 8

Это может быть не лучший способ для MVC (fooobar.com/questions/57482/...)

Ниже показано, как вы визуализируете представление в Application_Error и записываете его в ответ http. Вам не нужно использовать перенаправление. Это предотвратит второй запрос на сервер, поэтому ссылка в адресной строке браузера останется такой же. Это может быть хорошо или плохо, это зависит от того, что вы хотите.

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.
}

Просмотр

@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>
}

Ответ 9

Используйте следующий код для перенаправления на странице маршрута. Используйте exception.Message исключение. Строка запроса исключения Coz дает ошибку, если она расширяет длину запроса.

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

Ответ 10

У меня проблема с этим методом обработки ошибок: В случае web.config:

<customErrors mode="On"/>

Обработчик ошибок - это поиск в представлении Error.shtml и шаг потока управления в Application_Error global.asax только после исключения

System.InvalidOperationException: представление "Ошибка" или его мастер было не найден или нет, механизм просмотра поддерживает найденные местоположения. были обысканы следующие местоположения: ~/Views/home/Error.aspx ~/Views/home/Error.ascx ~/Views/Shared/Error.aspx ~/Views/Shared/Error.ascx ~/Views/home/Error.cshtml ~/Views/home/Error.vbhtml ~/Views/Shared/Error.cshtml ~/Views/Shared/Error.vbhtml at System.Web.Mvc.ViewResult.FindView(Контекст ControllerContext)....................

Итак,

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

httpException всегда имеет значение null customErrors mode = "On" :( Это вводит в заблуждение Затем <customErrors mode="Off"/> или <customErrors mode="RemoteOnly"/> пользователи видят customErrors html,  Тогда customErrors mode = "On" этот код также неверен


Другая проблема этого кода, что

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

Вернуть страницу с кодом 302 вместо реального кода ошибки (402,403 и т.д.)