9.2.2 .net framework下的MVC 控制項的封裝(下)

来源:http://www.cnblogs.com/BenDan2002/archive/2016/12/07/6129395.html
-Advertisement-
Play Games

控制項封裝的部分說明 可能有人覺得應該前後端分離,我也承認這是應該的方向,我們也在考慮使用ng2等簡化前端。但是,我們封裝控制項還是因為如下原因綜合考慮的: 我們這是個框架,上面支撐了許多個應用,包含幾百個頁面,每個頁面都去寫一堆的js\css\html標簽可能對開發人員來說非常麻煩,且每個人寫的都可能 ...


控制項封裝的部分說明

可能有人覺得應該前後端分離,我也承認這是應該的方向,我們也在考慮使用ng2等簡化前端。但是,我們封裝控制項還是因為如下原因綜合考慮的:

  • 我們這是個框架,上面支撐了許多個應用,包含幾百個頁面,每個頁面都去寫一堆的js\css\html標簽可能對開發人員來說非常麻煩,且每個人寫的都可能不一樣。為了更簡化中、低級開發人員的工作才提供的這種封裝,個人認為這樣才是簡化和標準化開發的做法
  • 像我們這裡有datatable、文件上傳等的控制項,datatable就包含分頁、超鏈、排序、格式化等等,js非常複雜,附件上傳更複雜,這個不做封裝實在不方便使用
  • 我們後面有自定義表單、自定義數據查詢等功能,控制項都是通過拖拽生成的,必須使用封裝方式

其實呢,mvc也提供了html.textfor等寫法,其中有的也封裝了js的,甚至校驗也是封裝的js。本節內容進階二,是直接使用cshtml,部分做到了前後端分離。當然了,如果有更好的建議和做法,歡迎提出來。

 

看本篇之前,建議先看一下上一篇9.2.1 .net framework下的MVC 控制項的封裝(上)

進階一:For類型控制項的做法

我們在上一篇的最開始樣例中,寫了MVC控制項的三種寫法。

1 @model UserInfo
2 
3  
4 <input type="text" id="t2" value="t2Value" /> <!—第一種寫法 -->
5 
6 @Html.TextBox("t1", "t1value"); <!—第二種寫法 -->
7 
8 @Html.TextBoxFor(user => user.EMail) <!—第三種寫法 -->

 

第一種是html的原始寫法,控制項的封裝不會用這種方法(.net core的taghelper就可以寫成這樣了)。在上一篇的介紹中,我們講解了按照第二種樣子來做控制項的封裝。但是當我們的控制項要綁定cshtml頁面的model屬性時,要寫成第三種方式,也就是寫成類似@Html.TextboxFor(user => user.Email)的For寫法。

這裡的user是指的UserInfo,這個寫法就是在頁面中生成Id為Email的input元素,值也自動填充UserInfo的實例的電子郵件地址EMail,並且如果UserInfo類的Email屬性上有校驗、顯示名等Attribute時,可以自動生成校驗腳本和標簽。例如下麵就是對UserName和Age兩個屬性增加顯示名稱和校驗。

1 [Display(Name="用戶名")]
2 [Required(ErrorMessage = "*姓名必填")]
3 public string UserName { get; set; }
4 
5 [Display(Name = "年齡")]
6 [Required(ErrorMessage = "*年齡必填")]
7 public int Age { get; set; }

 這種寫法,會在最終生成的html代碼中,自動添加用戶名和年齡的標簽和校驗腳本。

 

既然這種寫法可以自動綁定model,也可以自動生成標簽和校驗等,我們該如何實現For的寫法呢?下麵我們仍舊以下拉多選控制項為例,進行說明。

同樣的,我們需要寫一個MultiSelectFor的控制項,這個For控制項是個泛型類

 1     public class MultiSelectFor<TModel, TProperty> : MvcControlForBase<TModel, TProperty>
 2     {
 3         /// <summary>
 4         /// 初始化
 5         /// </summary>
 6         /// <param name="htmlHelper"></param>
 7         /// <param name="expression"></param>
 8         /// <param name="htmlAttributes"></param>
 9         public MultiSelectFor(HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, object htmlAttributes)
10             : base(htmlHelper, expression, htmlAttributes)
11         {
12             this.DataSource = new Dictionary<string, string>();
13         }
14         ……
15     }

 

