天天看點

.NET/ASP.NETMVC 大型站點架構設計—遷移Model中繼資料設定項(自定義中繼資料提供程式)1.需求背景介紹(Model中繼資料設定項應該與View綁定而非ViewModel)2.遷移ViewModel設定到外部配置檔案(擴充Model中繼資料提供程式)

閱讀目錄:

1.需求背景介紹(Model中繼資料設定項應該與View綁定而非ViewModel)

1.1.确定問題域範圍(可以使用DSL管理問題域前提是鎖定領域模型)

2.遷移ViewModel設定到外部配置檔案(擴充Model中繼資料提供程式)

2.1.實作中繼資料提供程式(簡單示例)

使用ASP.NETMVC建構普通的中小型站點可以使用簡單的Model中繼資料設定方式來控制ViewModel如何顯示在View中,但是複雜的應用場景不會這麼簡單的就能完成;大型站點的ViewModel的體積非常大,真的大的超乎我們的想象(當然這裡面有曆史原因),這麼大的一個顯示實體我們需要在不同的頁面中呈現它會非常的棘手;然而小型站點不太會遇見ViewModel在幾十個頁面中顯示的情況出現,一般頁面也就是幾十個差不多了;

在大型電子商務應用中,UI層的一個ViewModel不僅用來呈現資料還充當着與遠端SOA接口通訊的DTO作用,如果為了結構清晰完全可以将ViewModel與DTO分開,但是有時候我們确實需要考慮額外的性能開銷(有時候我們隻能接受曆史遺留的問題,技術債務累積多久就要還多久);

這篇文章将講解如何利用ASP.NETMVC開發大型站點時ViewModel中設定的中繼資料設定項随着不同的業務View不同而調用不同的中繼資料設定項,簡單的講也就是我們不會直接在ViewModel上應用中繼資料控制特性,而是通過将Model中繼資料設定項與具體的View綁定的方式來控制它在不同的View中運用不同的中繼資料控制項,中繼資料控制特性不會和具體的ViewModel綁定而是和具體的View綁定,因為隻有View才是固定呈現什麼内容,而ViewModel是用來共用的顯示資料項的容器,我将通過本篇文章來講解如何設計這樣的高擴充性的ASP.NETMVC ViewModel使用結構;

在考慮使用配置檔案将所需要的東西配置起來的時候,我們需要先确定到底需要将什麼配置起來;這就需要我們先确定問題域,其實這也就是面向DSL程式設計的方法;

DSL:簡單了解為面向特定領域的語言;該語言主要用來解決特定領域的實作問題,剛開始我們可以會把這個概念定義的過于龐大,希望能通過DSL解決一切領域問題,其實這是錯誤的;DSL其實是一小部分領域問題的提煉,如:我們這裡的将ModelMetadata設定特性從原來定義在ViewModel上的遷移到外部去,這其中的主要問題域就是将ModelMetadata設定項與View綁定,而不是ViewModel;

隻有先準确的找到問題域之後我們才能設計DSL來充分的表達這個問題域,通過XML能很好的表達任何特定領域結構的模型,當然你完全可以自己去設計DSL;

<a href="http://s3.51cto.com/wyfs02/M01/11/C1/wKioL1Lcu8iwXQu9AAKOmvUhr9o430.jpg" target="_blank"></a>

目前對ViewModel中設定的中繼資料控制特性都會作用于使用該ViewModel的所有View,我們要解決的問題是将上圖中的ModelMetadata域提取出去與View進行綁定,進而得到一個幹淨的ViewModel和靈活的面向View的中繼資料控制功能;當我們成功遷移之後,我們将得到下圖中的結構;

<a href="http://s3.51cto.com/wyfs02/M00/11/C1/wKiom1LcvATA23U_AALqvBadUhY734.jpg" target="_blank"></a>

最終我們會得出這樣的一個滿足實際需求的結構;

