Обработка исключений Блокировка приложения, обработчик исключений, работающий в ASP.NET, не может вызвать Response.End () - PullRequest
4 голосов
/ 26 июня 2009

Используя .NET 3.5, ASP.NET, Enterprise Library 4.1 Блоки обработки и регистрации исключений, я написал собственный обработчик исключений для отображения стандартной страницы ошибок, как показано ниже:

[ConfigurationElementType(typeof(CustomHandlerData))]
public class PageExceptionHandler : IExceptionHandler {

    public PageExceptionHandler(NameValueCollection ignore) {
    }

    public Exception HandleException(Exception ex, Guid handlingInstanceID) {

        HttpResponse response = HttpContext.Current.Response;
        response.Clear();
        response.ContentEncoding = Encoding.UTF8;
        response.ContentType = "text/html";
        response.Write(BuildErrorPage(ex, handlingInstanceID));
        response.Flush();
        //response.End(); // SOMETIMES DOES NOT WORK

        return ex;
    }
}

Это вызывается из политики обработки исключений:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <configSections>
    <section name="exceptionHandling" type="Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.Configuration.ExceptionHandlingSettings, Microsoft.Practices.EnterpriseLibrary.ExceptionHandling, Version=4.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
  </configSections>
  <exceptionHandling>
    <exceptionPolicies>
      <add name="Top Level">
        <exceptionTypes>
          <add type="System.Exception, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
            postHandlingAction="None" name="Exception">
            <exceptionHandlers>
              <add logCategory="General" eventId="0" severity="Error" title="Application Error"
                formatterType="Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.TextExceptionFormatter, Microsoft.Practices.EnterpriseLibrary.ExceptionHandling, Version=4.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
                priority="0" useDefaultLogger="false" type="Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.Logging.LoggingExceptionHandler, Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.Logging, Version=4.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
                name="Log Full Details" />
              <add type="PageExceptionHandler, Test, Culture=neutral, PublicKeyToken=null"
                name="Display Error Page" />
            </exceptionHandlers>
          </add>
        </exceptionTypes>
      </add>
    </exceptionPolicies>
  </exceptionHandling>
</configuration>

Все работает нормально, за исключением того, что страница ошибки была включена в разметку, отображаемую в момент возникновения ошибки. Я думал, что добавление response.End() в обработчик исключений решит эту проблему. Это произошло, но затем я заметил, что иногда ASP.NET выдает ThreadAbortException при попытке завершить поток ответа. Это единственный тип исключения, который не может быть перехвачен и проигнорирован. Хмм! Может кто-нибудь сказать мне:

  1. При каких условиях ASP.NET выдает ThreadAbortException при попытке завершить поток ответа?
  2. Есть ли более безопасная альтернатива response.End(), которую я мог бы использовать?
  3. Если нет, есть ли способ определить, является ли поток ответа "готовым к выполнению" перед вызовом response.End()?

РЕДАКТИРОВАТЬ - Больше контекста

Цель этого кода - максимально упростить добавление обработки ошибок в стиле EHAB в веб-приложение. У меня есть пользовательский IHttpModule, который необходимо добавить в файл web.config. В событии Application_Error модуля он вызывает ExceptionPolicy.HandleException. Политика обработки исключений должна быть настроена на вызов класса PageExceptionHandler, описанного выше. Вся эта процедура включает в себя только небольшое количество XML-конфигурации, никаких дополнительных файлов и лишних строк кода в потребляющем веб-приложении.

На самом деле, код в его нынешнем виде работает нормально. Однако в некоторых ситуациях желательно иметь явный блок перехвата в коде веб-приложения, который напрямую вызывает политику обработки исключений. Этот сценарий - то, что не работает должным образом. Я хотел бы единственное решение, которое работает при всех возможных методах его вызова. У меня складывается впечатление, что было бы намного проще, если бы EHAB не использовался, но, к сожалению, он используется во всем нашем коде и обеспечивает так много других преимуществ, что я предпочел бы сохранить его.

EDIT - Результаты тестирования

Я создал тестовый комплект для проверки кода шестью различными способами:

  1. Запись на текущую страницу HttpResponse напрямую.
  2. Создание объекта исключения и прямой вызов ExceptionPolicy.HandleException(ex, "Top Level").
  3. Бросок объекта исключения, его перехват и вызов ExceptionPolicy.HandleException(ex, "Top Level") в блоке catch.
  4. Создание объекта исключения и разрешение вызова события Page_Error ExceptionPolicy.HandleException(ex, "Top Level").
  5. Создание объекта исключения и разрешение вызова события Application_Error ExceptionPolicy.HandleException(ex, "Top Level").
  6. Бросок объекта исключения и разрешение моего пользовательского IHttpModule вызова класса ExceptionPolicy.HandleException(ex, "Top Level").

Результаты теста для каждого метода без кода для завершения ответа после записи разметки страницы с ошибкой:

  • Методы 1, 2, 3 - разметка страницы с ошибкой в ​​сочетании с разметкой тестовой страницы.
  • Методы 4, 5, 6 - разметка страницы с ошибкой заменила разметку тестовой страницы (желаемый результат).

Результаты теста для каждого метода при вызове HttpContext.Current.ApplicationInstance.CompleteRequest:

  • Методы 1, 2, 3 - ошибка разметки страницы в сочетании с разметкой тестовой страницы.
  • Методы 4, 5, 6 - разметка страницы с ошибкой заменила разметку тестовой страницы (желаемый результат).

