受夠了if (ModelState.IsValid)?ActionFitlter也是一路的坑啊!

来源:http://www.cnblogs.com/freeflying/archive/2017/05/17/6870158.html
-Advertisement-
Play Games

這篇博客真是乾貨,幹得估計還有點“磕牙”,所以還提供視頻和代碼。但基礎稍弱的同學,怕還是得自行補充一些基礎知識——就一篇文章,確實沒辦法面面俱到。 視頻和代碼下載:Demo - 百度雲盤 · 一起幫 參考原文:Automatic ModelState validation in ASP.NET MV ...


這篇博客真是乾貨,幹得估計還有點“磕牙”,所以還提供視頻和代碼。但基礎稍弱的同學,怕還是得自行補充一些基礎知識——就一篇文章,確實沒辦法面面俱到。

 

緣起

我忘了是不是在園子里講過,我命名為“截斷式編程”的寫法。其主要目的,就是把簡單的、過濾條件、“非主幹的”邏輯放在最前面。比如在ASP.NET MVC的Action中,處理POST時,我們通常都要進行服務端驗證,於是我們就可以這樣寫:

        [HttpPost]
        public ActionResult Send(MessageSendModel model)
        {
            #region 非截斷式寫法

            //if (ModelState.IsValid)
            //{
            //    //假設發送了一個消息
            //    Response.Write("消息已經發送");
            //}

            //return View(model);

            #endregion

            #region 截斷式編程

            //過濾條件
            if (!ModelState.IsValid)
            {
                return View(model);
            }

            //主幹程式:假設發送了一個消息
            Response.Write("消息已經發送");

            return RedirectToAction("Send");

            #endregion
        }
View Code

由於採用了這種寫法,我們很快就發現了一個問題:if (!ModelState.IsValid) { return View(model); }到處都是。

是不是有點“壞味道”的感覺?你是不是想怎麼“弄”它一下?

 

ActionFilter

首先想到的,當然就是ActionFilter了:在Action執行之前,用一個Filter進行檢查,不就OK了嗎?

我覺得這個想法不錯,但是,但是,請註意,一定要問一個為什麼!為什麼別人想不到呢?——這怎麼可能?!

所以,在自己動手之前,養成習慣,google/bing一下,看看別人是怎麼弄的。

果不其然,找到一篇博客::Automatic ModelState validation in ASP.NET MVC ,和我的思路一模一樣!\(^o^)/

而且,他想得比我更周全!看得我那個興奮啊……

所以,這裡我們得到的第一個經驗:動手之前先搜一搜,不要重覆造輪子

再引申開一點,

英文 + google = 偉大的程式員。

至少在目前,以及可預見的將來,對於開發人員而言,英語非常重要,非常重要,重要性怎麼強調都不為過。大家可以試一下,有沒有中文的類似的博客資料等,我沒去試。但根據我的經驗,相比於英文資料,中文資料是非常匱乏的。

關於使用搜索引擎,很多同學覺得這是一種“可恥的行為”,但其實不然。你一定要明白:你的目的是解決問題,而不是炫技,非得把什麼東西都記在腦子裡,非得什麼都自己寫出來……算了,這話可能很多人不接受,篇幅有限,懶得說了。懂的人一點就通,不懂的人你怎麼說都沒用。

 

最簡單的情形

一開始解決方案還是比較簡單的。我就直接放代碼了:

public class ValidateModelStateAttribute : ActionFilterAttribute
{       
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {

        if (!viewData.ModelState.IsValid)
        {
            filterContext.Result = new ViewResult();
        }
    }
}
View Code

理解的難點大概就在於為什麼:return View(); 和 filterContext.Result = new ViewResult(); 是等價的。

我覺得有這個問題的根源還是沒有理解“面向對象”,這一直是.NET陣營程式員所缺乏的。

一起幫的QQ群里有時候就有同學犯迷糊,聊了之後,我就明白了,他始終把return View();認為是“轉到那個View頁面”的意思,而沒有能夠理解成:這就是一個方法,返回的是一個ActionResult對象。大家能明白我的意思吧?所有的Action都是一個方法,一個返回ActionResult對象的方法,然後ASP.NET MVC框架根據這個ActionResult對象,找到相應的View進行呈現(Render)。這中間多了一個環節,但這不是“脫了褲子放屁”,是非常有必要的,而且非常“精妙”的一個架構設計。

你看,這樣Filter里就可以通過給filterContext.Result賦值而達到頁面呈現的效果。當然,這麼做最大的好處還在於UI層的“可測試化,這又是一個非常龐雜的話題,此處先略過。

多說一句,一定要說這一句:感謝這位同學提出問題,讓我知道作為初學者,那些地方是難點。話說,我做直播這麼久,要收到點反饋可真難啊!

 

