ASP.NET MVC 异常处理之最佳实现

点击查看原文

异常处理是所有应用程序的的一个重要部分。ASP.NET 为处理异常提供了数种不同的方法。在这篇文章中,我们将学习如何最佳的实现 ASP.NET MVC 异常处理。

MVC异常处理的5种方法

在 .NET 、ASP.NET 和 MVC 之间有几种可行的方式来处理应用程序异常。

  • Web.Config 文件中的 <customErrors> 结点
  • MVC 的 HandleError 特性
  • Controller.OnException 方法
  • HttpApplication Application_Error 事件
  • 使用Stackify 的 Retrace 收集异常

这些异常处理方法都有各自的优缺点。您可能需要组合使用它们才能正确处理异常并记录错误信息。
在异常处理时,你需要完成两个重要的事情:

  1. 优雅地处理异常并向用户展示友好的错误页面。
  2. 记录错误方便您了解并监控错误信息。

必须项:在 Web.Config 的 <customErrors> 结点中配置全局错误页面

向您的用户展示“yellow screen of death(黄色错误页)”是您的最后一个选择。如果您不知道那是什么的话,那正是我所谈论的标准的黄色ASP.NET错误页面。

对于所有应用程序,我总是建议在您的 Web.Config 中指定自定义错误页面。最坏情景下,如果出现未处理的异常,用户看到的也将是配置中指定的自定义错误页面。

<system.web>
    <customErrors mode="On" defaultRedirect="~/ErrorHandler/Index">
        <error statusCode="404" redirect="~/ErrorHandler/NotFound"/>
    </customErrors>
<system.web/>

更多信息:How to Use Web.Config customErrors for ASP.NET

使用 MVC HandleErrorAttribute 自定义 Responses

HandleErrorAttribute 继承自 FilterAttribute ,可以应用于整个 controller 或着 controller 中某个的 action 方法。

---------------
/*
* HandleErrorAttribute 定义如下:
*/
 public class HandleErrorAttribute : FilterAttribute, IExceptionFilter
{
    //Other members...

    /// <summary>Called when an exception occurs.</summary>
    /// <param name="filterContext">The action-filter context.</param>
    /// <exception cref="T:System.ArgumentNullException">The <paramref name="filterContext" /> parameter is null.</exception>
    public virtual void OnException(ExceptionContext filterContext)
    {
        if (filterContext == null)
            throw new ArgumentNullException("filterContext");
        if (filterContext.IsChildAction || filterContext.ExceptionHandled || !filterContext.HttpContext.IsCustomErrorEnabled)
            return;
        Exception exception = filterContext.Exception;
        //500
        if (new HttpException((string)null, exception).GetHttpCode() != 500 || !this.ExceptionType.IsInstanceOfType((object)exception))
            return;
        string controllerName = (string)filterContext.RouteData.Values["controller"];
        string actionName = (string)filterContext.RouteData.Values["action"];
        HandleErrorInfo model = new HandleErrorInfo(filterContext.Exception, controllerName, actionName);
        ExceptionContext exceptionContext = filterContext;
        ViewResult viewResult1 = new ViewResult();
        viewResult1.ViewName = this.View;
        viewResult1.MasterName = this.Master;
        viewResult1.ViewData = (ViewDataDictionary)new ViewDataDictionary<HandleErrorInfo>(model);
        viewResult1.TempData = filterContext.Controller.TempData;
        ViewResult viewResult2 = viewResult1;
        exceptionContext.Result = (ActionResult)viewResult2;
        filterContext.ExceptionHandled = true;
        filterContext.HttpContext.Response.Clear();
        filterContext.HttpContext.Response.StatusCode = 500;
        filterContext.HttpContext.Response.TrySkipIisCustomErrors = true;
    }
}

HandleErrorAttribute 只能处理在MVC action方法中发生的 Http Status Code 等于 500(Internal Server Error)的错误。它不会追踪在MVC管道之外帮助的异常。在其他HTTP模块、MVC路由等可能出现异常。

什么时候该使用 HandleErrorAttribute ?

由于它无法捕获所有的可能出现异常,所以对于全局未处理的异常处理来说这是一个糟糕的解决方案。

