.NET/ASP.NETMVC 大型站點架構設計—遷移Model後設資料設定項(自定義後設資料提供程式)

王清培發表於2014-01-20

閱讀目錄:

  • 1.需求背景介紹(Model後設資料設定項應該與View繫結而非ViewModel)
    • 1.1.確定問題域範圍(可以使用DSL管理問題域前提是鎖定領域模型) 
  • 2.遷移ViewModel設定到外部配置檔案(擴充套件Model後設資料提供程式)
    • 2.1.實現後設資料提供程式(簡單示例)

1.需求背景介紹(Model後設資料設定項應該與View繫結而非ViewModel)

使用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使用結構;

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

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

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

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

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

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

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

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

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

這樣我們就可以將一組強大的後設資料提供程式植入到ASP.NETMVC框架的內部;

通過CustomModelMetadataProviderFactory建立用於獲取任何一個外部型別的後設資料提供程式物件,比如:CustomModelMetadataProviderWithDb(用於資料庫的介面),CustomModelMetadataProviderWithConfig(使用者配置檔案),CustomModelMetadataProviderWithService(遠端Service);

遷移ModelMetadate快取資料(緊要關頭可以進行記憶體優化)

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

2.1.實現後設資料提供程式(簡單示例)

View、ViewModel、ModelMetadata 對映設計:

 1 using System.Collections.Generic;
 2 using System.Linq;
 3 using System.Web.Mvc; 
 4 
 5 namespace MvcApplication4.Seed
 6 {
 7     public enum View
 8     {
 9         HomePage_Index,
10         HomePage_Edit
11     }
12     public enum ViewModel
13     {
14         Customer
15     }
16     public class ViewMappingModelMetadata
17     {
18         public View View { get; set; }
19         public ViewModel ViewModel { get; set; }
20         public ModelMetadata Metadata { get; set; }
21     } 
22 
23     public class ViewMappingModelMetadataCollection : Dictionary<View, List<ViewMappingModelMetadata>>
24     {
25         private static ViewMappingModelMetadataCollection coll = new ViewMappingModelMetadataCollection();
26         static ViewMappingModelMetadataCollection()
27         {
28             //在Homepage下的檢視———來自外部檔案的介面,這裡只是示例顯示
29             coll.Add(View.HomePage_Index, new List<ViewMappingModelMetadata>());
30             coll[View.HomePage_Index].Add(new ViewMappingModelMetadata()
31             {
32                 View = View.HomePage_Index,
33                 ViewModel = ViewModel.Customer,
34                 Metadata =
35                     new ModelMetadata(CustomModelMetadataProviderWithConfig.CurrentProvider, typeof(Models.Customer),
36                     () => { return new Models.Customer().CustomerId; }, typeof(string), "CustomerId")
37                     {
38                         DisplayFormatString = @"HomePage\DisplayName:{0}"
39                     }
40             });
41             //在EditCustomer下的檢視——來自外部檔案的介面,這裡只是示例顯示
42             coll.Add(View.HomePage_Edit, new List<ViewMappingModelMetadata>());
43             coll[View.HomePage_Edit].Add(new ViewMappingModelMetadata()
44             {
45                 View = View.HomePage_Edit,
46                 ViewModel = ViewModel.Customer,
47                 Metadata = new ModelMetadata(
48                     CustomModelMetadataProviderWithConfig.CurrentProvider, typeof(Models.Customer),
49                     () => { return new Models.Customer().CustomerId; }, typeof(string), "CustomerId")
50                     {
51                         DisplayFormatString = @"Edit\DisplayName:{0}"
52                     }
53             });
54         }
55         public static ViewMappingModelMetadataCollection Current
56         {
57             get { return coll; }
58         } 
59 
60         public ModelMetadata GetMetadataByView(View view, ViewModel model)
61         {
62             var metaList = from item in coll[view] where item.ViewModel == model select item.Metadata;
63             return metaList != null && metaList.Count() > 0 ? metaList.LastOrDefault() : null;
64         }
65     }
66 } 

這兩段是要被放到框架內部去完成的,這裡只是為了演示其後設資料的設定原理,所以簡單這麼寫;

System.Web.Mvc.ModelMetadataProvider 實現自定義後設資料提供程式:

 

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Web.Mvc;
 4 
 5 namespace MvcApplication4.Seed
 6 {
 7     public class CustomModelMetadataProviderWithConfig : System.Web.Mvc.ModelMetadataProvider
 8     {
 9         private static CustomModelMetadataProviderWithConfig provider = new CustomModelMetadataProviderWithConfig();
10         public static CustomModelMetadataProviderWithConfig CurrentProvider
11         {
12             get { return provider; }
13         }
14 
15         public override IEnumerable<ModelMetadata> GetMetadataForProperties(object container, Type containerType)
16         {
17             throw new NotImplementedException();//複雜型別實現,屬性的迴圈獲取
18         }
19 
20         public override ModelMetadata GetMetadataForProperty(Func<object> modelAccessor, Type containerType, string propertyName)
21         {
22             throw new NotImplementedException();//複雜型別實現,屬性的迴圈獲取
23         }
24 
25         public override ModelMetadata GetMetadataForType(Func<object> modelAccessor, Type modelType)
26         {
27             if (modelAccessor == null) return null;
28             if (System.Web.HttpContext.Current.Session["viewname"] == null) return null;
29             var result = ViewMappingModelMetadataCollection.Current.GetMetadataByView(
30                     (View)System.Web.HttpContext.Current.Session["viewname"], (ViewModel)System.Web.HttpContext.Current.Session["viewmodel"]);
31             if (modelAccessor != null)
32                 result.Model = modelAccessor().GetType().GetProperty("CustomerId").GetValue(modelAccessor());
33             return result;
34         }
35     }
36 }

Customer模型定義:

1 public class Customer
2 {
3     public string CustomerId { get; set; }
4 } 

在模型上我們沒有應用任何一個 後設資料控制特性,但是我們將在介面上看到效果;

View 檢視定義:

 1 @model  MvcApplication4.Models.Customer 
 2 
 3 <table>
 4     <tr>
 5         <td>
 6             <h2>編輯模式.</h2>
 7             <h3>@Html.DisplayForModel(Model.CustomerId)</h3>
 8         </td>
 9     </tr>
10 </table> 
11 
12 @model  MvcApplication4.Models.Customer 
13 
14 <table>
15     <tr>
16         <td>
17             <h2>顯示模式.</h2>
18             <h3>@Html.EditorForModel(Model.CustomerId)</h3>
19         </td>
20     </tr>
21 </table> 
22 
23 這是兩種模型的呈現方式; 

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

 

相關文章