繼承泛型的控制項基類MvcControlForBase<TModel, TProperty>,一樣的也要有一個泛型的基控制項構造類

public abstract class MvcControlForBuilderBase<TModel, TProperty, TMvcControl, TBuilder>

  where TMvcControl : MvcControlForBase<TModel, TProperty>

  where TBuilder : MvcControlForBuilderBase<TModel, TProperty, TMvcControl, TBuilder>

這兩個類的實現與上一篇很類似。我們不再仔細介紹了,唯一需要註意的是控制項基類MvcControlForBase<TModel, TProperty>的Name和Id是從表達式中獲取的,而不是通過構造函數的參數傳入的。因此構造函數傳入的是Expression<Func<TModel, TProperty>>。控制項的Name可以自動從Expression中獲得,因此可以直接改為只讀屬性。

 1     protected string Name
 2     {
 3         get
 4         {
 5             if (Attributes.ContainsKey("name"))
 6             {
 7                 return Attributes["name"].ToString();
 8             }
 9             else
10             {
11                 return ExpressionHelper.GetExpressionText(Expression);
12             }
13         }
14     }

 

MultiSelectFor因為能夠自動生成Id、Name、Value、Label等,因此構造函數中也就不再傳入Label、value等參數,而是傳入表達式Expression,這些數據應該直接從for的表達式中獲取。為了實現獲取value,我們需要在render方法中,增加下麵的內容

ModelMetadata metadata = ModelMetadata.FromLambdaExpression<TModel, TProperty>(this.Expression, this.Helper.ViewData);

List<string> value = metadata.Model as List<string>;

以及生成select控制項時,寫法也更簡單了,直接從表達式生成

divTag.InnerHtml = Helper.DropDownListFor(Expression, selectList, HtmlAttributes).ToHtmlString();

同樣的,按照上一篇的做法,如果在cshtml頁面中按照如下的方式使用multiselect控制項(控制項的含義是:選擇一個人的多個職責,Duty是職責,一個人可能有多個職責,例如項目經理、高級開發工程師)

@model UserInfo

@HtmlHelper.MultiSelectFor(p => p.Duty).SetDataSource().Render()

HtmlHelper擴展方法也需要增加一個:

1     public static MultiSelectForBuilder<TModel, TProperty> MultiSelectFor<TModel, TProperty>(this HtmlHelper<TModel> helper, Expression<Func<TModel, TProperty>> expression, object htmlAttributes = null)
2     {
3         return new MultiSelectForBuilder<TModel, TProperty>(new MultiSelectFor<TModel, TProperty>(helper, expression, htmlAttributes));
4     }

 

進階二:資源性視圖的應用

為了更簡化WriteHtml和WriteScript的寫法,我們將腳本和html元素都寫到一個cshtml中,這樣代碼看起來更簡潔,也更易讀,也可以實現前後端的分離。我們以日期選擇控制項DatePicker為例,DataPicker.cshtml內容如下:

 1 @{
 2     string id = this.ViewData.GetString("Id");
 3     string name = this.ViewData.GetString("Name");
 4     string dateFormat = this.ViewData.GetString("DateFormat");
 5     bool readOnly = this.ViewData.GetBool("ReadOnly");
 6 }
 7 @if (!readOnly)
 8 {
 9     <script>
10         require(['jquery', 'jquery.ui', 'ready!'], function ($) {
11             var options = {
12                 changeYear: true,
13                 changeMonth: true,
14                 dateFormat: "@(dateFormat.ToLower().Replace("yyyy", "yy"))"
15             };
16             $("#@(id)").datepicker(options);
17         });
18     </script>
19 }

 

寫成這樣的好處就是不要在WriteHtml和WriteScript中寫一大堆的腳本、html標簽的拼接程式。

再回到DataPicker控制項來,這樣datapicker不需要再寫WriteScript方法,僅僅重寫WriteHtml方法,這個方法主要工作也簡化為:傳入ViewData,然後調用部分視圖DataPicker.cshtml:

1 ViewDataDictionary vdata = new ViewDataDictionary();
2 
3 vdata["Id"] = this.Id;
4 vdata["Name"] = this.Name;
5 vdata["DateFormat"] = this.DateFormat;
6 vdata["ReadOnly"] = (this.DisplayStatus == FieldDisplayStatus.ReadOnly);
7 
8 writer.Write(Helper.Partial("MicroLibrary.Presentation.Web.Controls.CommControls.DatePicker.Views.DatePicker", vdata));

 

這裡需要註意的就是Helper.Partial方法,我們這裡調用的是這個重載方法

 1 //
 2 // 摘要:
 3 //     以 HTML 編碼字元串的形式呈現指定的分部視圖。
 4 //
 5 // 參數:
 6 //   htmlHelper:
 7 //     此方法擴展的 HTML 幫助器實例。
 8 //
 9 //   partialViewName:
10 //     要呈現的分部視圖的名稱。
11 //
12 //   viewData:
13 //     用於分部視圖的視圖數據字典。
14 //
15 // 返回結果:
16 //     以 HTML 編碼字元串形式呈現的分部視圖。
17 public static MvcHtmlString Partial(this HtmlHelper htmlHelper, string partialViewName, ViewDataDictionary viewData);

 

Helper.Partial的參數是分部視圖名稱,但是為什麼是MicroLibrary.Presentation.Web.Controls.CommControls.DatePicker.Views.DatePicker這樣的寫法呢?

 

我們前面提到,我們又更近一步做了封裝,將這些控制項都放到一個項目中,打包成應用程式集給各個項目使用。控制項使用的cshtml也會被打包進應用程式集中,做法就是將cshtml文件屬性設置為嵌入的資源,編譯時就會以資源的方式直接打包在dll中。當然了,也可以將控制項的cshtml複製到Web項目中,但是這種做法確實有些土,我們用的是更高大上的做法J

打包進應用程式集的文件的寫法就是這樣的,類名的全名稱,例如這裡的DataPicker.cshtml就是MicroLibrary.Presentation.Web.Controls.CommControls.DatePicker.Views.DatePicker。

但是怎麼通過MicroLibrary.Presentation.Web.Controls.CommControls.DatePicker.Views.DatePicker來定位DataPicker.cshtml呢?這涉及到頁面查找的問題,說來話長了。

一般情況下,Razor視圖引擎的基類是RazorViewEngine,繼承於抽象類型BuildManagerViewEngine,再繼承自VirtualPathProviderViewEngine。ViewEngine中的方法FindView和FindPartialView是按照如下的目錄進行搜索的:

  • ~/Views/{ControllerName}/{ViewName}.cshtml
  • ~/Views/Shared/{ViewName}.cshtml
  • ~/Areas/{AreaName}/Views/{ControllerName}/{ViewName}.cshtml
  • ~/Areas/{AreaName}/Views/ Shared /{ViewName}.cshtml

我們可以繼承VirtualPathProviderViewEngine,重寫FindView和FindPartialView修改上面列表,以適應自己系統的目錄搜索要求。

這種方式對於文件系統方式下的目錄搜索是可以的,但是如果視圖文件存放在資料庫、Dll的資源中,這種方式就不好使了。這時候,應該是使用更底層的VirtualPathProvider。MSDN中的說明是這樣的:VirtualPathProvider提供了一組可讓 Web 應用程式從虛擬文件系統中檢索資源的方法。我們這裡就是通過修改它來完成從Dll的資源中獲取視圖文件。

在Global.asax中的寫法如下:

1 var embeddedViewResolver = new EmbeddedViewResolver();
2 var viewTable = embeddedViewResolver.GetEmbeddedViews();
3 var embeddedProvider = new EmbeddedViewVirtualPathProvider(viewTable);
4 HostingEnvironment.RegisterVirtualPathProvider(embeddedProvider);

 

第一行,創建資源性視圖的解析類,這個類初始化時,先找到控制項所在的程式集。

1     public class EmbeddedViewResolver : IEmbeddedViewResolver
2     {
3         private IList<Assembly> assemblies;
4 
5         public EmbeddedViewResolver() {
6             this.assemblies = new List<Assembly>() { Assembly.GetAssembly(typeof(MvcControlBase)) };
7         }
8     }