要想成功遷移設定項我們必須要搞清楚ASP.NETMVC中Model中繼資料提供程式的原理,這樣我們才能将原來擷取中繼資料的方式改變成我們自己的擷取政策;在中繼資料提供程式對象模型中主要的功能分為兩部分(這裡我們隻介紹擷取中繼資料過程):

<a href="http://s3.51cto.com/wyfs02/M01/11/C1/wKiom1LcvCXhnVA6AAE0vo5OTB8326.jpg" target="_blank"></a>

我們需要将BuildModelMetadata功能區域換成我們自己的政策;

<a href="http://s3.51cto.com/wyfs02/M00/11/C1/wKioL1LcvBWiNp8wAAI79GDi5Hw775.jpg" target="_blank"></a>

這樣我們就可以将一組強大的中繼資料提供程式植入到ASP.NETMVC架構的内部;

通過CustomModelMetadataProviderFactory建立用于擷取任何一個外部類型的中繼資料提供程式對象,比如:CustomModelMetadataProviderWithDb(用于資料庫的接口),CustomModelMetadataProviderWithConfig(使用者配置檔案),CustomModelMetadataProviderWithService(遠端Service);

遷移ModelMetadate緩存資料(緊要關頭可以進行記憶體優化) 在ASP.NETMVC内部提供了用來擷取某個ViewModel的ModelMetadata的提供程式,通過該入口我們将可以把Model中繼資料緩存在我們自己的容器中,當然絕佳的緩存位置就是目前應用伺服器的本地程序,這裡是最好的緩存位置,我們緩存中繼資料主要不是為了改變它的存放位置而是要改變它擷取的途徑和方式,這樣其實會有很多好處,比如:通過工具化管理記憶體中的緩存資料,對其進行壓縮等等,因為你已經可以控制其擷取中繼資料的過程,這在緊要關頭可能就是救命稻草,這裡隻是一點擴充性的介紹,當然要看具體的需求了,不過這确實是一個好的思路;

View、ViewModel、ModelMetadata 映射設計:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

<code>using</code> <code>System.Collections.Generic;</code>

<code>using</code> <code>System.Linq;</code>

<code>using</code> <code>System.Web.Mvc;</code>

<code>namespace</code> <code>MvcApplication4.Seed</code>

