一、应用场景
对于B/S应用程序,在部署到正式环境运行的过程中,很有可能出现一些在前期测试过程中没有发现的一些异常或者错误,或者说只有在特定条件满足时才会发生的一些异常,对于使用ASP.NET MVC开发的应用程序站点,在部署到IIS上后,如果开发人员未对程序进行错误处理,那么一旦程序出现未处理的错误或异常,用户将看到一个让人感到及其困惑的错误堆栈跟踪页面,使得站点的用户体验下降,从程序的角度上来说,不做自定义错误处理也不利于程序出问题时的根源查找,因为很多时候有些错误只在特定条件下满足时才重现,一旦错过,可能就需要花大量时间去测试来重现问题,如果此时开发人员有对程序中的运行时异常进行日志记录,那么或许将提供一些有价值的错误根源信息,下面我将向下大家讲解如何实现自定义异常处理并跳转到友好的错误提示页面。
二、异常处理&自定义错误页
1、通过异常过滤器 实现异常处理和自定义错误页
asp.net mvc 提供了 异常过滤器 的方式来实现当执行controller中某个action方法时抛出了未处理的异常时的捕捉,mvc中的异常过滤器是以特性(Attribute)的形式存在的,定义一个自定义异常过滤器只需要两个步骤:
1、定义一个类,继承FilterAttribute类,并实现IExceptionFilter接口 2、应用自定义异常过滤器至指定的 action方法 或 controller类 或 全局应用。
异常过滤器代码
1 using log4net; 2 using System; 3 using System.Collections.Generic; 4 using System.Linq; 5 using System.Web; 6 using System.Web.Mvc; 7 8 namespace Blog20180413.Filters 9 { 10 public class CustomExceptionFilterAttribute : FilterAttribute, IExceptionFilter 11 { 12 //log4net组件,用于日志记录。 13 static readonly ILog log = LogManager.GetLogger(typeof(CustomExceptionFilterAttribute)); 14 public void OnException(ExceptionContext filterContext) 15 { 16 //对捕获到的异常信息进行日志记录,方便开发人员排查问题。 17 log.Error("应用程序异常", filterContext.Exception); 18 19 //跳转到自定义的错误页,增强用户体验。 20 ActionResult result = new ViewResult() { ViewName = "CustomErrorPage" }; 21 filterContext.Result = result; 22 //异常处理结束后,一定要将ExceptionHandled设置为true,否则仍然会继续抛出错误。 23 filterContext.ExceptionHandled = true; 24 } 25 } 26 }
使用异常过滤器
1 using Blog20180413.Filters; 2 using System; 3 using System.Collections.Generic; 4 using System.Linq; 5 using System.Web; 6 using System.Web.Mvc; 7 8 namespace Blog20180413.Controllers 9 { 10 public class TestExceptionHandleController : Controller 11 { 12 [CustomExceptionFilter] 13 public ActionResult Index() 14 { 15 string str = string.Empty; 16 //将抛出转换异常 17 int result = int.Parse(str); 18 return View(); 19 } 20 } 21 }
注意:
第二个步骤中提到,可以将自定义异常过滤器 只应用到 action或者controller,如果只想将指定的异常过滤器以特性的形式应用到指定的一个或者多个controller或者action,而不想应用到所有的controller或者action,那么必须将该异常过滤器继承FilterAttribute类,这是因为mvc框架是通过FilterAttributeFilterProvider.GetFilters来获取标记在指定controller或者action上的异常过滤器特性的,而GetFilters内部逻辑要求必须继承自FilterAttribute类。
如果需要将自定义的异常过滤器应用到所有的controller的action上,那么需要将该自定义异常过滤器注册到全局,代码如下:
1 using Blog20180413.Filters; 2 using System.Web; 3 using System.Web.Mvc; 4 5 namespace Blog20180413 6 { 7 public class FilterConfig 8 { 9 public static void RegisterGlobalFilters(GlobalFilterCollection filters) 10 { 11 filters.Add(new CustomExceptionFilterAttribute()); 12 } 13 } 14 }
2、通过在Global.asax 中定义Application_Error方法 实现异常处理和自定义错误页
上面提到的 自定义异常过滤器只能捕获在执行action方法过程中抛出的异常(即使注册为全局过滤器也只能捕获action方法执行过程中抛出的异常),如果需要捕获更高级别的异常,也就是在请求执行过程中出现的任何异常(如在控制器的构造函数中抛出异常),那么可以使用该种方式,代码如下:
1 using log4net; 2 using log4net.Config; 3 using System; 4 using System.Collections.Generic; 5 using System.IO; 6 using System.Linq; 7 using System.Web; 8 using System.Web.Mvc; 9 using System.Web.Routing; 10 11 namespace Blog20180413 12 { 13 public class MvcApplication : System.Web.HttpApplication 14 { 15 static readonly ILog log = LogManager.GetLogger(typeof(MvcApplication)); 16 protected void Application_Start() 17 { 18 AreaRegistration.RegisterAllAreas(); 19 FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); 20 RouteConfig.RegisterRoutes(RouteTable.Routes); 21 XmlConfigurator.ConfigureAndWatch(new FileInfo(Server.MapPath("~/web.config"))); 22 } 23 24 protected void Application_Error(object sender, EventArgs e) 25 { 26 Exception exception = Server.GetLastError(); 27 //Server.ClearError(); 28 //这里记录错误日志信息 29 log.Error("MvcApplication 捕获异常", exception); 30 //跳转到指定的自定义错误页 31 Response.Redirect("/CustomErrorHandle/CustomErrorPage"); 32 } 33 } 34 }
3、通过配置system.web->customErrors节点 实现自定义错误页
当你的站点发生异常时,如果你只是想简单的跳转到一个自定义错误页面,而不是对异常进一步处理时,那么你可以简单的作如下配置操作即可:
需要在web.config中做如下配置:
1 <system.web> 2 <customErrors mode="On" defaultRedirect="CustomErrorPage"> 3 </customErrors> 4 </system.web>
注意:这里的CustomErrorPage是一个视图文件,放在Shared共享目录下。
如果你注册了HandleErrorAttribute异常过滤器到全局,那么在你的错误页中将能获取到和异常相关的一些信息。但此时配置到defaultRedirect的值的必须是Error
也就是自定义错误视图页面的名称必须为Error.cshtml,并且放在Shared目录,当然,你也可以通过在创建HandleErrorAttribute全局过滤器的过程中,设置器View属性,这样你就可以不用讲错误视图的名称设置为Error了.如下:
1 public static void RegisterGlobalFilters(GlobalFilterCollection filters) 2 { 3 HandleErrorAttribute errorAttribute = new HandleErrorAttribute(); 4 errorAttribute.View = "CustomErrorPage"; 5 filters.Add(errorAttribute); 6 }
注册HandleErrorAttribute(使用默认的错误视图页面文件名)
public class FilterConfig { public static void RegisterGlobalFilters(GlobalFilterCollection filters) { filters.Add(new HandleErrorAttribute()); } }
定义Error.cshtml视图页
1 @{ 2 Layout = null; 3 } 4 @model HandleErrorInfo 5 <!DOCTYPE html> 6 <html> 7 <head> 8 <meta name="viewport" content="width=device-width" /> 9 <title>Error</title> 10 </head> 11 <body> 12 <div> 13 @*通过HandleErrorAttribute异常过滤器捕获到的异常信息存储在Model属性中*@ 14 @Model.Exception.Message 15 </div> 16 </body> 17 </html>
之所以通过注册HandleErrorAttribute过滤器捕获的异常在错误页中能获取异常信息可以看HandleErrorAttribute类的内部实现,发现加载错误视图页面的过程中,传递了一个HandleErrorInfo对象过去。
1 public virtual void OnException(ExceptionContext filterContext) 2 { 3 if (filterContext == null) 4 { 5 throw new ArgumentNullException("filterContext"); 6 } 7 if (!filterContext.IsChildAction && (!filterContext.ExceptionHandled && filterContext.HttpContext.IsCustomErrorEnabled)) 8 { 9 Exception innerException = filterContext.Exception; 10 if ((new HttpException(null, innerException).GetHttpCode() == 500) && this.ExceptionType.IsInstanceOfType(innerException)) 11 { 12 string controllerName = (string) filterContext.RouteData.Values["controller"]; 13 string actionName = (string) filterContext.RouteData.Values["action"]; 14 HandleErrorInfo model = new HandleErrorInfo(filterContext.Exception, controllerName, actionName); 15 ViewResult result = new ViewResult { 16 ViewName = this.View, 17 MasterName = this.Master, 18 ViewData = new ViewDataDictionary<HandleErrorInfo>(model), 19 TempData = filterContext.Controller.TempData 20 }; 21 filterContext.Result = result; 22 filterContext.ExceptionHandled = true; 23 filterContext.HttpContext.Response.Clear(); 24 filterContext.HttpContext.Response.StatusCode = 500; 25 filterContext.HttpContext.Response.TrySkipIisCustomErrors = true; 26 } 27 } 28 }
1 public string View 2 { 3 get 4 { 5 if (string.IsNullOrEmpty(this._view)) 6 { 7 return "Error"; 8 } 9 return this._view; 10 } 11 set => 12 (this._view = value); 13 } 14
三、总结
总的来说,Application_Error方法用于处理针对请求管道级别的发生的异常错误,而Mvc异常过滤器则只能处理在执行action方法过程中出现的异常.能处理的范围相对Application_Error较小,但在实际项目开发中,用Mvc异常过滤器处理异常相对会多一点,因为我们的功能业务往往体现在控制器的action方法执行的过程中,也就是在这个过程中较容易产生异常。故开发中用Mvc异常过滤器就能适应大部分的异常处理需求。