1. 程式人生 > >MVC系列——一個異常訊息傳遞引發的思考

MVC系列——一個異常訊息傳遞引發的思考

前言:最近在某個專案裡面遇到一個有點糾結的小問題,經過半天時間的思索和嘗試,問題得到解決。在此記錄一下解決的過程,以及解決問題的過程中對.net裡面MVC異常處理的思考。都是些老生常談的問題,不多說,直接上“主菜”。

一、問題重現

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

    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)) { } }

為了模擬重現問題,我儘量將程式碼簡化再簡化。

  [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); } }

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

然後我們看看客戶端的測試程式碼:

@{
    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>  
}

本地除錯、執行得到正常結果

 釋出到IIS,本地訪問仍然正常。可是當我們遠端訪問的時候問題出現了。

 

所有的遠端訪問機器上面都出現了系統預設的錯誤訊息,而不是我們返回的業務異常訊息。

二、初次嘗試

對於這種本地能看到詳細異常,而遠端看不到詳細異常的問題,相信有一定經驗的朋友肯定想到了一個配置,那就是Web.config裡面的CustomErrors節點,我們配置下預設開啟自定義異常不就行了嗎。嘿嘿!就是這麼簡單!博主當初也是這麼樂呵呵的去嘗試的。我們在Web.config的System.web節點下面加入這個節點

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

可是很遺憾,問題依舊!後來想是不是自己對於On、Off、RemoteOnly的理解有誤?於是乎三個項逐個嘗試,結果均已失敗告終!

於是乎開始有點鬱悶了,這種問題原來怎麼沒遇到過了,程式碼“貌似”沒什麼大問題啊,如果有問題,本地應該也不能得到才對啊。於是乎分析,這可能不是我們程式碼的問題,而是IIS給我做了一層統一的異常處理,我們只需要將這層統一的異常處理去掉就行了啊。道理是這麼個道理,可是如何去實現呢。於是乎把IIS的各個功能都試了個遍,最後的谷歌的一篇帖子裡面找到了一些幫助。解決方案如下。

三、解決方案

1、“不是程式碼的問題”的解決方案

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

 

原來,IIS預設是不讓遠端使用者檢視異常的詳細錯誤的,如果是遠端使用者,IIS會預設給你返回一個各種狀態碼對應的預設訊息,我們自定義的訊息將會被此覆蓋。如果改成選中第二項,就表示不管是本地使用者還是遠端使用者均可以看到詳細異常。

這樣配置之後不用更改任何程式碼,不用理會是否配置了CustomErrors節點,遠端使用者均可以正常獲取到程式返回的異常訊息:

2、“是程式碼的問題”的解決方案

有了上面的解決方案,為何還會有“是程式碼的問題”的解決方案呢?這才是本文想要表達的中心思想。既然我們通過配置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,遠端訪問的時候得到的異常訊息又變成了“錯誤的請求”。其實這不難理解,當你禁用自定義錯誤資訊,那麼系統肯定會給你返回預設的異常資訊了。

四、總結

由上述的兩種解決方案可以看出這裡其實有三道防線:

第一道防線是最外層的防線,就是IIS的錯誤頁配置,如果這層配置選擇的是詳細錯誤,那麼不管你其他的配置是什麼樣,都會返回使用者自定義的錯誤資訊;

第二道防線是中間的那層,就是web.config裡面的CustomErrors節點,如果第一道防線是預設配置,這層防線才會生效;

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

歡迎各位轉載,但是未經作者本人同意,轉載文章之後必須在文章頁面明顯位置給出作者和原文連線,否則保留追究法律責任的權利

相關推薦

MVC系列——一個異常訊息傳遞引發思考

前言:最近在某個專案裡面遇到一個有點糾結的小問題,經過半天時間的思索和嘗試,問題得到解決。在此記錄一下解決的過程,以及解決問題的過程中對.net裡面MVC異常處理的思考。都是些老生常談的問題,不多說,直接上“主菜”。 一、問題重現 專案是一個傳統.net framework的MVC專案,為了簡便,專案

一個異常引發的集合多執行緒思考

  對於Hashtable和HashMap,相信每個學習Java的人都不會陌生,這兩個集合在用法上並沒有什麼不同,但在使用環境上卻有很大差別: (1)區別,這兩個類主要有以下幾方面的不同:      Hashtable和HashMap都實現了Map介面,但是Hashtabl

一個異常引發的對Hashtable和HashMap的思考

  對於Hashtable和HashMap,相信每個學習Java的人都不會陌生,這兩個集合在用法上並沒有什麼不同,但在使用環境上卻有很大差別: (1)區別,這兩個類主要有以下幾方面的不同:      Hashtable和HashMap都實現了Map介面,但是Hashtabl

0.28+0.34=? 一個簡單小數加法引發思考

div 十進制數 繼續 數據類型 html ava one 單純 2.3   0.28+0.34=?   我相信這個簡單的加法,誰都會,肯定等於0.62嘛。   這是兩個特別簡單的加法,那如果我在其整數位置上加上其他的數字,或者多加幾個和項,你是否還能快速算過來?   我想