<code>{</code>

<code>    </code><code>public</code> <code>enum</code> <code>View</code>

<code>    </code><code>{</code>

<code>        </code><code>HomePage_Index,</code>

<code>        </code><code>HomePage_Edit</code>

<code>    </code><code>}</code>

<code>    </code><code>public</code> <code>enum</code> <code>ViewModel</code>

<code>        </code><code>Customer</code>

<code>    </code><code>public</code> <code>class</code> <code>ViewMappingModelMetadata</code>

<code>        </code><code>public</code> <code>View View { </code><code>get</code><code>; </code><code>set</code><code>; }</code>

<code>        </code><code>public</code> <code>ViewModel ViewModel { </code><code>get</code><code>; </code><code>set</code><code>; }</code>

<code>        </code><code>public</code> <code>ModelMetadata Metadata { </code><code>get</code><code>; </code><code>set</code><code>; }</code>

<code>    </code><code>public</code> <code>class</code> <code>ViewMappingModelMetadataCollection : Dictionary&lt;View, List&lt;ViewMappingModelMetadata&gt;&gt;</code>

<code>        </code><code>private</code> <code>static</code> <code>ViewMappingModelMetadataCollection coll = </code><code>new</code> <code>ViewMappingModelMetadataCollection();</code>

<code>        </code><code>static</code> <code>ViewMappingModelMetadataCollection()</code>

<code>        </code><code>{</code>

<code>            </code><code>//在Homepage下的視圖———來自外部檔案的接口,這裡隻是示例顯示</code>

<code>            </code><code>coll.Add(View.HomePage_Index, </code><code>new</code> <code>List&lt;ViewMappingModelMetadata&gt;());</code>

<code>            </code><code>coll[View.HomePage_Index].Add(</code><code>new</code> <code>ViewMappingModelMetadata()</code>

<code>            </code><code>{</code>

<code>                </code><code>View = View.HomePage_Index,</code>

<code>                </code><code>ViewModel = ViewModel.Customer,</code>

<code>                </code><code>Metadata =</code>

<code>                    </code><code>new</code> <code>ModelMetadata(CustomModelMetadataProviderWithConfig.CurrentProvider, </code><code>typeof</code><code>(Models.Customer),</code>

<code>                    </code><code>() =&gt; { </code><code>return</code> <code>new</code> <code>Models.Customer().CustomerId; }, </code><code>typeof</code><code>(</code><code>string</code><code>), </code><code>"CustomerId"</code><code>)</code>

<code>                    </code><code>{</code>

<code>                        </code><code>DisplayFormatString = </code><code>@"HomePage\DisplayName:{0}"</code>

<code>                    </code><code>}</code>

<code>            </code><code>});</code>

<code>            </code><code>//在EditCustomer下的視圖——來自外部檔案的接口,這裡隻是示例顯示</code>

<code>            </code><code>coll.Add(View.HomePage_Edit, </code><code>new</code> <code>List&lt;ViewMappingModelMetadata&gt;());</code>

<code>            </code><code>coll[View.HomePage_Edit].Add(</code><code>new</code> <code>ViewMappingModelMetadata()</code>

<code>                </code><code>View = View.HomePage_Edit,</code>

<code>                </code><code>Metadata = </code><code>new</code> <code>ModelMetadata(</code>

<code>                    </code><code>CustomModelMetadataProviderWithConfig.CurrentProvider, </code><code>typeof</code><code>(Models.Customer),</code>

<code>                        </code><code>DisplayFormatString = </code><code>@"Edit\DisplayName:{0}"</code>

<code>        </code><code>}</code>

<code>        </code><code>public</code> <code>static</code> <code>ViewMappingModelMetadataCollection Current</code>

<code>            </code><code>get</code> <code>{ </code><code>return</code> <code>coll; }</code>

<code>        </code><code>public</code> <code>ModelMetadata GetMetadataByView(View view, ViewModel model)</code>

<code>            </code><code>var</code> <code>metaList = </code><code>from</code> <code>item </code><code>in</code> <code>coll[view] </code><code>where</code> <code>item.ViewModel == model </code><code>select</code> <code>item.Metadata;</code>

<code>            </code><code>return</code> <code>metaList != </code><code>null</code> <code>&amp;&amp; metaList.Count() &gt; 0 ? metaList.LastOrDefault() : </code><code>null</code><code>;</code>

<code>}</code>

<a href="http://s3.51cto.com/wyfs02/M02/11/C1/wKiom1LcvGOyaIhUAAMWffXtoP8729.jpg" target="_blank"></a>

這兩段是要被放到架構内部去完成的,這裡隻是為了示範其中繼資料的設定原理,是以簡單這麼寫;

System.Web.Mvc.ModelMetadataProvider 實作自定義中繼資料提供程式:

<code>using</code> <code>System;</code>

<code>    </code><code>public</code> <code>class</code> <code>CustomModelMetadataProviderWithConfig : System.Web.Mvc.ModelMetadataProvider</code>

<code>        </code><code>private</code> <code>static</code> <code>CustomModelMetadataProviderWithConfig provider = </code><code>new</code> <code>CustomModelMetadataProviderWithConfig();</code>

<code>        </code><code>public</code> <code>static</code> <code>CustomModelMetadataProviderWithConfig CurrentProvider</code>

<code>            </code><code>get</code> <code>{ </code><code>return</code> <code>provider; }</code>

<code>        </code><code>public</code> <code>override</code> <code>IEnumerable&lt;ModelMetadata&gt; GetMetadataForProperties(</code><code>object</code> <code>container, Type containerType)</code>

<code>            </code><code>throw</code> <code>new</code> <code>NotImplementedException();</code><code>//複雜類型實作,屬性的循環擷取</code>

<code>        </code><code>public</code> <code>override</code> <code>ModelMetadata GetMetadataForProperty(Func&lt;</code><code>object</code><code>&gt; modelAccessor, Type containerType, </code><code>string</code> <code>propertyName)</code>

<code>            </code><code>throw</code> <code>new</code> <code>NotImplementedException();</code><code>//複雜類型實作,屬性的循環擷取</code>

<code>        </code><code>public</code> <code>override</code> <code>ModelMetadata GetMetadataForType(Func&lt;</code><code>object</code><code>&gt; modelAccessor, Type modelType)</code>

<code>            </code><code>if</code> <code>(modelAccessor == </code><code>null</code><code>) </code><code>return</code> <code>null</code><code>;</code>

<code>            </code><code>if</code> <code>(System.Web.HttpContext.Current.Session[</code><code>"viewname"</code><code>] == </code><code>null</code><code>) </code><code>return</code> <code>null</code><code>;</code>

<code>            </code><code>var</code> <code>result = ViewMappingModelMetadataCollection.Current.GetMetadataByView(</code>

<code>                    </code><code>(View)System.Web.HttpContext.Current.Session[</code><code>"viewname"</code><code>], (ViewModel)System.Web.HttpContext.Current.Session[</code><code>"viewmodel"</code><code>]);</code>

<code>            </code><code>if</code> <code>(modelAccessor != </code><code>null</code><code>)</code>

<code>                </code><code>result.Model = modelAccessor().GetType().GetProperty(</code><code>"CustomerId"</code><code>).GetValue(modelAccessor());</code>

<code>            </code><code>return</code> <code>result;</code>

Customer模型定義:

<code>public</code> <code>class</code> <code>Customer</code>

<code>    </code><code>public</code> <code>string</code> <code>CustomerId { </code><code>get</code><code>; </code><code>set</code><code>; }</code>

在模型上我們沒有應用任何一個 中繼資料控制特性,但是我們将在界面上看到效果;

View 視圖定義:

<code>@model  MvcApplication4.Models.Customer</code>

<code>&lt;</code><code>table</code><code>&gt;</code>

<code>    </code><code>&lt;</code><code>tr</code><code>&gt;</code>

<code>        </code><code>&lt;</code><code>td</code><code>&gt;</code>

<code>            </code><code>&lt;</code><code>h2</code><code>&gt;編輯模式.&lt;/</code><code>h2</code><code>&gt;</code>

<code>            </code><code>&lt;</code><code>h3</code><code>&gt;@Html.DisplayForModel(Model.CustomerId)&lt;/</code><code>h3</code><code>&gt;</code>

<code>        </code><code>&lt;/</code><code>td</code><code>&gt;</code>

<code>    </code><code>&lt;/</code><code>tr</code><code>&gt;</code>

<code>&lt;/</code><code>table</code><code>&gt;</code>

<code>            </code><code>&lt;</code><code>h2</code><code>&gt;顯示模式.&lt;/</code><code>h2</code><code>&gt;</code>

<code>            </code><code>&lt;</code><code>h3</code><code>&gt;@Html.EditorForModel(Model.CustomerId)&lt;/</code><code>h3</code><code>&gt;</code>

這是兩種模型的呈現方式;

<a href="http://s3.51cto.com/wyfs02/M01/11/C1/wKioL1LcvIDwwb2EAACcV13jnlc872.jpg" target="_blank"></a>

<a href="http://s3.51cto.com/wyfs02/M00/11/C1/wKiom1LcvLODkgW3AACQDh8jEJI862.jpg" target="_blank"></a>

我們自動設定的中繼資料已經起到效果了;

 本文轉自 王清培 51CTO部落格,原文連結:http://blog.51cto.com/wangqingpei557/1353112,如需轉載請自行聯系原作者

繼續閱讀