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

124

У меня есть код в событии 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?

WDuffy
источник
У меня точно такая же проблема. Я также нашел этот ТАК вопрос: stackoverflow.com/questions/3713939/…, который предлагает перевести сервер IIS7 в классический режим. К сожалению, для нас это не вариант. У кого-нибудь есть лучшие решения?
Джесси Уэбб
Вот еще один связанный вопрос, и его ответы (ни один из которых не принят) предлагают вообще не использовать Application_Error () ... stackoverflow.com/questions/1194578/…
Джесси Уэбб
@Gweebz Я опубликовал ответ о том, как я это обошел, но до сих пор не нашел надежной документации о том, почему у меня такое поведение.
WDuffy
Я добавил ответ, который объясняет, почему Application_Error()метод не вызывается. Я также объяснил свое окончательное решение.
Джесси Уэбб
Настоятельно рекомендую эту статью , чтобы заставить работать пользовательские ошибки.
ThomasArdal

Ответы:

133

ОБНОВЛЕНИЕ
Поскольку этот ответ предоставляет решение, я не буду его редактировать, но я нашел более чистый способ решения этой проблемы. Подробнее см. Мой другой ответ ...

Исходный ответ:
я понял, почему 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 искать представление Error и никогда не вызывает Application_Error()метод. Мне не удалось найти документацию по этому поводу, но это объясняется в этом ответе на programmers.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
+1! Действительно хорошее решение, но каковы могут быть последствия смешивания MVC со страницами aspx?
Diego
2
У нас это есть в PROD уже пару месяцев, и я не обнаружил отрицательного воздействия. Единственная «ошибка» заключалась в том, что нам пришлось изменить наш CI и выполнить развертывание для выполнения задачи AspCompile MSBUILD, потому что MVC в этом не нуждается, но когда мы добавили файлы .as , они потребовали этого. Могут быть и другие проблемы, но они еще не всплыли ...
Джесси Уэбб
Я пытался использовать этот подход в MVC4, и, по-видимому, он работает для меня, только когда у меня есть <customErrors mode="Off" />. Удаление filters.Add(new HandleErrorAttribute());или нет не имеет никакого эффекта.
Grzegorz Sławecki
на странице aspx вы можете добавить вызов ajax для вызова представления, которое вы хотите показать. Сохранение необходимости дублировать код
Майк
71

Я решил это, создав 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());
}
отметка
источник
6
+1 Если этот ответ работает, мне кажется, что это самый элегантный и, возможно, предполагаемый способ (в рамках MVC) для обработки ошибок.
Мэтт Хэмсмит, 01
2
Это отлично работает для меня и намного аккуратнее, чем принятый здесь ответ. Ницца!
Alex Warren
3
Это сработает для решения проблемы регистрации исключений. Как вы совместили это с отображением настраиваемых страниц ошибок?
Джесси Уэбб,
3
Я попробовал это, и он работал хорошо, за исключением случая 404. Для 404-х он не будет отображать представление Errors.cshtml, это даст мне только YSoD. Если пользовательские коды 404 не требуются, это решение определенно чище!
Джесси Уэбб
1
Мне это нравится. Работает с CustomErrors = On
Illidan
36

Я нашел статью, в которой описывается гораздо более чистый способ создания пользовательских страниц ошибок в веб-приложении 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; это решение фактически использует его!

Джесси Уэбб
источник
6
Стоит отметить, что я думаю, что это решение работает только в IIS 7 и новее; элемент httpErrors был добавлен совсем недавно.
Джесси Уэбб
Этот ответ не сработал для меня, это заставило меня, это игра меня ужасно синего цвета: HTTP Error 500.0 - Internal Server Error The page cannot be displayed because an internal server error has occurred. экран ошибки, а не моя запрошенная /Error/Indexстраница
Серж Саган
@SerjSagan Я только что попробовал это в новом проекте, чтобы проверить шаги, и он отлично работает. Вы используете IIS, IIS Express или VS Dev Server? Это не будет работать в VS Dev Server. Когда мой проект был настроен на использование VS Dev Server, а не IIS, я заметил ошибки желтого экрана вместо пользовательских страниц ошибок.
Джесси Уэбб,
1
@SerjSagan Но похоже, что вы получаете ошибки синего экрана IIS, в отличие от классических ошибок желтого экрана. Это заставляет меня предположить, что вы используете какую-то форму IIS. Если да, прочтите статью, на которую я ссылался в первом предложении, особенно в последнем разделе, озаглавленном: «Несколько важных заметок». Там написано:To able to overwrite global settings in global IIS settings (file: C:\Windows\System32\inetsrv\config \applicationHost.config) should be: <section name="httpErrors" overrideModeDefault="Allow" />
Джесси Уэбб,
3
@SerjSagan Если вы измените errorMode на DetailLocalOnly или Custom, ваша страница будет отображаться.
Daniel P
8

В случае 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папки.

разработчик
источник
1

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

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

Тим Харди
источник
0

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

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

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

ChrisBint
источник
Спасибо, Крис, я прочитал документацию, и в ней говорится, что Application_Error будет вызываться при возникновении необработанной ошибки (чего я ожидаю) и что если Server.ClearError () не будет вызываться, секция customErrors web.config будет последней точкой обработки. Однако включение customErrors - это то, что останавливает запуск Application_Error.
WDuffy
В документации, на которую вы ссылаетесь, говорится о приложении ASP.NET, и похоже, что веб-приложения MVC3 ведут себя по-другому.
Джесси Уэбб,
0

Чтобы обойти это, я оставил клиентские ошибки отключенными и обработал все ошибки из события 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();
    }

}
WDuffy
источник
Я пробовал ваше решение, но у меня оно не сработало. Со строками Response.StatusCode = ###;он показывал встроенные страницы ошибок MVC в C:\inetpub\custerr\en-US. Мне также не понравилась идея вручную вызывать HttpHandlers или контроллеры из моего метода Application_Error (). Я рад, что вы нашли решение своей проблемы, я знаю, какие головные боли это доставило мне.
Джесси Уэбб
0

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

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 определяется как «Слишком большая длина содержимого». Важно отметить, что 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, но я этого не тестировал.

Дэн Ягнов
источник
0

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

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

Мэтт Кемп
источник