在使用ASP.NET时,常常使用Page的错误事件Error进行错误捕捉和处理。这种方式可以集中处理所有异常,这种方式有利有弊。集中处理的好处就不用啰嗦了,这里只说明一下这种方式的局限,就是当页面中的某个控件发生异常之后,整个页面执行都会中断,然后处理异常,这样一来,页面就无法显示。
在实际开发中,常常有这样的需求,即页面是由多个相对独立的控件组成,其中一个控件的错误不能影响到其它控件的正常显示。这就需要在控件内部捕捉错误,并自行处理错误,然而控件基类并没有提供这样的错误捕捉功能。如何用简单有效方法来实现呢?
其实我们可以实现一个基类,并把所有在控件生命期中会调用到的方法都封装起来,这样只要继承这个控件,就可以方便地实现在控件内部自行捕捉错误的功能。请看下面的代码:
public abstract class AbstractControl: Control

{
/**//// <summary>
/// 异常栈
/// </summary>
public Stack Exceptions
{
get
{
if (exceptions == null)
{
exceptions = new Stack();
}
return exceptions;
}
}
protected override void CreateChildControls()
{
try
{
CreateChildControlsByCatchedException();
}
catch (HttpUnhandledException)
{
throw;
}
catch (Exception ex)
{
Exceptions.Push(ex);
}
}

/**//// <summary>
/// 创建子控件(已进行异常捕捉处理)
/// </summary>
protected virtual void CreateChildControlsByCatchedException()
{
}

/**//// <summary>
///
/// </summary>
/// <param name="e"></param>
protected override void OnPreRender(EventArgs e)
{
try
{
OnPreRenderByCatchedException(e);
}
catch (HttpUnhandledException)
{
throw;
}
catch (Exception ex)
{
Exceptions.Push(ex);
}
}

/**//// <summary>
/// 呈现前事件(已进行错误捕捉处理)
/// </summary>
/// <param name="e"></param>
protected virtual void OnPreRenderByCatchedException(EventArgs e)
{
base.OnPreRender (e);
}

/**//// <summary>
/// 72
/// </summary>
/// <param name="e"></param>
protected virtual void DesigningOnPreRenderByCatchedException(EventArgs e)
{
}

/**//// <summary>
/// 呈现
/// </summary>
/// <param name="writer"></param>
protected override void Render(HTMLTextWriter writer)
{
if (Exceptions.Count > 0)
{
while (Exceptions.Count > 0 )
{
Exception ex = (Exception) Exceptions.Pop();
RenderException(writer, ex);
}
return;
}
try
{
RenderByCatchedException(writer);
}
catch (HttpUnhandledException)
{
throw;
}
catch (Exception ex)
{
RenderException(writer, ex);
}
}

/**//// <summary>
/// 呈现(已进行错误捕捉处理)
/// </summary>
/// <param name="writer"></param>
protected virtual void RenderByCatchedException(HTMLTextWriter writer)
{
base.Render (writer);
}

/**//// <summary>
/// 呈现异常
/// </summary>
/// <param name="writer"></param>
/// <param name="ex"></param>
private void RenderException(HTMLTextWriter writer, Exception ex)
{
writer.AddAttribute(HTMLTextWriterAttribute.Title, BuildExceptionInfomation(ex));
writer.AddStyleAttribute("font-weight", "700");
writer.AddStyleAttribute("color", "#f00");
writer.AddStyleAttribute("border", "1px solid #ddd");
writer.AddStyleAttribute("cursor", "pointer");
writer.AddStyleAttribute("padding", "0px 3px 0px 3px");
writer.AddStyleAttribute("background-color", "#ffe");
writer.RenderBeginTag(HTMLTextWriterTag.Span);
writer.Write("!");
writer.RenderEndTag();
}

/**//// <summary>
/// 生成异常信息
/// </summary>
/// <param name="ex"></param>
/// <returns></returns>
private string BuildExceptionInfomation(Exception ex)
{
StringBuilder sb = new StringBuilder();
sb.Append(ex.Message);
sb.Append(Environment.NewLine);
sb.Append(ex.GetType().FullName);
sb.Append(Environment.NewLine);
sb.Append(ex.StackTrace);
return sb.ToString();
}

/**//// <summary>
/// 中断程序的执行
/// </summary>
/// <param name="ex"></param>
protected virtual void Interrupt(Exception ex)
{
throw new HttpUnhandledException(ex.Message, ex);
}
}
上面的代码只重载了OnPreRender、Render和CreateChildControls三个方法,实际上还有OnInit、OnLoad等,可以视实际需要而重载,这样重载之后,所有错误都被捕捉,并存放在错误栈中,并在呈现时将错误以某种格式呈现在界面上。注意,继承AbstractControl基类的控件应重载如RenderByCatchedException之类的方法。
如果某些错误不希望被捕捉,而是直接抛出到页面上,这时候还可以调用Interrupt方法来将错误直接抛出到页面上,并中断整个页面的执行。