MVC系列——一個異常消息傳遞引起的思考

前言:最近在某個項目裏面遇到一個有點糾結的小問題,通過半天時間的思索和嘗試,問題獲得解決。在此記錄一下解決的過程,以及解決問題的過程當中對.net裏面MVC異常處理的思考。都是些老生常談的問題,很少說,直接上「主菜」。javascript

本文原創地址:http://www.cnblogs.com/landeanfen/p/8135844.htmlhtml

1、問題重現

項目是一個傳統.net framework的MVC項目,爲了簡便,項目裏面定義了一個自定義異常類用於向客戶端傳遞錯誤消息,客戶端接收到異常的消息時在瀏覽器裏面彈出提示。先來看看這個自定義異常類CustormerException的定義java

    public class CustomerException : System.Exception
    {
        public CustomerException()
        {

        }
        public CustomerException(string message) : base(message)
        {

        }
        public CustomerException(string message, params object[] args) : base(string.Format(message, args))
        {

        }
    }

爲了模擬重現問題,我儘可能將代碼簡化再簡化。web

  [BaseException]
    public class DefaultController : Controller
    {
        // GET: Default
        public ActionResult Index()
        {
            return View();
        }

        public JsonResult Login(string userName, string password)
        {
            if (userName == "admin" && password == "admin")
            {
                return Json(true, JsonRequestBehavior.AllowGet);
            }
            else
            {
                throw new CustomerException("用戶名或者密碼錯誤");
            }
        }
    }

    public class BaseExceptionAttribute : HandleErrorAttribute
    {
        public override void OnException(ExceptionContext filterContext)
        {
            if (filterContext.HttpContext.Request.IsAjaxRequest() && filterContext.Exception is CustomerException)
            {
                filterContext.ExceptionHandled = true;
                filterContext.HttpContext.Response.StatusCode = (int)System.Net.HttpStatusCode.BadRequest;
                var result = new ContentResult() { Content = filterContext.Exception.Message, ContentType = MediaTypeNames.Text.Plain };
                filterContext.Result = result;
            }
            else
            {
                //記錄日誌
            }

            base.OnException(filterContext);

        }
    }

代碼不復雜,就是一個通用的異常過濾器,用於記錄日誌和傳遞消息到客戶端。ajax

而後咱們看看客戶端的測試代碼:瀏覽器

@{
    ViewBag.Title = "Index";
}

用戶名:<input type="text" id="username"/>
密碼  :<input type="text" id="password"/>
<button id="btnAjaxError" type="button">登錄</button>
@section Script
{
  <script type="text/javascript">

    $(function () {
        $("#btnAjaxError").click(function () {
            $.ajax({
                url:"/Default/Login",
                data: { userName: $('#username').val(), password: $('#password').val() },
                type: 'post'
            }).done(function (data) {
                console.log("successful:"+data);
            }).fail(function (a, b, c) {
                debugger;
                console.log("fail:"+a.responseText);
            });
        });
    });
</script>  
}

本地調試、運行獲得正常結果ide

 發佈到IIS,本地訪問仍然正常。但是當咱們遠程訪問的時候問題出現了。post

 

全部的遠程訪問機器上面都出現了系統默認的錯誤消息,而不是咱們返回的業務異常消息。測試

2、初次嘗試

對於這種本地能看到詳細異常,而遠程看不到詳細異常的問題,相信有必定經驗的朋友確定想到了一個配置,那就是Web.config裏面的CustomErrors節點,咱們配置下默認開啓自定義異常不就好了嗎。嘿嘿!就是這麼簡單!博主當初也是這麼樂呵呵的去嘗試的。咱們在Web.config的System.web節點下面加入這個節點url

<customErrors mode="On"></customErrors>

但是很遺憾,問題依舊!後來想是否是本身對於On、Off、RemoteOnly的理解有誤?因而乎三個項逐個嘗試,結果均已失敗了結!