它适用于为特定的 MVC controller 或 action 方法指定特定的错误页面。在您的 Web.Config 的 <customErrors> 结点中,指定一个错误页面适用于通用(全局)错误页面。HandleErrorAttribute 可以给你更精细的控制,如果您需要的话。

注意:如果您需要使用 HandleErrorAttribute,请确保在您的Web.Config 中 <customErrors> 结点设为 On。

举个例子,当出现 SqlException 异常时,你希望显示一个特定的页面,你可以使用类似于如下的代码:

[HandleError(ExceptionType = typeof(SqlException), View = "SqlExceptionView")]
public string GetClientInfo(string username)
{
    return Dll.Query(username);
}

使用 HandleErrorAttribute 的缺点是,它无法记录异常日志!

使用 MVC Controller OnException 来自定义 Responses

OnException 方法与 HandleErrorAttribute 相似,但是更为灵活。它可以处理所有的 Http Status Code,而不仅仅是 Http Status Code 等于 500 的错误。同样你也可以记录异常日志。

/*
* IExceptionFilter 接口 的 OnException 方法定义如下:
*/
namespace System.Web.Mvc
{
  /// <summary>Defines the methods that are required for an exception filter.</summary>
  public interface IExceptionFilter
  {
    /// <summary>Called when an exception occurs.</summary>
    /// <param name="filterContext">The filter context.</param>
    void OnException(ExceptionContext filterContext);
  }
}

示例:

public class UserMvcController : Controller
{
   protected override void OnException(ExceptionContext filterContext)
   {
      filterContext.ExceptionHandled = true;

      //Log the error!!
      _Logger.Error(filterContext.Exception);

      //Redirect or return a view, but not both.
      filterContext.Result = RedirectToAction("Index", "ErrorHandler");
      // OR 
      filterContext.Result = new ViewResult
      {
         ViewName = "~/Views/ErrorHandler/Index.cshtml"
      };
   }
}

什么时候使用 OnException 方法处理 MVC 异常?

如果您希望为用户展示自定义的错误页面,或者记录自定义异常信息,那 OnException 就是一个很棒的解决方案。 相比 HandleErrorAttribute 它更为灵活,而且不需要将 Web.Config 中的 <customErrors> 设置为 On。

注意: 所有 HTTP status codes 都会调用 OnException 方法。所以对于简单错误(如:由于错误的Url引发的404错误)的处理需要你十分小心。

使用 HttpApplication Application_Error 作为全局异常处理

到目前为止,我们已经介绍了三种不同的方法来自定义异常发生时您的用户将看到的响应,OnException 是你唯一可以用来记录异常信息的选项。

为了记录应用程序中可能发生的所有未处理的异常,你应该实现基本错误的日志记录功能,如下所示:

public class MvcApplication : System.Web.HttpApplication
{
   protected void Application_Start()
   {
      AreaRegistration.RegisterAllAreas();
      FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
      RouteConfig.RegisterRoutes(RouteTable.Routes);
      BundleConfig.RegisterBundles(BundleTable.Bundles);
   }

   protected void Application_Error()
   {
      var ex = Server.GetLastError();
      //log the error!
      _Logger.Error(ex);
   }
}

什么时候使用 Application_Error ?

Always!HttpApplication 的 Application_Error 方法提供了收集和记录所有未处理的错误的最佳机制。

用Stackify Retrace收集所有.NET异常

Stackify的APM解决方案Retrace轻松访问.NET性能分析API,以跟踪应用程序的性能,直至代码级别。 作为它的一部分,它可以自动收集所有未处理的异常,或被配置为接收所有抛出的异常,即使它们被处理和丢弃。 Retrace不需要任何代码更改!

Retrace允许您查看和监视所有的应用程序错误。 查看我们的错误监控功能以了解更多信息。


Retrace

总结

有多种方法可以完成 MVC 错误处理。您总是应该在 Web.Config中的 <customErrors>结点中指定默认的错误页面,并且在 HttpApplication 的 Application_Error 方法中记录未处理的异常信息。

HandleErrorAttribute 或者 OnException 为用户显示错误方式提供更为精细的控制。

如果你想跟踪所有的应用程序异常,请确保检查我们的Retrace和我们的错误监控功能。 您还可以使用我们的免费探查器Prefix,免费查看您的工作站上的所有应用程序异常。

更多信息:

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343