Результаты теста для каждого метода при вызове HttpContext.Current.Response.End:

  • Методы 1, 5, 6 - разметка страницы с ошибкой заменила разметку тестовой страницы (желаемый результат).
  • Методы 2, 3, 4 - разметка страницы ошибки заменила разметку тестовой страницы, но EHAB выдает ExceptionHandlingException дважды .

Результаты теста для каждого метода, когда был вызван HttpContext.Current.Response.End, но заключены в блок try ... catch:

  • Методы 5, 6 - разметка страницы с ошибкой заменила разметку тестовой страницы (желаемый результат).
  • Методы 1, 3, 4 - разметка страницы ошибки заменила разметку тестовой страницы, но ASP.NET выдает ThreadAbortException, который перехватывается и поглощается блоком перехвата.
  • Метод 2 - ошибка разметки страницы заменила разметку тестовой страницы, но я получаю ThreadAbortException , а также два ExceptionHandlingException с.

Это смешной набор поведений. : - (

Ответы [ 6 ]

2 голосов
/ 08 июля 2009

Посмотрите на ELMAH .
На ELMAH есть много статей и инструкций, просто Google или Bing для этого :-)

Преимущества
+ В нем регистрируется почти все
+ Уведомление по электронной почте
+ Удаленный просмотр ошибок

Я бы предложил использовать это и перенаправить на страницу ошибки по умолчанию, как упоминал Ариэль.

0 голосов
/ 21 августа 2009

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

Раствор 1

Никаких изменений в текущей архитектуре вообще. Глобальный обработчик исключений уже работает как нужно. Если по какой-либо причине в коде необходим явный блок catch (например, для отображения ошибок проверки в форме ввода), я скажу разработчикам явно вызывать Response.End, например,

try {
    // ...
} catch(Exception ex) {
    if(ExceptionPolicy.HandleException(ex, "Top Level")) {
        throw;
    }
    Response.End();
}

Решение 2

Перестаньте пытаться перезаписать текущий поток ответов и выполните подлинное перенаправление на страницу ошибки. Это прекрасно работает со всеми шестью методами испытаний. Следующая проблема заключалась в том, чтобы найти способ сделать это, что потребовало бы минимально возможных изменений в потребляющем проекте и забрал все грязные детали.

Шаг 1. Добавьте в проект файл-заполнитель ErrorPage.ashx, который содержит только ссылку на стандартный класс обработчика веб-страниц и не содержит кода.

<%@ WebHandler Class="WebApplicationErrorPage" %>

Шаг 2. Переадресация на эту страницу в классе PageExceptionHandler с использованием сеансового ключа для передачи всех необходимых данных.

public Exception HandleException(Exception ex, Guid handlingInstanceID) {

    HttpResponse response = HttpContext.Current.Response;
    HttpSessionState session = HttpContext.Current.Session;

    session["ErrorPageText"] = BuildErrorPage(ex, handlingInstanceID);
    response.Redirect(errorPageUrl, false);

    return ex;
}

Шаг 3 - Сделайте класс WebApplicationErrorPage действительно тупым обработчиком HTTP, который просто пишет в поток ответов.

public class WebApplicationErrorPage : IHttpHandler, IReadOnlySessionState {

    public bool IsReusable {
        get {
            return true;
        }
    }

    public void ProcessRequest(HttpContext context) {

        response.Clear();
        response.ContentEncoding = Encoding.UTF8;
        response.ContentType = "text/html";
        response.Write((string) context.Session["ErrorPageText"]);
        response.Flush();
    }
}

Заключение

Я думаю, что я буду придерживаться решения 1, потому что оно не включает в себя изменение и изменение существующей архитектуры (реализация решения 2 для реального будет намного сложнее, чем приведенные выше фрагменты кода). Я отправлю решение 2 для возможного использования в другом месте.

0 голосов
/ 19 августа 2009

Это может помочь: http://msdn.microsoft.com/en-us/library/cyayh29d.aspx

В частности «Ваш код очистки должен быть в предложении catch или предложении finally, потому что исключение ThreadAbortException перебрасывается системой в конце предложения finally или в конце предложения catch, если предложение finally отсутствует .

Вы можете предотвратить сброс системы системой, вызвав метод Thread .. ::. ResetAbort. Однако делать это следует только в том случае, если ваш собственный код вызвал исключение ThreadAbortException. «

Обратите внимание, что Response.End и Response.Redirect могут генерировать исключение ThreadAbortException при определенных условиях.

0 голосов
/ 10 июля 2009

Попробуйте использовать Application_Error в Global.asax для захвата любых необработанных исключений, генерируемых любой страницей в вашем приложении.

0 голосов
/ 26 июня 2009

Мой подход к обработке исключений ASP.net всегда заключался в том, чтобы перехватить его, записать в журнал и затем перенаправить на общую страницу ошибки со стандартным сообщением об ошибке (конечный пользователь не будет заботиться об исключениях или отслеживании стека). Вы уже зарегистрировали исключение в своей политике перед обработчиком, поэтому, возможно, все, что вам нужно сделать, это перенаправить ответ на страницу ошибки и переместить туда логику BuildErrorPage. Вы можете передать handleInstanceId в строке запроса.

0 голосов
/ 26 июня 2009

Взгляните на это объяснение Microsoft . В нем говорится, что это относится к ASP.Net 1.1, но я думаю, что это все еще применяется в 2.0. По сути, вы должны вместо этого вызвать метод HttpContext.Current.ApplicationInstance.CompleteRequest.

Кроме того, я не уверен, почему вы не можете поймать ThreadAbortException - почему вы не можете сделать что-то вроде

try {
    // code
} catch (ThreadAbortException)
{
    //do nothing
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...