然而,這裡有一個問題

如果沒有bing(話說不能google真特麽的悲劇)一下,我肯定就到此為止了。然而,高手就是高手,Ben Foster想到了另外一個問題:

假設頁面中需要由後臺傳來的數據才能正常呈現時,如何在Filter里賦值,再傳遞給View?

這樣說有點暈,所以我才專門弄了一個Demo,用DropdownList做例子。DropdownList的可選項數據是從後臺獲取的,由於HTTP的“無狀態協議特征,POST不能“繼承”GET時獲取的數據,咋辦呢?

 

TempData解圍

很多種辦法,Ben Foster的博客里是封裝一個方法,GET和POST都調用。

我以前項目,是利用的MVC中的TempData,在兩個請求見共用數據。這樣,如果可選項數據是從資料庫獲取的,可以減少一次查詢,有一點點性能上的提高。

TempData真是一個非常好的東西,這就是我喜歡.NET的原因,它非常的“貼心”,給人的感覺是“始終面向一線開發人員的,它知道你在寫代碼的時候可能會遇到什麼問題,從而事先給你準備好解決方案。

思路和實現都很簡單:GET的時候,就把POST需要的數據存到TempData中;POST的時候,就把TempData中的數據取出來(需要一個強制轉換,因為數據是存放為Object類型的)。

代碼就不貼了,因為這不是最終的解決方案。

 

但FilterAction里腫麽辦

在Controller裡面你怎麼玩都行,但我們現在要“封裝”啊,我們要在Filter里解決這個問題啊!

傻眼了。關鍵的問題在於我們需要在OnActionExecuting(Action執行之前)進行驗證,而此時Action的ViewModel並沒有生成,我們可以從ControllerBase.ActionParameters 中取值,但取出來的是一個Object類型,你不知道要把它轉換成什麼類型的(當然你可以用反射做,但非常複雜,複雜到你都覺得沒有必要),TempData也是一樣的問題。

每次這個時候,我就會想起ASP.NET WebForm中的ViewState來,那些年認為它是“性能殺手”,對它口誅筆伐。MVC的誕生並流行,估計和這玩意就有非常大(多大呢?我亂說的,三成吧)的關係。

但現在MVC沒ViewState了,要自己處理,呵呵,又有點懷戀以前WebForm開發的“便捷”了。

再展開來說,DateSet,Linq to SQL,Entity Framework……一系列的技術,一經推出,都是吵吵嚷嚷,不可開交。其實何必呢,各有各的用處,各有各的適用場景,脫離了具體的業務要求,能爭出個什麼高下來?

所以我一直強調,軟體工程,技術選擇以解決問題為標準。問題千差萬別,所以使用的技術不可能整齊劃一,一句話,“沒有銀彈”。

 

PRG模式

如果是我的話,這時候就放棄了。但Ben Foster給出了一個非常棒的解決方案:利用PRG模式

PRG是Post, Redirect, Get的縮寫,意思是所有的POST請求,都Redirect到GET的Action,哪怕返回的實際上是同一個頁面。示例代碼如下:

        [HttpPost]
        public ActionResult Send(MessageSendModel model)
        {

            //主幹程式:假設發送了一個消息
            Response.Write("消息已經發送");

            //註意:不是return View()
            //1、是Redirect
            //2、重定向的這個Action是和自己同名的,都是Send
            return RedirectToAction("Send");

        }
View Code

我非常欣慰的是我們的系統架構,一開始就是按照這個原則搭建的。最早接觸PRG模式的時候,我也是有點迷迷糊糊的,但想到大家都這樣用,而且看起來也沒什麼壞處,就採用了,後來真的是,一次又一次的發現這種模式的便利。這次又是這樣搭上了“便車

記得我在直播里說過的:如果架構中出現了很多稀奇古怪難以剋服的問題,一般來說,就是因為你沒走在大道上。

通用的慣例模式,就是大道,別人已經走過的路啊。你循著別人走過的道走,碰到問題的時候,也一樣會比較容易的找到解決問題的方案;你要獨闢蹊徑——通常情況下可能不是你想獨闢蹊徑,呵呵,多半是自己走岔了吧——那荒山野嶺的,確實很難找到求助。

 

終極方案

思路就是:

  • 在Action的POST的Filter里,如果未通過驗證,就把ModelState存放在TempData之中;
  • 給Aciton的GET也添加一個Filter,用於取出TempData中的ModelState,並Merge到自己的ModelState中。
  • 於是,通過Redirect,GET的Action得到了錯誤提示信息

非常清晰,我圖片和代碼混用吧:

