ASP.NET MVC2 异常处理机制中令人费解的HTTP 500错误
在Visual Studio 2010 BETA2所集成之ASP.NET MVC应用程序中,可以使用HandleErrorAttribute来捕获并处理异常。
但在默认情况下,用于显示异常的视图(Error.aspx)必须应用母版页(Site.Master),否则,如果尝试使用一个普通的视图来显示ASP.NET MVC应用程序异常信息,会报告“HTTP 500内部服务器错误”:
异常信息显示视图必须应用母版页,这是一个很让人郁闷的限制。
为什么会有这个限制?
使用Reflector反汇编HandleErrorAttribute的OnException()方法,此方法将会在控制器中的Action方法有异常抛出时被ASP.NET MVC自动调用:
public virtual void OnException(ExceptionContext filterContext)
{
//……代码略
//生成用于显示异常信息的ViewResult
filterContext.Result = new ViewResult { ViewName = this.View,
MasterName = this.Master, ViewData = new ViewDataDictionary<HandleErrorInfo>(model),
TempData = filterContext.Controller.TempData };
filterContext.ExceptionHandled = true;
filterContext.HttpContext.Response.Clear();
filterContext.HttpContext.Response.StatusCode = 500;
filterContext.HttpContext.Response.TrySkipIisCustomErrors = true;
}
}
可以看到,在生成用于显示异常信息的ViewResult之后,设置filterContext的ExceptionHandled=true,这将通知后继的异常捕获过滤器(如果有的话):本应用程序抛出的异常已经被处理过了,其他的人可以不用理会这个异常了。
比较令人费解的是紧接着后面有3句代码引发了HTTP 500错误。
经测试发现,如果ViewResult实例化的视图对象应用了母版页时,ASP.NET MVC会“吃掉”这一HTTP 500错误。
然而,如果要实例化的视图对象是独立的,上述3句代码就“真”的引发了HTTP 500错误。
ASP.NET MVC的开发者为什么要加入这3句代码?笔者百思不得其解。
尽管不知道原因,但我们可以通过编写一个自定义异常处理类来“屏蔽”掉上述3句引发麻烦的代码。其思路是这样的:
从HandleErrorAttribute派生一个自定义类MyErrorHandleAttribute,重写其基类的OnException方法。然后,给其添加一个bool类型的UseMasterPage属性,当其为false时,跳过上面3句引发麻烦的代码。
MyErrorHandleAttribute的使用方法与HandleErrorAttribute一模一样,只是多了一个UseMasterPage属性:
[MyErrorHandle(ExceptionType=typeof(NullReferenceException),
View="ShowErrorUseViewPage",UseMasterPage=false)]
[MyErrorHandle(ExceptionType=typeof(ApplicationException),
View = "ShowErrorUseMasterPage",UseMasterPage=true)]
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
public ActionResult ThrowNullReferenceException()
{
throw new NullReferenceException();
}
public ActionResult ThrowApplicationException()
{
throw new ApplicationException();
}
}
其中ShowErrorUseViewPage.aspx是普通视图,没有应用母版。
以下是MyErrorHandleAttribute类的代码:
public class MyErrorHandle : HandleErrorAttribute
{
public bool UseMasterPage
{
get;
set;
}
public override void OnException(ExceptionContext filterContext)
{
if (filterContext == null)
{
throw new ArgumentNullException("filterContext");
}
if (!filterContext.ExceptionHandled && filterContext.HttpContext.IsCustomErrorEnabled)
{
Exception innerException = filterContext.Exception;
if ((new HttpException(null, innerException).GetHttpCode() == 500) && this.ExceptionType.IsInstanceOfType(innerException))
{
string controllerName = (string)filterContext.RouteData.Values["controller"];
string actionName = (string)filterContext.RouteData.Values["action"];
HandleErrorInfo model = new HandleErrorInfo(filterContext.Exception, controllerName, actionName);
filterContext.Result = new ViewResult { ViewName = this.View, MasterName = this.Master, ViewData = new ViewDataDictionary<HandleErrorInfo>(model), TempData = filterContext.Controller.TempData };
filterContext.ExceptionHandled = true;
if (UseMasterPage)
{
filterContext.HttpContext.Response.Clear();
filterContext.HttpContext.Response.StatusCode = 500;
filterContext.HttpContext.Response.TrySkipIisCustomErrors = true;
}
}
}
}
}
除了加方框的代码,其余部分都是原样复制于Reflector反汇编出来的代码。
将保存了上述代码的“MyErrorHandleAttribute.cs”文件添加到ASP.NET MVC项目中,就可以在控制器中使用此自定义的异常处理类了。
在以下环境中MyErrorHandleAttribute均工作正常:
(1)使用VS2010 BETA2开发和调试ASP.NET MVC2项目:
(2)将ASP.NET MVC2项目发布到IIS 7.5(应用ASP.NET 4.0集成模式)上以HTTP方式访问。
需要指出的是,在笔者的测试中,发现现有版本的HandleErrorAttribute并非始终不能使用独立的视图对象,当使用VS2010 BETA2开发时,HTTP 500错误有时会发生有时又不会发生,实在令人费解。
我想,这是不是ASP.NET MVC2当前版本的一个BUG?
也许等到VS2010正式版时,ASP.NET MVC开发小组的工程师会解决这一问题。