ASP.NET MVC Користувальницька помилка Поводження Application_Error Global.asax?


108

У мене є базовий код для визначення помилок у моєму додатку 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?
    }
}

Відповіді:


104

Замість того, щоб створити для цього новий маршрут, ви можете просто переспрямувати на контролер / дію та передати інформацію за допомогою запиту. Наприклад:

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, ви створите об’єкт сеансу для всіх цих звернень. Це може бути проблемою (продуктивністю) для широко використовуваних систем.


Коли ви говорите "будьте уважні до циклу", що саме ви маєте на увазі? Чи є кращий спосіб впоратися з цим типом переадресації помилок (якщо припустити, що це широко використовувана система)?
aherrick

4
Під циклом я маю на увазі, коли ви маєте помилку на своїй сторінці помилок, то ви будете перенаправлені на свою сторінку помилок знову і знову ... (наприклад, ви хочете занести свою помилку в базу даних, і вона вниз).
andrecarlucci

125
Перенаправлення на помилки йде всупереч архітектурі Інтернету. URI повинен залишатися таким же, коли сервер відповідає на правильний код статусу HTTP, щоб клієнт знав точний контекст відмови. Реалізація HandleErrorAttribute.OnException або Controller.OnException є кращим рішенням. І якщо вони не вдаються, зробіть Server.Transfer ("~ / помилка") у Global.asax.
Asbjørn Ulsberg

1
@Chris, Це прийнятно, але не найкраща практика. Тим більше, що його часто переспрямовують на файл ресурсу, який подається з кодом статусу HTTP 200, що дозволяє клієнту вірити, що все пішло нормально.
Asbjørn Ulsberg

1
Мені довелося додати <httpErrors errorMode = "Детально" /> до web.config, щоб зробити цю роботу на сервері.
Jeroen K

28

Щоб відповісти на початкове запитання "як правильно передавати маршрутизовані дані в контролер помилок?":

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

9

Я знайшов рішення для проблеми з 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");
    }
}

Під час реалізації цього я отримав таку помилку: "System.Web.HttpRequest" не містить визначення для "IsAjaxRequest". Ця стаття має рішення: stackoverflow.com/questions/14629304 / ...
Джуліан Dormon

8

Раніше я боровся з ідеєю централізації глобальної процедури обробки помилок у додатку MVC. У мене є повідомлення на форумах ASP.NET .

По суті, він обробляє всі ваші програми в глобальній.asax, не потребуючи контролера помилок, прикрашаючи [HandlerError]атрибут або поводячись з customErrorsвузлом в web.config.


6

Можливо, кращим способом вирішення помилок у MVC є застосування атрибуту HandleError до контролера чи дії та оновлення файлу Shared / Error.aspx, щоб робити те, що ви хочете. Об'єкт Model на цій сторінці включає властивість Exception, а також ControllerName та ActionName.


1
Як тоді ви впораєтеся з 404помилкою? оскільки для цього немає призначеного контролера / дії?
Дементік

Прийнята відповідь включає 404s. Цей підхід корисний лише для 500 помилок.
Брайан

Можливо, ви повинні це відредагувати у своїй відповіді. Perhaps a better way of handling errorsсхоже на всі помилки, а не лише на 500.
Дементік

4

Application_Error, що має проблеми з запитами Ajax. Якщо помилка обробляється в Action, яку викликає Ajax - вона відобразить Ваш Погляд помилок всередині отриманого контейнера.


4

Це може бути не найкращим способом для MVC ( https://stackoverflow.com/a/9461386/5869805 )

Нижче описано, як ви подаєте подання у 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>
}

Це найкращий спосіб ІМО. Саме те, що я шукав.
Стів Харріс

@SteveHarris рада, що це допомогло! :)
буркай

3

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


0

Використовуйте наступний код для переадресації на сторінці маршруту. Використовуйте виняток. Повідомлення про виняток. Рядок запиту винятку Coz видає помилку, якщо вона збільшує довжину рядка запиту.

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

-1

У мене є проблема з таким підходом до обробки помилок: У разі 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 на System.Web.Mvc.ViewResult.FindView (контекст ControllerContext) ........ ............

Так

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

httpException завжди є нульовим, тоді 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 тощо)

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.