Application_Error не срабатывает, когда customerrors = "On"

У меня есть код в global.asax файле Application_Error, который выполняется при возникновении ошибки, и отправляет мне сообщения об ошибке.

void Application_Error(object sender, EventArgs e)
{
    var error = Server.GetLastError();

    if (error.Message != "Not Found")
    {
        // Send email here...
    }

}

Это отлично работает, когда я запускаю его в Visual Studio, однако, когда я публикую наш сервер, событие Application_Error не срабатывает.

После некоторого тестирования я могу запустить стрельбу Application_Error, когда я установил customErrors="Off", однако установка его обратно на customErrors="On" останавливает событие от повторного запуска.

Может ли кто-нибудь предположить, почему Application_Error не будет стрелять, когда customErrors включены в web.config?

Ответ 1

UPDATE
Поскольку этот ответ действительно дает решение, я не буду его редактировать, но я нашел гораздо более чистый способ решения этой проблемы. Подробнее см. мой другой ответ.

Оригинальный ответ:
Я понял, почему метод Application_Error() не вызывается...

Global.asax.cs

public class MvcApplication : System.Web.HttpApplication
{
    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
        filters.Add(new HandleErrorAttribute()); // this line is the culprit
    }
...
}

По умолчанию (при создании нового проекта) приложение MVC имеет некоторую логику в файле Global.asax.cs. Эта логика используется для отображения маршрутов и регистрации фильтров. По умолчанию он регистрирует только один фильтр: фильтр HandleErrorAttribute. Когда customErrors включены (или через удаленные запросы, когда он установлен на RemoteOnly), HandleErrorAttribute указывает MVC искать представление об ошибке и никогда не вызывает метод Application_Error(). Я не мог найти документацию об этом, но это объясняется в этом ответе на programers.stackexchange.com.

Чтобы получить метод ApplicationError(), вызванный для каждого необработанного исключения, просто удалите строку, которая регистрирует фильтр HandleErrorAttribute.

Теперь проблема заключается в следующем: как настроить customErrors, чтобы получить то, что вы хотите...

В разделе customErrors по умолчанию используется redirectMode="ResponseRedirect". Вы также можете указать атрибут defaultRedirect для маршрута MVC. Я создал ErrorController, который был очень простым и изменил мой web.config, чтобы выглядеть так...

web.config

<customErrors mode="RemoteOnly" redirectMode="ResponseRedirect" defaultRedirect="~/Error">
  <error statusCode="404" redirect="~/Error/PageNotFound" />
</customErrors>

Проблема с этим решением заключается в том, что он перенаправляет 302 URL-адреса вашей ошибки, а затем эти страницы отвечают кодом состояния 200. Это приводит к тому, что Google индексирует страницы с ошибками, что плохо. Он также не очень соответствует спецификации HTTP. То, что я хотел сделать, не было перенаправлено и переопределить исходный ответ с помощью собственных пользовательских представлений об ошибках.

Я попытался изменить redirectMode="ResponseRewrite". К сожалению, эта опция не поддерживает маршруты MVC, только статические HTML-страницы или ASPX. Сначала я попытался использовать статическую HTML-страницу, но код ответа был еще 200, но, по крайней мере, он не перенаправлял. Затем я получил идею от этого ответа...

Я решил отказаться от MVC для обработки ошибок. Я создал Error.aspx и PageNotFound.aspx. Эти страницы были очень простыми, но у них была одна магия...

<script type="text/C#" runat="server">
    protected override void OnLoad(EventArgs e)
    {
        base.OnLoad(e);
        Response.StatusCode = (int) System.Net.HttpStatusCode.InternalServerError;
    }
</script>

Этот блок сообщает, что страница будет обслуживаться с правильным кодом состояния. Грубо, на странице PageNotFound.aspx я использовал HttpStatusCode.NotFound. Я изменил свой web.config таким образом...

<customErrors mode="RemoteOnly" redirectMode="ResponseRewrite" defaultRedirect="~/Error.aspx">
  <error statusCode="404" redirect="~/PageNotFound.aspx" />
</customErrors>

Все отлично работало!

