[Перевод] Лучшие практические решения обработки исключений в веб-приложениях ASP.NET

суббота, 19 июня 2010, Alexander Honcharuk

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

Сколько раз вы видели сообщение об ошибке, которая не имеет никакого смысла или, по крайней мере обеспечивает некоторую полезную информацию, а еще лучше - сколько раз вы видели известный экран с ошибкой с сообщением исключения и полную трассировку на желтом фоне? Я бы ответил: “Слишком много раз”. Вот почему, между прочим, некоторые мои коллеги были очень заинтересованы в технике обработки исключений и практических решений.

Цель данной статьи дать показать, что обработка исключений с точки зрения пользователя и с точки зрения людей, которые поддерживают приложения, и показать лучшие решения, как осуществлять полезные обработки ошибок в веб-приложениях ASP.NET. Эта статья относится к моим предыдущим статьям CSS Message Boxes for different message types и Create MessageBox user control using ASP.NET and CSS, эти две статьи описывают, как показывать удобные пользователю сообщений.

1. Какая информация должна быть представлена пользователю?

Как я уже говорил раньше, бессмысленные сообщения об ошибках будут сбивать с толку пользователей. Не имея никаких сообщений об ошибках и разрешение приложению остановится вызовет желание никогда не кликать на ссылку, которая указывает на ваше приложение. :) Сообщения типа "Произошла ошибка" или "System.InvalidOperationException: свойство ConnectionString не инициализировано" ничего не значат для конечного пользователя. На случай, когда не знаешь, что точно произошло, должна быть сохранена информация о том, что нужно делать дальше.

Цель полезной обработки исключений, дать понять пользователям , что что-то пошло не так и что дальше они могут использовать приложения, даже если произошла ошибка.

Таким образом, как минимум что вы должны сделать, так это показать пользователю простую страницу с ошибкой. Она должна показать основную информацию о том, что произошло и то, что пользователь может делать дальше. Например, вы можете показать общее сообщение об ошибке и кнопку "Назад", которая может отправить пользователя на предыдущую страницу.

image[1]

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

  1. что случилось
  2. что будет затронуты
  3. что пользователь отсюда может делать
  4. и никакой ценной информации поддержки

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

image[1]

Давайте посмотрим, что нужно еще.

2. Сохранение исключений в логи.

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

Какую информацию следует хранить в журнал исключений?

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

Можно, однако, журнал дополнительной информации, например, проверки подлинности или информация об анонимном пользователе. В Exception Management Architecture Guide на MSDN приводится перечень возможной информации, которая может быть сохранена:

Data Source
Date and time of exception DateTime.Now
Machine name Environment.MachineName
Exception source Exception.Source
Exception type Type.FullName obtained from Object.GetType
Exception message Exception.Message
Exception stack trace Exception.StackTrace—this trace starts at the point the exception is thrown and is populated as it propagates up the call stack.
Call stack Environment.StackTrace—the complete call stack.
Application domain name AppDomain.FriendlyName
Assembly name AssemblyName.FullName, in the System.Reflection namespace
Assembly version Included in the AssemblyName.FullName
Thread ID AppDomain.GetCurrentThreadId
Thread user Thread.CurrentPrincipal in the System.Threading namespace

Чем больше информации вы сохраните в журнал, тем легче будет для разработчиков определить причину ошибки и её исправить.

Где должен хранится журнал исключений?

Вы можете записывать исключения туда, куда хотите: текстовый файл, XML файл или в базу данных. Я всегда использую базы данных, поскольку это позволяет мне легко отслеживать журнал и помечать исключения, которые решены. Если исключение не может быть сохранено в базу данных, из-за проблем с подключением, то можно использовать использован XML файл.

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

image[1]

Этот провайдер может быть одним классом, который будет содержать три метода: LogException - что будет парсить исключения и сохранять всю информацию в таблице, ResolveException - будет удалять исключение из журнала и GetExceptions, который вернет список нерешенных исключений.

Это легкий и простой способ для выявления и исправления ошибок в живом приложении.

3. Как реализовать полезную обработку исключений?

Во-первых, вы должны рассматривать как исключения будут пойманы. Если вы строите многоуровневое приложений, то вам придется ловить исключения среднего уровня классов, и генерировать новые, свои собственые исключения с полезной информацией для клиента.

