在寫.net core下mvc控件的編寫以前,我先說一下.net framework下咱們MVC控件的作法。 css
MVC下控件的寫法,主要有以下三種,最後一種是泛型的寫法,mvc提供的控件都是基本控件。html
1 @model UserInfo 2 3 <input type="text" id="t2" value="t2Value" /> <!—第一種寫法 --> 4 @Html.TextBox("t1", "t1value"); <!—第二種寫法 --> 5 @Html.TextBoxFor(user => user.EMail) <!—第三種寫法 -->
可是咱們在寫大型系統的時候,像自動完成autocomplete、下拉多選multiselect、附件accessory、富文本編輯htmleditor、人員機構選擇oguinput等控件會在許多地方都會用到,這些控件也都包含一部分html標籤和script腳本。 express
就如下拉多選列表爲例,其實這個控件包含了一個標籤(例如"請選擇人員:",不過我後來發現截圖沒截上),一個用於展現的select控件,一個用於存放實際選擇值的隱藏控件,以及一個JQuery的MultiSelect.js腳本,和調用MultiSelect.js的腳本。 設計模式
若是在各個使用到MultiSelect的cshtml頁面中寫上這麼一堆重複性的html標籤和對應的腳本總不是一個好的技術人員該作的事情,封裝成一個MultiSelect控件是一個比較好的選擇。 mvc
既然作控件的封裝,首先就是抽象,將一個個的控件抽象、提取造成控件基類。這個基類,咱們叫MvcControlBase。 框架
可是,只是這樣的話,若是在頁面中使用,例如MultiSelect就得寫成這樣:ide
1 @{ 2 MultiSelect control = new MultiSelect(…..); //建立實例 3 control.DataSource = …; //設置各類屬性,這裏須要設置數據源等屬性 4 Html.Raw(control.Render().ToString(); //呈現 5 }
這種寫法在cs程序中是沒有問題的,可是在cshtml中顯得有些土的掉渣了。html內的寫法儘可能簡潔,像以下這樣連綴的寫法:函數
@Html.MultiSelect(…).SetDataSource(…).SetShowItemsCount(5).Render() ui
這就涉及到另一個封裝基類,咱們叫控件構建器,MvcControlBuilderBase。 this
下面就具體介紹下這兩個基類。
基控件類MvcControlBase是全部MVC控件的抽象,那麼應該包含名稱Name,Id(經過Name自動生成的),控件的標籤名稱,值,屬性集合,標籤單元格數,控件單元格數,顯示狀態等,這些屬性應該是全部控件都會用到的,以及一個前臺呈現(Render)方法。
對於選擇日期控件 ,Name和Id都是theDate,標籤名稱是"日期:",值是"2016/10/17",屬性集合包含了cssclass、Style等,顯示狀態有普通、只讀、隱藏三個狀態。對於單元格數,咱們使用了BootStrap做爲css的框架集,若是設置了單元格數是3,那麼會自動生成col-md-3\col-xs-3\col-sm-3等cssclass。
由上引伸出基控件的構造函數定義以下:
protected MvcControlBase(HtmlHelper helper, string name, object value, string label, object attributes)
由於顯示狀態、單元格數等都有缺省值,咱們在構造函數中沒有對應的參數。
基控件類MvcControlBase最重要的方法是Render方法,就是在頁面中呈現控件。該方法,首先調用WriteHtml方法,生成Html標籤,而後再調用WriteScript方法,生成Script腳本,最終將標籤和腳本的內容生成MvcHtmlString返回。
1 public IHtmlString Render() 2 { 3 MvcHtmlString result = null; 4 5 using (HtmlTextWriter htmlWriter = new HtmlTextWriter(new StringWriter())) 6 { 7 WriteHtml(htmlWriter); 8 9 htmlWriter.WriteLine(); 10 11 using (HtmlTextWriter scriptWriter = new HtmlTextWriter(new StringWriter())) 12 { 13 WriteScript(scriptWriter); 14 15 string scriptString = scriptWriter.InnerWriter.ToString(); 16 17 if (!string.IsNullOrWhiteSpace(scriptString)) 18 { 19 htmlWriter.RenderBeginTag(HtmlTextWriterTag.Script); 20 htmlWriter.Write(scriptString); 21 htmlWriter.RenderEndTag(); 22 } 23 } 24 25 result = new MvcHtmlString(htmlWriter.InnerWriter.ToString()); 26 } 27 28 return result; 29 }
WriteHtml方法是個抽象方法,各個子控件必須予以重寫,生成控件的html元素; WriteScript方法,是個虛方法,若是子控件不須要腳本能夠不重寫。
protected virtual void WriteScript(HtmlTextWriter writer) { }
protected abstract void WriteHtml(HtmlTextWriter writer);
咱們繼續以多選下拉框控件MultiSelect爲例說明如何繼承基控件。MultiSelect控件比缺省控件會多兩個屬性,一個是數據源DataSource,就是下拉列表的全部數據,還有一個是ShowItemsCount,就是下拉列表顯示的行數。此外,Value的類型也應該是List<string>,表明哪一個數據被選擇,就是下拉列表的選中內容。
上圖的例子中,數據源是全部人List<UserInfo>,Value是Aaron和Adair,ShowItemsCount是9。
由於要控制select元素的展現內容和方式,所以MultiSelect前臺須要部分腳本,咱們選擇一個JQuery的開源的multiselect腳本。像圖中顯示的全選、查找等,是在multiselect.js腳本中實現的,咱們初始化時,將該腳本的是否顯示全選、查找的選項設置爲true,沒有經過咱們封裝的控件暴露出來。
具體作法是,重寫WriteScript方法,編寫調用腳本的$(function() { … })的腳本,調用的腳本比較簡單,就再也不詳細介紹了。而後重寫WriteHtml腳本,生成標籤和一個div。div內包含兩個控件,一個是顯示在頁面的select,一個是隱藏的hidden,用於存放選中值的。select控件的options就是從DataSource中獲取全部數據生成的,屬於Value的數據則設置選中狀態。
1 public class MultiSelect : MvcControlBase 2 { 3 /// <summary> 4 /// 初始化 5 /// </summary> 6 /// <param name="htmlHelper"></param> 7 /// <param name="expression"></param> 8 /// <param name="htmlAttributes"></param> 9 public MultiSelect(HtmlHelper htmlHelper, string name, List<string> value, string labelName, object htmlAttributes) 10 : base(htmlHelper, name, value, labelName, htmlAttributes) 11 { 12 this.DataSource = new Dictionary<string, string>(); 13 this.ShowItemsCount = 5; 14 } 15 16 #region 屬性 17 /// <summary> 18 /// 列表值 19 /// </summary> 20 internal Dictionary<string, string> DataSource { get; set; } 21 /// <summary> 22 /// 顯示項目的數量 23 /// </summary> 24 internal int ShowItemsCount { get; set; } 25 #endregion 26 27 /// <summary> 28 /// 輸出控件JS代碼 29 /// </summary> 30 /// <param name="writer"></param> 31 protected override void WriteScript(HtmlTextWriter writer) 32 { 33 string classes = string.Empty; 34 if (this.DisplayStatus == FieldDisplayStatus.ReadOnly) 35 { 36 classes = "ui-state-disabled"; 37 } 38 else if (this.DisplayStatus == FieldDisplayStatus.Hidden) 39 { 40 classes = " hidden"; 41 } 42 43 44 string script = string.Format(@" 45 $(function(){{ 46 $(""#{0}"").multiselect({{ 47 noneSelectedText: ""請選擇"", 48 checkAllText: ""全選"", 49 uncheckAllText: ""全不選"", 50 selectedList: {1}, 51 classes: ""{2}"" 52 }}); 53 }});", "select-" + this.Id, this.ShowItemsCount.ToString(), classes); 54 55 writer.Write(script); 56 57 } 58 59 /// <summary> 60 /// 輸出控件Html代碼 61 /// </summary> 62 /// <param name="writer">HtmlTextWriter</param> 63 protected override void WriteHtml(HtmlTextWriter writer) 64 { 65 List<string> value = this.Value as List<string>; 66 67 if (this.DisplayStatus == FieldDisplayStatus.Hidden || this.LabelCellNumber <= 0) 68 { 69 writer.Write(Helper.Label(LabelName, new RouteValueDictionary { { "class", string.Format("col-sm-{0} control-label hidden", LabelCellNumber) } }).ToHtmlString()); 70 } 71 else 72 { 73 writer.Write(Helper.Label(LabelName, new RouteValueDictionary { { "class", string.Format("col-sm-{0} control-label", LabelCellNumber) } }).ToHtmlString()); 74 } 75 76 TagBuilder divTag = new TagBuilder("div"); 77 divTag.AddCssClass(string.Format("col-sm-{0}", ControlCellNumber)); 78 79 List<SelectListItem> selectList = new List<SelectListItem>(); 80 foreach (KeyValuePair<string, string> kvp in this.DataSource) 81 { 82 SelectListItem item = new SelectListItem(); 83 item.Text = kvp.Value; 84 item.Value = kvp.Key; 85 if (value != null && value.Contains(kvp.Key)) 86 { 87 item.Selected = true; 88 } 89 selectList.Add(item); 90 } 91 92 //select 標籤的名字 93 IDictionary<string, object> HtmlAttributesForSelect = new RouteValueDictionary(); 94 HtmlAttributesForSelect.Add("id", "select-" + this.Id); 95 HtmlAttributesForSelect.Add("multiple", "multiple"); 96 97 divTag.InnerHtml = Helper.DropDownList(Name, selectList, HtmlAttributesForSelect).ToHtmlString(); 98 writer.Write(divTag.ToString()); 99 writer.Write("<input type=\"hidden\" id=\"" + this.Id + "\" />"); 100 } 101 }
按照上面的方法,MultiSelect控件基本就完成了。爲了實現@Html.MultiSelect(…).SetDataSource(…).SetShowItemsCount(5).Render() 這樣的寫法,咱們還得寫控件構建器。同樣的,有控件基類MvcControlBase,也得有控件基構建器類MvcControlBuilderBase。控件構造器主要做用是,傳入控件,根據控件的屬性和方法,生成一個個的連綴方法。
這個構建器類MvcControlBuilderBase我只寫了一部份內容,類定義的寫法比較繞一些,也算是一種泛型的設計模式吧,也確實是解決問題的一個程序寫法,方法呢也要返回構建器自身。
1 public abstract class MvcControlBuilderBase<TMvcControl, TBuilder> 2 where TMvcControl : MvcControlBase 3 where TBuilder : MvcControlBuilderBase<TMvcControl, TBuilder> 4 { 5 /// <summary> 6 /// 構造函數 7 /// </summary> 8 /// <param name="control">當前Control的實例</param> 9 protected MvcControlBuilderBase(TMvcControl control) 10 { 11 this.Control = control; 12 } 13 14 /// <summary> 15 /// 要生成的控件 16 /// </summary> 17 public TMvcControl Control { get; private set; } 18 19 /// <summary> 20 /// 設置Class名稱 21 /// </summary> 22 /// <param name="className"></param> 23 /// <returns></returns> 24 public virtual TBuilder CssClass(string className) 25 { 26 this.Control.Attributes.Merge(new { @class = className }); 27 return this as TBuilder; 28 } 29 30 /// <summary> 31 /// 設置style內容 32 /// </summary> 33 /// <param name="styleValue"></param> 34 /// <returns></returns> 35 public virtual TBuilder CssStyle(string styleValue) 36 { 37 this.Control.Attributes.Merge(new { style = styleValue }); 38 return this as TBuilder; 39 } 40 41 /// <summary> 42 /// 以匿名方式設置控件html屬性 43 /// </summary> 44 /// <param name="attributes">html屬性集合</param> 45 public virtual TBuilder HtmlAttributes(IDictionary<string, object> attributes) 46 { 47 Control.Attributes.Clear(); 48 Control.Attributes.Merge(attributes); 49 return this as TBuilder; 50 } 51 52 /// <summary> 53 /// 以Html方式輸出控件代碼 54 /// </summary> 55 public virtual IHtmlString Render() 56 { 57 return Control.Render(); 58 } 59 60 /// <summary> 61 /// 重寫tostring方法,返回html代碼 62 /// </summary> 63 /// <returns></returns> 64 public override string ToString() 65 { 66 return Render().ToString(); 67 } 68 }
咱們仍是以MultiSelect控件的構建器MultiSelectBuilder爲例子,寫法就比較簡單,主要實現SetDataSource和SetShowItemsCount就好了。
1 public class MultiSelectBuilder : MvcControlBuilderBase<MultiSelect, MultiSelectBuilder> 2 { 3 /// <summary> 4 /// 構造函數 5 /// </summary> 6 /// <param name="control">控件</param> 7 public MultiSelectBuilder(MultiSelect control) 8 : base(control) 9 { 10 } 11 12 /// <summary> 13 /// 設置List,根據傳入的Dictionary 14 /// </summary> 15 /// <param name="func"></param> 16 /// <returns></returns> 17 public MultiSelectBuilder SetDataSource(Dictionary<string, string> list) 18 { 19 Control.DataSource.Merge(list); 20 return this; 21 } 22 /// <summary> 23 /// 設置顯示項目的數量 24 /// </summary> 25 /// <param name="func"></param> 26 /// <returns></returns> 27 public MultiSelectBuilder SetShowItemsCount(int iCount) 28 { 29 Control.ShowItemsCount = iCount; 30 return this; 31 } 32 }
控件和控件的構建器完成後,如何在cshtml中像@Html.TextBox()同樣,只要寫成@Html.MultiSelect(…).SetDataSource(…).SetShowItemsCount(5).Render()就實如今cshtml頁面中呈現多選下拉控件呢?
這裏的Html是HtmlHelper,顧名思義HtmlHelper是html的幫助類,在這裏的主要功能是協助在cshtml頁面中呈現Html控件。咱們再來看一下cshtml頁面,每一個cshtml最終在運行時是一個類,這個類繼承了WebViewPage類,這個類有一個屬性就是public HtmlHelper Html { get; set; }。這裏的Html就是這個屬性。
咱們應該寫一個HtmlHelper的擴展方法,來實現Html.MultiSelect()的寫法。這個方法,返回值不是控件,而是控件的構建器,也就是MultiSelectBuilder。
1 public static MultiSelectBuilder MultiSelect(this HtmlHelper helper, string name, List<string> value, string labelName, object htmlAttributes = null) 2 { 3 return new MultiSelectBuilder(new MultiSelect(helper, name, value, labelName, htmlAttributes)); 4 }
到此爲止,一個基本的控件就完成,使用控件的cshtml頁面中也只須要寫上相似@Html.MultiSelect(…).SetDataSource(…).SetShowItemsCount(5).Render()的語句就搞定了。