Резюме:

  • Удалите строку: filters.Add(new HandleErrorAttribute());
  • Использовать метод Application_Error() для регистрации исключений
  • Используйте customErrors с ResponseRewrite, указывая на страницы ASPX.
  • Сделать страницы ASPX ответственными за свои коды статуса ответа

Есть несколько минусов, которые я заметил с этим решением.

  • Страницы ASPX не могут разделить разметку с шаблонами Razor, мне пришлось переписать стандартную разметку заголовка и нижнего колонтитула веб-сайта для последовательного внешнего вида.
  • Доступ к страницам *.aspx можно получить напрямую, нажав на их URL-адреса.

Есть проблемы вокруг этих проблем, но я не был им достаточно озабочен, чтобы выполнить любую дополнительную работу.

Надеюсь, это поможет всем!

Ответ 2

Я решил это, создав ExceptionFilter и зарегистрировав там ошибку вместо Application_Error. Все, что вам нужно сделать, это добавить звонок в RegisterGlobalFilters

log4netExceptionFilter.cs

using System
using System.Web.Mvc;

public class log4netExceptionFilter : IExceptionFilter
{
    public void OnException(ExceptionContext context)
    {
        Exception ex = context.Exception;
        if (!(ex is HttpException)) //ignore "file not found"
        {
            //Log error here
        }
    }
}

Global.asax.cs

public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
    filters.Add(new log4netExceptionFilter()); //must be before HandleErrorAttribute
    filters.Add(new HandleErrorAttribute());
}

Ответ 3

Я нашел article, в котором описывается гораздо более чистый способ создания пользовательских страниц ошибок в веб-приложении MVC3, что не мешает способность регистрировать исключения.

Решение состоит в использовании элемента <httpErrors> раздела <system.webServer>.

Я настроил свой Web.config так...

<httpErrors errorMode="DetailedLocalOnly" existingResponse="Replace">
  <remove statusCode="404" subStatusCode="-1" />
  <remove statusCode="500" subStatusCode="-1" />
  <error statusCode="404" path="/Error/NotFound" responseMode="ExecuteURL" />
  <error statusCode="500" path="/Error" responseMode="ExecuteURL" />
</httpErrors>

Я также настроил customErrors на mode="Off" (как было предложено в статье).

Это заставляет ответы превышать действия ErrorController. Вот этот контроллер:

public class ErrorController : Controller
{
    public ActionResult Index()
    {
        return View();
    }

    public ActionResult NotFound()
    {
        return View();
    }
}

Представления очень прямые, я просто использовал стандартный синтаксис Razor для создания страниц.

Этого должно быть достаточно для использования пользовательских страниц ошибок с MVC.

Мне также нужно было зарегистрировать Исключения, поэтому я украл Отметить решение с помощью пользовательского ExceptionFilter...

public class ExceptionPublisherExceptionFilter : IExceptionFilter
{
    public void OnException(ExceptionContext exceptionContext)
    {
        var exception = exceptionContext.Exception;
        var request = exceptionContext.HttpContext.Request;
        // log stuff
    }
}

Последнее, что вам нужно, - зарегистрировать фильтр исключения в файле Global.asax.cs:

public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
    filters.Add(new ExceptionPublisherExceptionFilter());
    filters.Add(new HandleErrorAttribute());
}

Это похоже на гораздо более чистое решение, чем мой предыдущий ответ, и работает так же хорошо, насколько я могу судить. Мне это особенно нравится, потому что мне не хотелось, чтобы я боролся против структуры MVC; это решение действительно использует его!

Ответ 4

В случае ASP.NET MVC5 используйте

public class ExceptionPublisherExceptionFilter : IExceptionFilter
    {
        private static Logger _logger = LogManager.GetCurrentClassLogger();
        public void OnException(ExceptionContext exceptionContext)
        {
            var exception = exceptionContext.Exception;
            var request = exceptionContext.HttpContext.Request;
            // HttpException httpException = exception as HttpException;
            // Log this exception with your logger
            _logger.Error(exception.Message);
        }
    }

Вы можете найти его в папке FilterConfig.cs App_Start.

Ответ 5