因而乎開始有點鬱悶了,這種問題原來怎麼沒遇到過了,代碼「貌似」沒什麼大問題啊,若是有問題,本地應該也不能獲得纔對啊。因而乎分析,這可能不是咱們代碼的問題,而是IIS給我作了一層統一的異常處理,咱們只須要將這層統一的異常處理去掉就好了啊。道理是這麼個道理,但是如何去實現呢。因而乎把IIS的各個功能都試了個遍,最後的谷歌的一篇帖子裏面找到了一些幫助。解決方案以下。

3、解決方案

一、「不是代碼的問題」的解決方案

上文說到這個問題或許不是代碼的問題,而是IIS配置的問題。因而乎真的讓博主找到了解決方案。解決步驟以下:

 

 

 

原來,IIS默認是不讓遠程用戶查看異常的詳細錯誤的,若是是遠程用戶,IIS會默認給你返回一個各類狀態碼對應的默認消息,咱們自定義的消息將會被此覆蓋。若是改爲選中第二項,就表示不論是本地用戶仍是遠程用戶都可以看到詳細異常。

這樣配置以後不用更改任何代碼,不用理會是否配置了CustomErrors節點,遠程用戶都可以正常獲取到程序返回的異常消息:

二、「是代碼的問題」的解決方案

有了上面的解決方案,爲什麼還會有「是代碼的問題」的解決方案呢?這纔是本文想要表達的中心思想。既然咱們經過配置IIS的錯誤頁能夠解決這個問題,那麼咱們爲何不能在程序的範疇內去解決呢?博主是一個有點喜歡刨根問題的人,不斷分析代碼後發現,既然系統的默認錯誤消息能夠覆蓋咱們的自定義異常消息,那麼反過來,咱們自定義的異常消息爲何就不能覆蓋系統默認的異常消息呢?因而乎發如今重寫父類的OnException方法的時候,上面的代碼咱們是先執行的咱們自定義的異常消息,而後再調用 base.OnException(filterContext); 去執行系統默認的異常消息處理的,那麼咱們將這個順序倒置一下,反過來是否是可行呢?因而代碼就變成了這樣:

  public class BaseExceptionAttribute : HandleErrorAttribute
    {
        public override void OnException(ExceptionContext filterContext)
        {
            base.OnException(filterContext);
            if (filterContext.HttpContext.Request.IsAjaxRequest() && filterContext.Exception is CustomerException)
            {
                filterContext.ExceptionHandled = true;
                filterContext.HttpContext.Response.StatusCode = (int)System.Net.HttpStatusCode.BadRequest;
                var result = new ContentResult() { Content = filterContext.Exception.Message, ContentType = MediaTypeNames.Text.Plain };
                filterContext.Result = result;
            }
            else
            {
                //記錄日誌
            }
        }
    }

 咱們將上面經過配置IIS錯誤頁的解決方案還原,改爲默認的配置。去掉CustomErrors節點,從新發布以後,問題完美解決:

問題能解決,說明博主上面的推想或許是正確的,自定義異常和默認異常是存在一個前後順序的,咱們若是要覆蓋系統的異常,須要將咱們自定義異常的代碼放在後面執行。這個論斷是經過上述解決問題的思路推理得來的,並不必定正確,有興趣的能夠反編譯下dll看下是否真是這樣!

頗有趣的一點就是,這樣改了代碼以後,咱們若是在web.config裏面加入customErrors節點,而且將mode設置爲Off,遠程訪問的時候獲得的異常消息又變成了「錯誤的請求」。其實這不難理解,當你禁用自定義錯誤信息,那麼系統確定會給你返回默認的異常信息了。

4、總結

由上述的兩種解決方案能夠看出這裏其實有三道防線:

第一道防線是最外層的防線,就是IIS的錯誤頁配置,若是這層配置選擇的是詳細錯誤,那麼無論你其餘的配置是什麼樣,都會返回用戶自定義的錯誤信息;

第二道防線是中間的那層,就是web.config裏面的CustomErrors節點,若是第一道防線是默認配置,這層防線纔會生效;

第三道防線纔是代碼的範疇,這個受限於CustomErrors節點的配置。

本文原創出處:http://www.cnblogs.com/landeanfen/

歡迎各位轉載,可是未經做者本人贊成,轉載文章以後必須在文章頁面明顯位置給出做者和原文鏈接,不然保留追究法律責任的權利

相關文章
相關標籤/搜索