最核心代碼:

        /// <summary>
        /// Exports the current ModelState to TempData (available on the next request).
        /// </summary>       
        protected static void ExportModelStateToTempData(ControllerContext context)
        {
            context.Controller.TempData[Key] = context.Controller.ViewData.ModelState;
        }

        /// <summary>
        /// Populates the current ModelState with the values in TempData
        /// </summary>
        protected static void ImportModelStateFromTempData(ControllerContext context)
        {
            var prevModelState = context.Controller.TempData[Key] as ModelStateDictionary;
            context.Controller.ViewData.ModelState.Merge(prevModelState);
        }
View Code

就這麼簡單,完美!回味無窮。

其實,在POST的Action里return View();並不是一個好套路。

在我的項目我就發現了這麼一個問題:當return的View()里還含有@Html.Action()調用,且該調用需要區分GET和POST時,會進入POST所屬的ChildAction,這肯定是不符合邏輯的。(表述起來好吃力!慢慢看,一邊看一邊想,想不明白的看視頻吧……)

 

題外話

磨蹭了一天,終於寫完了。

寫得好累,感覺寫這一篇文章比那90分鐘的視頻還累,難怪現在好多開源項目都沒文檔,直接上視頻了……

最後說說“一起幫”吧,現在已經被當初的乞丐版強多了,隔三差五的也有同學在上面提問,算是有些生氣了,但人氣還是遠遠的不夠,根據QQ群里投票結果,現在主要精力應放在做推廣上。

推廣了好幾天了,有點效果,但唉呀我的媽呀!這推廣,比寫這篇博客還累,咋整?

園子里就不說這些了。“酒向知己飲,詩向會人吟”,以後博客園只會講技術,這也是博客園歡迎的。想聊點編程以外的,歡迎加QQ群:

179742319(付費入群搶紅包),312423951(驗證入群)

以及關註微信公眾號:我們一起幫

 

感謝評論區

 

@敲代碼的吃貨:

如果採用PRG的方案,就不能達到你的要求:“把提交上來的數據再次呈現以便修改的”。要滿足這種需求,只能在POST里return View(model); model里是帶著之前數據的。

@stoneniqiu:

該方案的優勢不是“減少”代碼量,而是從架構層面,解決POST中return View(); 造成的空異常問題。

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 7.1 常量 常量 是值從不變化的符號。定義常量符號時,它的值必須能夠在編譯時確定。 只能定義編譯器識別的基元類型的常量,如果是非基元類型,需把值設為null。 常量的值直接嵌入代碼,所以不能獲取常量地址,不能以傳引用的方式傳遞常量。 不能很好地支持跨程式集的版本控制(修改一個DLL中的常量值,需要 ...
  • 怎麼將XML字元串轉換為XmlDocument,並獲取部分節點值??? 總結關鍵知識點: 1-如何將XML字元串轉換為XmlDocdument:   XmlDocument xmlDocument = new XmlDocument();   xmlDocument.LoadXml(xmlSt... ...
  • ASP.NET mvc的最好的優點之一就是支持Model驗證,這個特性很方便你可以選擇在定義Model的時候在欄位中採用特性進行註解約定,也可以在代碼中自己進行手動驗證。下麵就來細說一下ASP.NET MVC Model驗證多種方式的實現。 一、瞭解什麼是ASP.NET MVC Model驗證 首先 ...
  • 前言 目前 EF Core 的最新版本為 ,所以本篇文章主要是針對此版本的一些說明。 註意:如果你要在Visual Studio 中使用 .NET Core 2.0 , 你需要至少 Visual Studio 2017 15.3 預覽版本。 安裝或升級到 EF Core 2.0 你可以通過以下命令來 ...
  • ASP.NET mvc的razor視圖引擎是一個非常好的.NET MVC框架內置的視圖引擎。一般情況我們使用.NET MVC框架為我們提供的這個Razor視圖引擎就足夠了。但是有時我們想在我們的項目支持多模板&skins機制,比如我們可能會有多套的模板,也就是多個View風格,而我們只需要改一下配置 ...
  • 請求一個ASP.NET mvc的網站和以前的web form是有區別的,ASP.NET MVC框架內部給我們提供了路由機制,當IIS接受到一個請求時,會先看是否請求了一個靜態資源(.html,css,js,圖片等),這一步是web form和mvc都是一樣的,如果不是則說明是請求的是一個動態頁面,就 ...
  • namespace ConsoleApp1 { class Program { static void Main(string[] args) { People tom = new People() ; tom.Name =" Tom"; tom.Age = 18; tom.MyPlace = Co ...
  • 本系列文章將複習和重新理解C#語言基礎知識 (一) C#基礎 (二)對象和類型 (三)繼承 (四)數組 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...