问题描述
|
我有一个带有许多ASCX控件的ASPX大页面。如果控件引发异常,则应记录该异常并仅隐藏自身。所有其他控件仍应呈现。
如何处理从前端文件引发的单个ASCX的异常(ASCX,而不是背后的代码)?例如:试图使用
<%= MethodThatThrowsANullReferenceException() %>
语法引用无效属性的控件。
显然,在Global.asax中使用通用错误处理程序方法无法解决问题。我需要处理单个控件上的异常。
解决方法
使您的所有UserControls都从自定义基类继承,例如:
public class CustomUserControl : UserControl
{
protected override void Render(HtmlTextWriter writer)
{
try
{
base.Render(writer);
}
catch (Exception e)
{
writer.Write(\"Could not load control. Sad face.\");
}
}
}
,我尝试覆盖Render方法,但这并不涵盖所有异常。
例如,如果在Page_Init,Load或Render期间引发某种异常,则将阻止页面呈现。
我们有不同的人员在不同的模块(控件)上工作,这些模块可以加载到单个页面中,但是我对每个模块的代码质量不承担任何责任,因此即使不是最佳实践,我也需要捕获异常并确定哪个控件无法加载,因为应用程序不能仅仅因为一个模块就失败了。
对于当今并不常见的这种特殊情况,自定义,应用程序或页面错误处理都无法正常工作。
我提出的解决方案是:
每个模块(Control.ascx)需要加载到Page(aspx)时,都包含在ModuleShell中,该Shell具有一些特定功能,并负责帮助Page_Error处理正常工作。
此ModuleShell不会尝试捕获其失败的子控件的异常,而仅在其能够正确加载的情况下才在每个生命周期阶段进行监视。
这是其片段:
protected void Page_Init(object sender,EventArgs e)
{
Modules.CurrentState = _mod;
}
protected void Page_Load(object sender,EventArgs e)
{
Modules.CurrentState = _mod;
}
protected void Page_PreRender(object sender,EventArgs e)
{
Modules.CurrentState = _mod;
}
模块是用于存储会话变量的静态类。
CurrentState是ModuleShell用来记录其名称的变量。
我们唯一获得的aspx中的Page_Error,将获得尝试加载的最后记录的ModuleShell。由于任何异常都会停止页面呈现,因此最后一个将其名称记录到主页面的ModuleShell可能是无法正确加载的页面。
这是一个草率的解决方案,但对模块开发人员是透明的。
,AFAIK,这是不可能的(至少以一种简单的方式)。
使用ASP.NET的丰富自定义错误处理:
当错误发生时,例外是
提出或抛出。有三种
可能会诱捕和处理的层
例外:
try...catch...finally
街区
Page
级或or5ѭ
水平。前两个发生的正确
在页面的代码中,以及
应用程序事件保存在内部
global.asax
。
Exception
对象包含
有关错误的信息,以及
事件在整个过程中冒泡
层,它进一步包裹
详情。粗略地说,
Application_Error
例外包含
Page_Error
例外
在ѭ7的基础上扩展,
在第一时间触发了起泡
地点。
如果用户控件内发生异常,则将其捕获到用户控件内的唯一方法是在try { } catch { }
块内进行处理。
我认为可以捕获到这样的异常的最低级别是下一个级别-Page_Error
级别:
protected void Page_Error(object sender,EventArgs e)
{
// the control which throw an exception
var control = (Control)sender;
control.Visible = false;
// the exception itself
var exception = Server.GetLastError();
Context.ClearError();
}
Context.ClearError()
方法甚至可以防止异常再冒泡到Application_Error
。但是不幸的是,随后引发了未处理的异常,页面处理停止,并且错误处理开始了。这意味着页面的渲染也将停止(因此,您不会在导致此异常的控件旁边看到控件)。
,您可以将尝试调用的方法包装在自己的方法中,该方法将返回相同的类型,但带有try {} catch {}
块。
public string MethodWrapper()
{
try
{
return MethodThatCanThrowException();
}
catch (SomeExceptionType)
{
//log exception
return string;
}
}
,Jim Bolla提出的一种选择是使所有控件都从同一基类继承,并在Render方法中使用Try / Catch。这本来可以。不幸的是,我正在处理的许多控件已经具有不同的基类。
此解决方案为我工作:
我向每个用户控件添加了以下代码(我确定可以将其进一步重构以减少重复):
#region Error Handling
public event EventHandler ControlCrashed;
private static readonly log4net.ILog Logger = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
protected override void RenderChildren(HtmlTextWriter writer)
{
try
{
base.RenderChildren(writer);
}
catch (Exception exc)
{
Logger.Error(\"Control failed to load. Hiding control. Message: \" + exc,exc);
//Ignore and hide the control.
this.Visible = false;
if (ControlCrashed != null)
ControlCrashed(this,EventArgs.Empty);
}
}
#endregion
这样可以解决任何前端渲染问题。如果父页面希望显示一个不错的错误消息,则可以处理ControlCrashed事件。