標籤:

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

正文

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

一、問題重現

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

public class CustomerException : System.Exceptionn {n public CustomerException()n {nn }n public CustomerException(string message) : base(message)n {nn }n public CustomerException(string message, params object[] args) : base(string.Format(message, args))n {nn }n }n

為了模擬重現問題,我盡量將代碼簡化再簡化。

  [BaseException]n public class DefaultController : Controllern {n // GET: Default public ActionResult Index()n {n return View();n }nn public JsonResult Login(string userName, string password)n {n if (userName == "admin" && password == "admin")n {n return Json(true, JsonRequestBehavior.AllowGet);n }n elsen {n throw new CustomerException("用戶名或者密碼錯誤");n }n }n }nn public class BaseExceptionAttribute : HandleErrorAttributen {n public override void OnException(ExceptionContext filterContext)n {n if (filterContext.HttpContext.Request.IsAjaxRequest() && filterContext.Exception is CustomerException)n {n filterContext.ExceptionHandled = true;n filterContext.HttpContext.Response.StatusCode = (int)System.Net.HttpStatusCode.BadRequest;n var result = new ContentResult() { Content = filterContext.Exception.Message, ContentType = MediaTypeNames.Text.Plain };n filterContext.Result = result;n }n elsen {n //記錄日誌 }nn base.OnException(filterContext);nn }n }n

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

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

@{n ViewBag.Title = "Index";n}nn用戶名<input type="text" id="username"/>n密碼 <input type="text" id="password"/> <button id="btnAjaxError" type="button">登陸</button>n@section Scriptn{n <script type="text/javascript">nn $(function () {n $("#btnAjaxError").click(function () {n $.ajax({n url:"/Default/Login",n data: { userName: $(#username).val(), password: $(#password).val() },n type: postn }).done(function (data) {n console.log("successful:"+data);n }).fail(function (a, b, c) {n debugger;n console.log("fail:"+a.responseText);n });n });n });n</script> n}n

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

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

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

二、初次嘗試

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

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

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

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

三、解決方案

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

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

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

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

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

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

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

我們將上面通過配置IIS錯誤頁的解決方案還原,改成默認的配置。去掉CustomErrors節點,重新發布之後,問題完美解決:

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

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

四、總結

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

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

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

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

作者:懶得安分

鏈接:MVC系列--一個異常消息傳遞引發的思考 - 懶得安分 - 博客園

聲明:本文由極樂科技合作作者撰寫,版權歸作者所有,轉載請註明作者與出處

推薦閱讀:

Python編程(b三):Python之MVC
MVC 模式的原理,它在 Android 中是如何運用的?
zencart 這個國外商城 CMS 源碼寫得怎樣?
NodeJS的MVC是如何工作的?
asp.net mvc 4 模型層為什麼可以通過簡單的 {get;set}就可以對數據進行設置和取得 這其中的實現原理是什麼?

TAG:MVC | NET | Net开发 |