第二行,解析類從控制項所在程式集中找到所有的資源行視圖,存放到EmbeddedViewTable中。EmbeddedViewTable相當與一個Dictionary,存放了ViewName和ViewMetaData的鍵值對。EmbeddedViewTable和ViewMetaData相對比較簡單,不做介紹了。

 1         public EmbeddedViewTable GetEmbeddedViews()
 2         {
 3             if (assemblies == null || assemblies.Count == 0) return null;
 4 
 5             var table = new EmbeddedViewTable();
 6 
 7             foreach (var assembly in assemblies)
 8             {
 9                 var names = GetNamesOfAssemblyResources(assembly);
10                 if (names == null || names.Length == 0) continue;
11 
12                 foreach (var name in names)
13                 {
14                     var key = name.ToLowerInvariant();
15                    
16                     if (!key.Contains(".views.")) continue; //要求所有的資源性視圖都應該在views目錄下
17 
18                     table.AddView(name, assembly.FullName);
19                 }
20             }
21 
22             return table;
23         }
24 
25         private string[] GetNamesOfAssemblyResources(Assembly assembly)
26         {
27             try
28             {
29                 return assembly.GetManifestResourceNames();
30             }
31             catch
32             {
33                 return new string[] { };
34             }
35         }

第三行,進入正題了,創建繼承於VirtualPathProvider的資源性視圖提供器EmbeddedViewVirtualPathProvider。重寫GetFile,如果是資源性視圖,去EmbeddedViewTable中獲取視圖,否則調用Previous.GetFile(virtualPath)繼續系統預設方式。同時也重寫FileExists和GetCacheDependency方法。

 1     public class EmbeddedViewVirtualPathProvider : VirtualPathProvider
 2     {
 3         private readonly EmbeddedViewTable _embeddedViews;
 4 
 5         public EmbeddedViewVirtualPathProvider(EmbeddedViewTable embeddedViews)
 6         {
 7             MicroLibraryExceptionHelper.IsNull(embeddedViews, this.GetType().FullName, TraceLogType.Error, "embeddedViews為空");
 8 
 9             this._embeddedViews = embeddedViews;
10         }
11 
12         private bool IsEmbeddedView(string virtualPath)
13         {
14             if (string.IsNullOrEmpty(virtualPath))
15                 return false;
16 
17             string virtualPathAppRelative = VirtualPathUtility.ToAppRelative(virtualPath);
18             if (!virtualPathAppRelative.StartsWith("~/Views/", StringComparison.InvariantCultureIgnoreCase))
19                 return false;
20             
21             var fullyQualifiedViewName = virtualPathAppRelative.Substring(virtualPathAppRelative.LastIndexOf("/") + 1, virtualPathAppRelative.Length - 1 - virtualPathAppRelative.LastIndexOf("/"));
22 
23             bool isEmbedded = _embeddedViews.ContainsEmbeddedView(fullyQualifiedViewName);
24             return isEmbedded;
25         }
26 
27         public override bool FileExists(string virtualPath)
28         {
29             return (IsEmbeddedView(virtualPath) ||
30                     Previous.FileExists(virtualPath));
31         }
32 
33         public override VirtualFile GetFile(string virtualPath)
34         {
35             if (IsEmbeddedView(virtualPath))
36             {
37                 string virtualPathAppRelative = VirtualPathUtility.ToAppRelative(virtualPath);
38                 var fullyQualifiedViewName = virtualPathAppRelative.Substring(virtualPathAppRelative.LastIndexOf("/") + 1, virtualPathAppRelative.Length - 1 - virtualPathAppRelative.LastIndexOf("/"));
39 
40                 var embeddedViewMetadata = _embeddedViews.FindEmbeddedView(fullyQualifiedViewName);
41                 return new EmbeddedResourceVirtualFile(embeddedViewMetadata, virtualPath);
42             }
43 
44             return Previous.GetFile(virtualPath);
45         }
46 
47         public override CacheDependency GetCacheDependency(
48             string virtualPath,
49             IEnumerable virtualPathDependencies,
50             DateTime utcStart)
51         {
52             return IsEmbeddedView(virtualPath)
53                 ? null : Previous.GetCacheDependency(virtualPath, virtualPathDependencies, utcStart);
54         }
55     }

