標籤:學習 壓力 簡單 request 全棧 wcf 關係 建立對象 erp
閑來繼續學習蔣金楠大師的ASP.NET MVC架構揭秘一書,當前主要閱讀的內容是Model中繼資料的解析,即使是閱讀完的現在,仍然有不少細節不是特別明白。好在這部分內容主要是關於Razor引擎的呈現的,通過註解的方式對Model進行自定的修飾,最終使得頁面在渲染時(即從cshtml檔案轉化為html時),相關的資料能夠按照指定的形式轉化並顯示。由於接下來的項目中不再打算使用Razor引擎,該引擎雖然很不錯,但也有一些問題,例如存在HTML5代碼與HtmlHelper的混寫,使得UI層很難與業務代碼層完全的分離。所以不太利於程式開發的分工,在當前的互連網需求迅速變化的情境下變得不是也別適合。而且相關工作人員的招聘等問題上也比較難得處理,不可能要求每一個工程師都具有全棧能力,企業也可能負擔的其相關成本,自己對微軟2012年推出的blend開發模式不太瞭解,不知道是不是只是支援WPF,WinForm等,可能有些理解上問題,請給予指正。個人觀點上,更傾向於直接使用HTML5開發,架構主要負責路由,過濾器等功能,當前攜程等網站也主要採用這種方式。因此,該章節算是選學了,主要介紹一些主要的概念。
首先要提及的是中繼資料,一說中繼資料,大家第一反應可能都是一樣的,就是中繼語言IL中對類的描述資訊。一般我們可以通過自訂特性的方式對其進行擴充,這兒的Model中繼資料只要用於控制Model對象(ViewModel對象),在View上的呈現形式。其使用System.Web.Mvc.ModelMetadata來表示Model中繼資料,並且ModelMetadata是一種迭代,支援自包含的結構,有點像組合模式。如果ModelMetadata是一個複雜類型,可以通過一個相應的XXXTypeConverter輔助類將其轉化為簡單類型。
接下來,簡單介紹一下與該架構相關資料註解特性,架構就是依靠這些註解特性和相應的模板方法來控制Model資料的顯示,如下表所示。
| 特性名稱 |
| UIHintAttribute |
|
| HiddenInputAttribute |
ScaffoldColumnAttribute |
| DisplayTypeAttribute |
DisplayFormatAttribute |
| EditableAttribute |
ReadOnlyAttribute |
| DisplayAttribute |
DisplayNameAttribute |
| RequiredAttribute |
|
| AllowHtmlAttribute |
|
| RadioButtonListAttribute |
CheckBoxListAttribute |
| DropdownListAttribute |
ListBoxAttribute |
相關特性大部分都是顧名思義,比較容易理解,也與EF中的註解特性相似,就不一一介紹了。需要注意的是,如果想自訂一個特性,那麼就要實現IMetadataAware介面,與它同名的介面也經常出現在.NET相關資料繫結中,代碼如下所示。
View Code
Tip:注意在項目中定義相關資源檔,將英文的屬性名稱轉化為中文。
常見模板方法為:
| 模板方法 |
| HtmlHelper<TModel> |
Display |
DisplayFor |
| |
Editor |
EditorFor |
| |
DisplayForModel |
EditForModel |
| |
Label |
LabelFor |
| |
DisplayText |
DisplayTextFor |
| |
DropdownList |
DropdownListFor |
| |
ListBox |
ListBoxFor |
以上可以很清楚的看到顯示模式和編輯模式兩種不同的顯示形式,由於架構是根據中繼資料對象中的資料類型屬性值去尋找對應模板的,因此將需要將模板的View定義放在EditorTemplates目錄下。接下來通過一個表格介紹一些架構預定義的模板。
| 預定義方法 |
| EmailAddress |
HiddenInput |
Html |
| Text&String |
Url |
MultilineText |
| Password |
Decimal |
Boolean |
| Collection |
Object |
|
最後用一個簡圖介紹下與ModelMetadata相關的類加強理解與記憶。
可以看到,ModelMetadata是會進行緩衝的,並且通過原型模式進行建立,在.NET中經常可以看到Provider,這兒主要起一個提供者的作用,但與工廠類等其他建立型模式有什麼區別仍然不是很清楚,還需要加強理解。
資料繫結對於長期進行.NET相關開發的技術人員來說,非常的熟悉。無論是最開始的WinForm, WebForm, 還是現在的WPF,ASP.NET MVC,只要是與前台頁面資料來源相關的內容,都離不開這個概念,該概念的實現極大的簡化了相關的開發工作。即使不使用Razor視圖引擎,直接使用靜態HTML5頁面,該模組仍然不可或缺。現在簡單的介紹一下ASP.NET MVC中相關的Model綁定。
在ASP.NET MVC架構中,Model綁定本質上就是為目標Action方法產生參數列表的過程,這些參數列表的來源可能是請求的URL,可能是HTTP的要求標頭或請求體中,通過參數的中繼資料資訊可以得到相關內容。主要有ControllerDescriptor,ActionDescriptor,ParameterDescriptor三個抽象類別,他們均實現了System.Reflection.ICustomAttributeProvider介面,其實主要就是對類別中繼資料資訊的封裝類,方便中繼資料資訊的使用,其具體實作類別與關係如所示。
通過可以看到,所有Reflected作為首碼的類都是實作類別,同時可以看到Controller、Action描述類的非同步版本,比較特殊的是TaskAsyncActionDescriptor,它可以在普通的(非非同步)的Controller中使用,在自己試圖搭建架構時可以模仿該方式,抽象類別,同步/非同步版本,接下來通過一個表格簡要介紹和比較以上三個描述類。
| 類型 |
簡介 |
| ControllerDescriptor |
比較特殊是GetFilterAttributes方法,用於擷取該控制器上的所有過濾器特性,ActionMethodSelectorAttribute特性包含GET, POST, PUT, DELETE, Head, Options, Patch等七個Http方法。 |
| ActionDescriptor |
GetFilters方法返回FilterInfo類型,包含ActionFilter,AuthorizationFilter,ExceptionFilter,ResultFilter等四種類型的篩選器,與J2EE類似。 |
| ParameterDescriptor |
其中屬性ParameterBindingInfo最為關鍵,實際包含ModelBinder對象,該對象是整個綁定模組的核心,同時Include,Exclude集合用於顯示設定參與/不參與綁定的屬性,Prefix屬性主要用於複雜的類型的綁定。 |
由於資料繫結的來源各不相同,架構通過介面IValueProvider類來統一提供資料。首先介紹NameValueCollectionValueProvider,屬於key/Value的形式,需要加強理解的是,在之前介紹的描述一個複雜資料類型的ModelMetadata具有樹型層次化結構,而NameValueColletion對象卻是一個"扁平"的結構,兩者的匹配通過首碼來完成。"扁平化"這個概念在現在資料呈現中出現的非常多,無論是這兒MVC中的ViewModel, 還是WPF中MVVM架構下的ViewModel。該類型的Provider主要包括FormValueProvider和QueryStringProvider,顧名思義,關聯HttpRequest中的QueryString屬性。
接下來介紹DictionaryValueProvider,與以前資料提供器的主要區別是其資料值不僅支援字串,還支援任意對象,可以使用泛型約束。主要包括RouteDataValueProvider,HttpFileCollectionProvider,ChildActionValueProvider。這裡想重點提及的是ChildActionProvider,它主要用於子Action中,由於子Action不同獨立用於響應用戶端的請求,只是用於產生部分的HTML。架構中使用ValueProviderFactory工廠類用於建立一系列的值提供器,同時使用ValueProviderFactories這個靜態類通過註冊的方式管理以上工廠。
之前有提過這部分最重要的類型就是ModelBinder,有了之前的基礎,現在是時候介紹它了。借用蔣大師的原話,"Model的綁定體現在從當前請求提取相應的資料並產生相應的對象作為調用目標Action方法的參數列表"。不知道大家還記不記得,之前在ActionDescriptor中提到的ParameterBindingInfo類型的對象,其中就有ModelBinder,為了便於理解仍然使用表格進行介紹。
| 類型 |
簡介 |
| IModelBinder |
包含BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)方法。 |
| ModelBinderAttribute |
用於自己定義模型的綁定器,之後會介紹預設綁定器,在未指定綁定時使用,也是最常見的。 |
| ModelBinders |
靜態類用於註冊模型繫結器,可以在Application_Start方法中為指定類型設定綁定器 |
| ModelBinderProvider |
GetBinder(Type modelType)方法用於根據指定的資料類型擷取相應的ModelBinder對象。 |
| ModelState |
*Model綁定除了設定參數列表以外,還將資料通過ModelState的形式儲存於Controller的ViewData中。sw |
| ModelBindingContext |
簡述Model綁定的過程,首先Action調用ActionInvoker執行Action,此時獲得ActionDescriptor對象,然後遍曆ActionDescriptor參數列表,根據ParameterDescriptor等對象建立ModelBindingContext。然後獲得指定的ModelBinder,調用GetModel得到由ValueProvider提供的相應參數值,最後以ModelState的形式儲存。 |
最後,介紹預設的Model綁定DefaultModelBinder類,這塊內容比較多,但由於是架構預設提供的,瞭解即可,主要思路如以下代碼所示。
View Code
以上可以比較明確的看到類型的綁定過程體現在GetModel方法中,所用參數均來自於綁定內容物件,通過模型名稱獲得值並轉化為指定模型類型,在簡單類型的處理上,已完全滿足。接下來是複雜類型,通過GetComplexModel方法獲得複雜物件,其實也比較好理解,綁定的過程是一個遞迴的過程,它通過反射根據資料類型建立對象,並將相關值賦到其屬性上。每一次的遞迴都是將屬性名稱作為首碼附加到現有首碼作為下一次遞迴的首碼。之後還有數組,集合,字典等類型的綁定,其中都包含一個深複刻的過程,有部分會涉及泛型方法的反射應用,都比較相似就不一一介紹了。
蔣大師的MVC架構解析確實是越學越有趣,即使是跟著學寫些範例程式碼也是收穫良多,尤其是關於類型、反射和委託等方面,平時在應用開發中確實很少會有機會寫這樣的代碼。今天學習的ASP.NET MVC中的Model的驗證,剛開時會以為這一章會比較簡單,因為之前已經學習過了Model中繼資料的解析、Model綁定,Model的驗證可能就只是DataAnnotation相關類的介紹。但實際學習的過程中,尤其是自訂用於修飾Action的驗證特性讓我到現在仍然感覺是比較萌萌噠,畢竟這一塊對於架構的擴充基本上涉及到了驗證相關的所有類型。除此之外,昨晚也是我第一次從https://aspnetwebstack.codeplex.com/上用git下載到了到MVC的源碼,本以為會比較艱難,但實際卻非常的方便,怒贊下。之後在VS2012開啟Nuget會自動下載相依元件,就可以編譯通過了。記得今年一直聽到各種關於微軟的開源計劃,自己接觸的知識領域還是比較低端,也不太清楚到底有些什麼原始碼可以看,當時首先想到的就是到目前為止仍然掌握很弱的WCF,然後查查居然也有源碼了,頓時覺得壓力山大,因為以後再做不好.NET就不能和媽媽說我看不到源碼了。原來一直關於.NET的彷徨,至少在這一刻得到很好的堅定,雖然由於市場的原因.NET在國內的發展比較飄忽,但從自身技術發展的角度,有了源碼,只要努力,我就可以生活大師的身邊,知道什麼是對的了,這個一直困惑我多年。不知道大家有沒有這樣的感受,即使對自己的代碼風格、設計理念非常認同,但重來沒有說應該這樣做的底氣。見笑了,言歸正傳,回到Model的驗證,內容比較對,篇幅很能比較長,望見諒。
首先介紹最為核心的ModelValidator抽象類別,該類的主要的成員方法包括:GetClientValidationRules(),傳回值為用戶端驗證規則,最終由HtmlHelper的模板方法渲染為html語句,由於未來項目中並不打算使用Razor引擎,這部分會略過一些內容,但之後有一部分關於JQuery-validate組件的擴充還是很有價值的;Validate(object container),傳回值為ModelValidationResult集合,需要注意的是該方法的參數container說明驗證過程是包含類型本身和其所轄的屬性成員的。接下來用圖表簡要介紹幾個MVC中的Model驗證解決方案:
| 驗證解決方案 |
簡介 |
| DataAnnotationsModelValidator |
最主要的驗證方案,包括常見的驗證特性:RequiredAttribute,RangeAttribute等 |
| ClientModelValidator |
用戶端驗證。 |
| DataErrorInfoModelValidator |
實現IDataErrorInfo介面,包括:DataErrorInfoClassModelValidator,DataErrorInfoPropertyModelValidator |
| ValidatableObjectAdapter |
實現IValidatableObject介面,也稱為"自我驗證",比較少使用。 |
這兒仍然使用Provider模式來提供相應的組件,ModelValidatorProvider類具有GetValidators(ModelMetadata metadata, ControllerContext context)
方法,前一個參數描述被驗證類型或熟悉的中繼資料對象,另一個為當前的ControllerContext。同時,具體的Provider與之前介紹的驗證解決方案的中類型相對應,在此就不一一介紹,需要注意的是在驗證一個類型時,是先驗證它的屬性,然後才驗證它自身,因此會出現驗證的短路現象,即屬性出錯,就不會繼續驗證和反饋容器類型的錯誤了。與之前一樣,這兒也會使用註冊表模式來管理Provider,使用上ModelValidatorProviders來進行註冊,架構預設會載入DataAnnotationXXX,ClientXXX,DataErrorInfoPropertyXXX,也可以把自訂的Provider加入其中。在架構中真正負責驗證工作的是一個CompositeModelValidator私人類,查看源碼確定是ModelValidator中的一個內部類,但為什麼這樣使用還有一些困惑,為什麼這樣需要完全隱藏掉該類?
接下來,介紹Model綁定與驗證的關係,在前文"Model的綁定"的介紹中提到Controller對象的ViewData包含ModelState集合,用於表示Model的狀態,其中既包括ValueProvider提供的值,也包括Errors驗證結果。驗證結果的呈現通過ValidationMessage,ValidationMessageFor擴充方法對單個屬性進行驗證,輸出html形式為(class="field-validation-error" data-valmsg-for="xxx",data-valmsg-replace="true"),ValidationSummary呈現容器整體的驗證結果,可以設定excludePropertyErrors參數。同時注意可以通過ModelState的AddModelError方法添加錯誤資訊,EditorForModel擴充方法在使用時會預設的顯示驗證錯誤時的資訊。
Model綁定中的驗證解釋起來比較拗口,但簡單說來就是DefaultModelBinder在遞迴的綁定複雜物件的過程中對綁定後的對象實施驗證,如所示。
為了更加瞭解Model綁定和驗證的關聯,自己跟著蔣大師的源碼基本原樣敲了一遍,主要裡面有一些用法自己還是不夠熟悉,多練練了,大家可以無視。
View Code View Code
繼續Model驗證的學習,順道提及一下,今天面試的時候又遇到了幾個新的問題,包括Code Smith的代碼產生工具(類似T4模板,但更方便)、Dapper.net的輕量級ORM架構、服務的等冪性等,將慢慢的學習和分享。
首先介紹最重要的基於驗證特性的聲明式Model驗證,ValidationAttribute是所有驗證特性的抽象基類,主要內容如下表所示:
| 成員變數或函數 |
簡介 |
| ErrorMessageResourceName |
錯誤訊息所在資源項的名稱 |
| ErrorMessageResourceType |
錯誤訊息所在資源項的類型 |
| IsValid() |
在驗證失敗時返一個ValidationResult對象 |
| GetValidationResult() |
實際調用受保護的IsValid() |
| TypeId |
在需要多次使用同一驗證特性時需要重寫該屬性,使得每一次的TypdId不相同。情形:假設需要控制不同層級人員用於不同工資範圍,定崗定薪。 |
繼承ValidationAttribute的驗證屬性類別如下所示:
| 特性名稱 |
| RequiredAttribute |
RangeAttribute |
| StringLengthAttribute |
Max/MinLengthAttribute |
| RegularExpressionAttribute |
CompareAttribute |
| CustomValidationAttribute |
|
在驗證過程中起作用的是DataAnnotaionsModelValidator對象的Validate方法,首先根據容器物件建立出表示驗證內容相關的ValidationContext對象,並採用ModelMetadata的DisplayName來做為內容相關的名稱,然後調用Attribute屬性的GetValidatioResult方法進行最終的驗證。同時MVC還定義了一個System.Web.Mvc.DataAnnotationsModelValidator<TAttribute>的泛型類,我們常用的RequiredAttribute均繼承於該類。
資料特性驗證的提供器其包含一個靜態驗證工廠集合ValidatableFactories,是一個以類型Type為key,指定委託DataAnnotationsValidatableObjectAdapterFactory為value的字典,比較少見的方式。該提供器的靜態構造方法中已將常見特性的驗證提供器加入,並提供靜態註冊方法註冊新的驗證提供器。之後的內容蔣大師分享了兩種擴充,一個是將ValidationAttribute應用在Action的參數上,和J2EE中Spring MVC的方式一致,以及實現同一個Model類型實現多種方式等,就不一一介紹了。
最後,簡要介紹用戶端驗證,在不用Razor引擎的前提下,這部分的主要價值就體現在關於JQuery外掛程式的擴充,關於javascript,提到最多的概念就是PE(Progressive Enhancement)漸進性增強和非入侵式Unobtrusive,最主要對的意思就是頁面可以在不支援JS的情況下顯示基本內容,再瀏覽器允許的情況增強顯示效果。這裡用到了最常見的前端驗證架構檔案jquery.validate.js,可以通過設定class的內聯方式來完成驗證,也可以直接通過validate方法來設定驗證,代碼如下:
View Code
在架構中,基於JQuery的Model驗證其實就是根據資料的驗證特性產生相應的js代碼,指定的html元素具有"data-val"屬性和一系列的以"data-val-"為首碼的屬性。並在之後一個<span>元素,該元素的CSS樣式為"field-validation-valid",當驗證失敗時替換為"field-validation-error"。在表示用戶端驗證的ModelClientValidationRule中,有一個ValidationParameters來表示驗證參數名和參數值。之後蔣大師又介紹了一個自訂驗證的例子,我只節選出js作為自己學習JQuery外掛程式的練習。
View Code
註:本文主要供自己學習,不妥之處望見諒。
參考資料:
[1]蔣金楠. ASP.NET MVC4架構揭秘[M]. 上海:電子工業出版社, 2012.
快速入門系列--MVC--04模型