Мне нравится ответ Mark с помощью ExceptionFilter, но другой вариант, если у вас есть все ваши контроллеры от одного и того же базового контроллера, - это просто переопределить OnException в вашем базовом контроллере. Вы можете вести регистрацию и отправлять по электронной почте. Это имеет то преимущество, что вы можете использовать любые зависимости, которые вы уже ввели в ваш базовый контроллер, с помощью контейнера IoC.

Вы все еще можете использовать свой IoC с IExceptionFilter, но немного сложнее настроить привязки.

Ответ 6

Насколько я знаю, вы передаете контроль на страницу, указанную в параметре url, и здесь будет указано ваше уведомление о событии, а не Application_Error

<customErrors defaultRedirect="myErrorPage.aspx"
              mode="On">
</customErrors>

Много информации можно найти здесь: http://support.microsoft.com/kb/306355

Ответ 7

Чтобы обойти это, я оставил отключенные пользовательские устройства и обрабатывал все ошибки из события Application_Error в global.asax. Это немного сложно с MVC, поскольку я не хотел возвращать 301 перенаправление, я хотел бы вернуть соответствующие коды ошибок. Более подробную информацию можно посмотреть в моем блоге на http://www.wduffy.co.uk/blog/using-application_error-in-asp-net-mvcs-global-asax-to-handle-errors/, но последний код указан ниже...

void Application_Error(object sender, EventArgs e)
{
    var error = Server.GetLastError();
    var code = (error is HttpException) ? (error as HttpException).GetHttpCode() : 500;

    if (code != 404)
    {
            // Generate email with error details and send to administrator
    }

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

    string path = Request.Path;
    Context.RewritePath(string.Format("~/Errors/Http{0}", code), false);
    IHttpHandler httpHandler = new MvcHttpHandler();
    httpHandler.ProcessRequest(Context);
    Context.RewritePath(path, false);
}

И вот контроллер

public class ErrorsController : Controller
{

    [HttpGet]
    public ActionResult Http404(string source)
    {
            Response.StatusCode = 404;
            return View();
    }

    [HttpGet]
    public ActionResult Http500(string source)
    {
            Response.StatusCode = 500;
            return View();
    }

}

Ответ 8

Эта запись в блоге помогла мне:

http://asp-net.vexedlogic.com/2011/04/23/asp-net-maximum-request-length-exceeded/

Если вы используете IIS 7.0 или более поздней версии, вы можете изменить файл Web.config для обработки слишком больших запросов. Есть некоторые оговорки, но вот пример:

<system.webServer>
  <security>
    <requestFiltering>
      <requestLimits maxAllowedContentLength="1048576" />
    </requestFiltering>
  </security>
  <httpErrors errorMode="Custom" existingResponse="Replace">
    <remove statusCode="404" subStatusCode="13" />
    <error statusCode="404" subStatusCode="13" prefixLanguageFilePath="" path="/UploadTooLarge.htm" responseMode="Redirect" />
  </httpErrors>
</system.webServer>

Ниже приведены дополнительные сведения об этих элементах конфигурационного файла:

http://www.iis.net/ConfigReference/system.webServer/security/requestFiltering/requestLimits

Код состояния 404.13 определяется как "Content Length Too Large". Важно отметить, что maxAllowedContentLength указывается в байтах. Это отличается от параметра maxRequestLength, который вы найдете в разделе <system.web>, который указан в килобайтах.

<system.web>
  <httpRuntime maxRequestLength="10240" />
</system.web>

Также обратите внимание, что атрибут path должен быть абсолютным путем, если responseMode равен Redirect, поэтому добавьте имя виртуального каталога, если это необходимо. Информативные ответы Джесси Уэбба показывают, как это сделать с помощью responseMode="ExecuteURL", и я бы подумал, что этот подход тоже будет хорошо работать.

Этот подход не работает, если вы работаете с Visual Studio Development Server (Cassini, веб-сервер, интегрированный в Visual Studio). Я предполагаю, что это будет работать в IIS Express, но я не тестировал это.

Ответ 9

У меня была та же проблема, когда Application_Error() не попадал. Я все испробовал, пока, наконец, я не перешел через то, что происходило. У меня был специальный код в событии ELMAH, которое добавляло JSON к отправляемому электронному письму, и там была нулевая ошибка!

Фиксирование внутренней ошибки позволило продолжить код в событии Application_Error(), как и ожидалось.