大家可能註意到GetFile返回的是EmbeddedResourceVirtualFile這個類,這個類繼承自VirtualFile,主要的方法是Open,就是從應用程式集的資源中返回當前資源性視圖的stream。

 1     public class EmbeddedResourceVirtualFile : VirtualFile
 2     {
 3         private readonly EmbeddedViewMetadata _embeddedViewMetadata;
 4 
 5         public EmbeddedResourceVirtualFile(EmbeddedViewMetadata embeddedViewMetadata, string virtualPath)
 6             : base(virtualPath)
 7         {
 8             MicroLibraryExceptionHelper.IsNull(embeddedViewMetadata, this.GetType().FullName, TraceLogType.Error, "embeddedViewMetadata 為空");
 9 
10             this._embeddedViewMetadata = embeddedViewMetadata;
11         }
12 
13         public override Stream Open()
14         {
15             Assembly assembly = GetResourceAssembly();
16             return assembly == null ? null : assembly.GetManifestResourceStream(_embeddedViewMetadata.Name);
17         }
18 
19         private Assembly GetResourceAssembly()
20         {
21             return AppDomain.CurrentDomain.GetAssemblies()
22                 .Where(assembly => string.Equals(assembly.FullName, _embeddedViewMetadata.AssemblyFullName, StringComparison.InvariantCultureIgnoreCase))
23                 .FirstOrDefault();
24         }
25     }

第四行,在系統中註冊我們的EmbeddedViewVirtualPathProvider。

 

通過上面的四個步驟,就可以完成資源性視圖的解析工作了,通過MicroLibrary.Presentation.Web.Controls.CommControls.DatePicker.Views.DatePicker來定位cshtml也得以實現。

 

面向雲的.net core開發框架


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

-Advertisement-
Play Games
更多相關文章
  • -- 帶參輸出的存儲過程 --測試方法:--declare @return int--exec 測試用勿刪 1, @return out--print @return-- Create PROCEDURE [dbo].[測試用勿刪]@userid int, @return int outASbegi ...
  • 網上一直說的是先安裝SQL Server 2014,再安裝VS2015,軟體就不會出現問題。我這次在什麼都沒準備的情況下安裝了VS2015,安裝之後發覺VS2015自帶的SQL2014只有連接伺服器和管理資料庫的功能,能有操作資料庫的功能,不能進入到SQL Server 2014 Managemen ...
  • 從0開始搭建SQL Server AlwaysOn 第四篇(配置異地機房節點) 第一篇http://www.cnblogs.com/lyhabc/p/4678330.html第二篇http://www.cnblogs.com/lyhabc/p/4682028.html第三篇http://www.cn ...
  • 安裝Oracle時出現環境變數Path的值大於1023的解決辦法 ...
  • 建庫 CREATE DATABASE 資料庫名 ON[PRIMARY] --預設屬於PRIMARY主文件組,可省略 ( NAME='', --主數據文件的邏輯名 名稱 FILEAME='', --主數據文件的物理名 路徑 .mdf 次資料庫為.ndf SIZE=5mb, --主數據文件初始大小 MA ...
  • 前言 很多時候資料庫的TempDB、日誌等文件的暴增可能導致磁碟空間被占滿,如果日常配置不到位,往往會導致資料庫故障,業務被迫中斷。 這種文件暴增很難排查,經驗不足的一些運維人員可能更是無法排查具體原因,導致問題不能徹底解決。 場景描述 客戶系統比較穩定,用了5台機器做了AlwaysOn高可用組,完 ...
  • MySQL 執行計劃 在SQL優化時,查看執行計劃,是一個有效的途徑。 1、Explain 語法 2、Explain 列說明 在mysql中,一個執行通常包括面的列: 下麵的列說明中,會使用這個例子進行說明。 2.1 id 通過Id,可以看到sql的執行順序。 預設情況下,一個簡單的select的i ...
  • 前言:打算做一個藥材價格查詢的功能,但剛開始一點數據都沒有靠自己找信息錄入的話很麻煩的,所以只有先到其它網站抓取存到資料庫再開始做這個了。 HtmlAgilityPack在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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...