Для передачи полезной информации клиенту (например, то, что пользователь может делать), вам придется создать специальный класс исключение, который наследуется от класса ApplicationException и добавить в него некоторые свойства. Если вы хотите отправить четыре значения, которые я упоминал раньше, то вам придется добавить еще четыре свойства у ваш класс. Вам также надо будет, переопределить конструктор класса ApplicationException и метод GetObjectData, для разрешения сериализации. Ниже пример пользовательского класса исключений, который назвем MyException.

public class MyException : ApplicationException {

#region Constructors

public MyException(string message): base(message)

{

}

public MyException(string message, Exception inner) : base(message, inner)
{

}

public MyException(string message, Exception inner,string _whatHappened
,string _whatHasBeenAffected
,string _whatActionsCanUserDo
,string _supportInformation): base(message, inner)
{
	whatHappened = _whatHappened;
	whatHasBeenAffected = _whatHasBeenAffected;
	whatActionsCanUserDo = _whatActionsCanUserDo;
	supportInformation = _supportInformation;
}
protected MyException(SerializationInfo info, StreamingContext context)
: base(info, context)
{
	whatHappened = info.GetString("whatHappened");
	whatHasBeenAffected = info.GetString("whatHasBeenAffected");
	whatActionsCanUserDo = info.GetString("whatActionsCanUserDo");
	supportInformation = info.GetString("supportInformation");
}
#endregion #region Methods
public override void GetObjectData(SerializationInfo info, StreamingContext context)
{
	info.AddValue("whatHasBeenAffected", whatHasBeenAffected, typeof(String));
	info.AddValue("whatActionsCanUserDo" ,whatActionsCanUserDo, typeof(String));
	info.AddValue("supportInformation", supportInformation, typeof(String));
	info.AddValue("whatHappened", whatHappened, typeof(String));
	base.GetObjectData(info, context);
}
#endregion #region Properties
private string whatHappened;
private string whatHasBeenAffected;
private string whatActionsCanUserDo;
private string supportInformation;
public string WhatHappened
{
	get { return whatHappened; }
	set { whatHappened = value; }
}
public string WhatHasBeenAffected
{
	get { return whatHasBeenAffected; }
	set { whatHasBeenAffected = value; }
}
public string WhatActionsCanUserDo
{
	get { return whatActionsCanUserDo; }
	set { whatActionsCanUserDo = value; }
}
public string SupportInformation
{
	get { return supportInformation; }
	set { supportInformation = value; }
}
#endregion
}

Поймать ошибки в представлении слоя может быть сделано на уровне приложения (глобальном) или на уровне страницы.

Уровень приложения

Минимальное, что вы должны сделать, это определить собственную страницу ошибки в файле web.config, которая будет по умолчанию отображать информацию об ошибке и контактную информацию. Вы можете определить определенную страницу для каждого кода ошибки, например, 404 - Файл не найден.

<customErrors mode="RemoteOnly" defaultRedirect="~/Error.aspx">
 <error statusCode="404" redirect="404.html"/> <error statusCode="500" redirect="500.html"/>
</customErrors>

Более сложный путь для создания динамической страницы ошибок, которые будут показываться каждый раз, когда возникает ошибка. На этой странице будет вся информация, которая представляет ценность для пользователей. Вы видели её на примере странице в начале данной статьи.
Для этого вы можете использовать Global.asax или класс HttpModule. В обоих случаях этот процесс будет таким же. Вы должны будете взять, последнюю ошибку, которая произошла, записать её в лог (если вы не сделали этого в классе среднего уровня), сохранить её в сессии и перенаправить на страницу с ошибкой.

void Application_Error(
object sender, EventArgs e) 
{
	// Get the last exception that has occurred 
	Exception ex = Server.GetLastError().GetBaseException();
	// You can perform logging here // Store it in the session
	Session["LastException"] = ex;
	// Redirect to Error page Server.Transfer("Error.aspx");
}

Страница с ошибки будет искать исключение в сессии и отображать содержащую информацию. Если в нем нет нужной информации, то будет отображатся сообщение по умолчанию.

protected void Page_Load(object sender, EventArgs e)
{
if (Session != null)
{
if (Session["LastException"] != null)
{
	Exception ex = (Exception)Session["LastException"];

if (ex is MyException)
{
	MyException myex = (MyException)ex;
	lblWhatHappened.Text = myex.WhatHappened;
	lblWhatHasBeenAffected.Text = myex.WhatHasBeenAffected;
	lblWhatYouCanDo.Text = myex.WhatActionsCanUserDo;
	lblSupportInformation.Text = myex.SupportInformation;
}
}
}
}