卜若的程式碼筆記-photon系列-第三章:訊息傳遞

1.建立訊息傳遞介面(photon的字典對映),客戶端 /// <summary> /// 像伺服器傳遞引數 /// </summary> /// <param name="MainCode">主運算元&l

高併發架構系列:如何從0到1設計一個MQ訊息佇列

訊息佇列作為系統解耦,流量控制的利器,成為分散式系統核心元件之一。 如果你對訊息佇列背後的實現原理關注不多,其實瞭解訊息佇列背後的實現非常重要。 不僅知其然還要知其所以然,這才是一個優秀的工程師需要具備的特徵。 今天,我們就一起來探討設計一個訊息佇列背後的技術。 訊息佇列整體設計思路 主要是設計

無法捕獲異常:Java關於在catch中丟擲一個異常給外圍函式卻捕獲不到該異常思考

先給出一串程式碼: public class TestException { public TestException(){ } boolean testEx()throws Exception{ boolean ret=true; try{ r

一個HTTP Basic Authentication引發異常

這幾天在做一個功能,其實很簡單。就是呼叫幾個外部的API,返回資料後進行組裝然後成為新的介面。其中一個API是一個很奇葩的API,雖然是基於HTTP的,但既沒有基於SOAP規範,也不是Restful風格的介面。還好使用它也沒有複雜的場景。只是構造出URL,傳送一個HTTP的get請求,然後給我返回一個XML

Android學習筆記2 建立另一個activity以及實現intent訊息傳遞

學習內容來自google教程:http://developer.android.com/intl/zh-cn/training/basics/firstapp/starting-activity.html#DisplayMessage 1 在之前的MainActivity基

一個由line-height引發的血案與思考

爆炸   最近UI走查,發現頁面中所有包含文字區塊的高度與設計稿中的高度完全不一致,然後UI妹子就爆炸了!   找了一下原因,發現是由於UI設計稿中設計的文字大部分是font-size:24px;line-height:24px,程式碼實現時為了不至

C# 一個計算器功能實現引發思考

一.需求     計算器功能需求,這個眾所周知,很明確了。 二.步驟分析 1)初級實現計算器  static int Calculator(int a,int b,string str) { switch(str) {

C#進階系列——WebApi 異常處理解決方案(轉)

機制 輸出 ges 如果 但是 rom lba slist 解決 出處:http://www.cnblogs.com/landeanfen/p/5363846.html 閱讀目錄 一、使用異常篩選器捕獲所有異常 二、HttpResponseException自

Spring MVC 單元測試異常 Caused by: org.springframework.core.NestedIOException: ASM ClassReader failed to parse class file

read cti exe document ive pri simple fff ces Sping 3.2.8.RELEASE + sping mvc + JDK 1.8運行異常。 java.lang.IllegalStateException: Failed to

程序設計基石與實踐系列之按值傳遞還是按引用

有趣 name align pos str 堆棧 技術分享 easy pan 從簡單的樣例開始.如果我們要交換兩個整形變量的值,在C/C++中怎麽做呢?我們來看多種方式,哪種能夠做到.void call_by_ref(int &p,int &q) { //

MVC系列博客之排球計分(一)需求分析

height repl 系列 ges 優勢 針對 .... 9.png ota 項目簡介: 這是MVC系列博客之排球計分程序,該程序可以是對教練或者裁判使用的,讓教練有權限對隊員進行查詢得分情況,讓教練對隊員的優勢劣勢進行了解,以便對隊伍進行調整。 讓裁判更

MVC系列博客之排球計分(三)模型類的實現

layers 自動生成 ext alt 感覺 名稱 數據 string 後來 最初我使用的是連接數據庫的方法來建立數據連接的,後來聽了同學用EF框架來生成數據庫自動連接,感覺很好用,然後我就重新用EF框架生成數據庫 使用EF框架生成數據庫,要有相應的模型類,模型類如下:

MVC系列博客之排球計分(四)視圖的實現

ont shtml dev 相關 control 沒有 mage evel 技術分享 Views 文件夾 Views 文件夾存儲的是與應用程序顯示(用戶界面)相關的文件(HTML 文件)。根據所采用的語言內容,這些文件可能擴展名可能是 html、asp、aspx、cshtm

spring mvc中關於url中傳遞中文亂碼的解決方法

般的 har rac color nco bytes utf8 int span 在傳值過程中,也是亂碼出現的頻繁地。先不說到底是什麽場景了,通常常用的方案有如下幾個 配置指定的filter <!-- 配置請求過濾器,編碼格式設為UTF-8,避免中文亂碼-->

mvc 返回一個對象 到視圖接收

log rect scripts del html mar bag tex urn public ActionResult InfoFrame() { List<Users> list = new List<Users

ASP.NET MVC編程入門--MVC5 傳遞參數與初始化數據

port ctp params cti 模型 top help mvc ring 傳遞參數格式: $(".limit").live("click", function () { top.location = "/Product