Уровень страницы

Если по некоторым причинам, вы хотите, чтобы пользователи, оставались на оригинальной форме в случае возникновения ошибки, то можно обрабатывать исключения в обработчике событий Page_Error. Вы можете делать это, если хотите выполнять определенные операции перед выводом сообщений об ошибке пользователю, или если вы хотите, чтобы пользователь продолжал работу немедленно. Вы должны добавить обработчик для Page.Error event вручную в Page_Load.

protected void Page_Load(object sender, EventArgs e)
{ Page.Error += new System.EventHandler(Page_Error);


}
protected void Page_Error(Object sender, EventArgs e)
{
	Exception ex = Server.GetLastError();
	// You can perform logging here        
	// UPDATE 06.06.2008: You'll have to redirect to an error page here
	// strikethroughed code just won't work :)
	if (ex is MyException) 
	{
		MyException myex = (MyException)ex;
		lblWhatHappened.Text = myex.WhatHappened; 
		lblWhatHasBeenAffected.Text = myex.WhatHasBeenAffected;
		lblWhatYouCanDo.Text = myex.WhatActionsCanUserDo; 
		lblSupportInformation.Text = myex.SupportInformation;
	}
}

Сведения об исключении должно быть отражены в верху страницы в правильно отформатированом виде.

Зачем в этом рассказе Try...Catch блоки?

Как вы заметили, я не люблю использовать Try...Catch блоки на стороне клиента. Гораздо проще и более практичный способом – это иметь централизованную обработку исключений.

Тем не менее, я интенсивно их использую в среднем уровне. Я ловлю все исключения в классах верхнего уровня, пеередаю их в MyException, и необходимую информацию отдаю клиенту. Так я контролирую все исключения, которые появляются в верхнем уровне.

4. Как в это вписывается ASP.NET Ajax?

Как вы наверное знаете, исключения, которые происходят во время обратного вызова Ajax представлены в виде окошка предупреждения JavaScript, что для пользователей полная катастрофа. В Дэйва Уорда есть прекрасная статью о том, как обрабатывать Ajax  исключения на стороне клиента. Кратко говоря, целью есть пойсать событие EngRequest так, что б можно было получить доступ к EndRequestEventArgs классу, который предоставляет информацию об приизошедших исключениях.Таким образом, можно устранить JavaScript оповещения и показать полезную информацию собственными сообщениями, как и я показывал в своих предыдущих статьях CSS Message Boxes и MessageBox user control using ASP.NET and CSS.

Однако, если вы хотите иметь возможность делать некоторую обработку исключений на сервере, когда ошибка происходит, то вы можете использовать ScriptManager. Вам надо будет определить обработчик OnAsyncPostBackError в определении ScriptManager.

<asp:ScriptManager ID="ScriptManager1" runat="server" OnAsyncPostBackError="ScriptManager1_AsyncPostBackError" />

Это позволит вам иметь доступ к ошибке на сервере.

protected void ScriptManager1_AsyncPostBackError(object sender, AsyncPostBackErrorEventArgs e)
{
	// do whatever you need to do here
}

Этот пример расширяет статью Дэйва, так что читайте ее первой!

5. Итог

Подводя итог, я повторю основные моменты в обработке исключений:

  1. Вы должны предоставлять пользователю значимые сообщения.
  2. Вы должны сохранять в журнал, как можно больше информации о исключениях и об окружающей среде, нассколько это возможно.
  3. Вы должны иметь специальный класс исключения, который будет использоваться по применению.
  4. Вы можете отобразить сообщение об ошибке либо на пользовательской странице, либо на исходной странице.
  5. Вы должны обрабатывать ошибки, возникающие в ходе работы Ajax вручную.

Я рекомендую вам прочитать следующие статьи:

  1. Exception Management Architecture Guide - MSDN patterns and practices
  2. Error Raising and Handling Guidelines - MSDN articles
  3. Design Guidelines Update: Exception Throwing - Krzysztof Cwalina
  4. ELMAH - Error Logging Modules And Handlers - Simone Busoli
  5. Try/Catch Blocks Can Hurt Performance Significantly - Chinh Do

Оригинал.

Компании из статьи


Microsoft Украина


Сайт:
http://www.microsoft.com/ukr/ua/

Microsoft Украина Украинское подразделение компании Microsoft.

Ищите нас в интернетах!

Комментарии

Свежие вакансии