.net core 除了繼續保留.net framework的HtmlHelper的寫法之外,還提供了TagHelper和ViewComponent方式生成控件。 javascript
咱們本節說的是使用TagHelper來生成控件。不過嚴格的提及來,TagHelper是對客戶端html元素的輔助類,例如渲染、增長服務端特性等。咱們可使用 taghelper 定義本身的標籤或更改已知標籤。使用TagHelper,VS.net編譯環境也能夠自動感知,並提供智能提示。由於TagHelper生成的控件,看起來像一個原生的HTML標籤同樣,也更利於美工進行頁面美化。 css
例如一個lable控件 <label asp-for="Email"></label>,生成的最終html就是這樣:<label for="Email">xxx@xx.com</label> html
若是要使用TagHelper,除了在頁面中using 命名空間以外,還須要使用@addTagHelper來使TagHelper可用。因爲咱們會編寫不止一個TagHelper,且在多個cshtml頁面使用,所以咱們將以下代碼 java
@using MicroStrutLibrary.Presentation.Web.Controlsjquery
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers json
@addTagHelper *, MicroStrutLibrary.Presentation.Web.Controls bootstrap
寫在視圖文件 Views/_ViewImports.cshtml中。寫在Views/_ViewImports.cshtml的意思是,默認全部的在 Views 和Views下級目錄中的視圖文件均可以使用TagHelper,通配符 ("*") 來指定在特定程序集(Microsoft.AspNetCore.Mvc.TagHelpers和MicroStrutLibrary.Presentation.Web.Controls)中的全部的 TagHelpers 在 Views目錄和子目錄中的視圖文件中都是可用的。第一個程序集是mvc自帶的,第二個是咱們本身的控件程序集。具體的說明你們仍是看.net core文檔吧。 mvc
下面,咱們仍舊以上一節介紹的多選控件MultiSelect爲例,說明下咱們的TagHelper封裝過程。 框架
MultiSelect在cshtml中的寫法以下,asp-dataSource傳入的是數據源,select的全部下拉內容都是經過datasource生成的,而asp-value傳入的是選中的項:異步
1 @model Dictionary<string, string> 2 @{ 3 List<string> values = ViewData.Get<List<string>>("Values"); 4 } 5 6 <multiSelect id="txtInput0" asp-dataSource="@Model" asp-value="@values"></multiSelect>
Model和values從controller中傳過來的,具體以下:
1 public IActionResult MultiSelect() 2 { 3 Dictionary<string, string> data = new Dictionary<string, string>(); 4 data.Add("1", "Aaaaa"); 5 data.Add("2", "Aaron"); 6 data.Add("3", "Abbott"); 7 data.Add("4", "Abel"); 8 data.Add("5", "Abner"); 9 data.Add("6", "Abraham"); 10 data.Add("7", "Adair"); 11 data.Add("8", "Adam"); 12 data.Add("9", "Addison"); 13 14 List<string> value = new List<string>(); 15 value.Add("2"); 16 value.Add("7"); 17 ViewData["Values"] = value; 18 19 return View(data); 20 }
這樣,最終生成的html代碼和頁面以下:
1 <div> 2 <select id="txtInput0" name="txtInput0" multiple="multiple"> 3 <option value="1" >Aaaaa</option> 4 <option value="2" selected>Aaron</option> 5 <option value="3" >Abbott</option> 6 <option value="4" >Abel</option> 7 <option value="5" >Abner</option> 8 <option value="6" >Abraham</option> 9 <option value="7" selected>Adair</option> 10 <option value="8" >Adam</option> 11 <option value="9" >Addison</option> 12 </select> 13 <script> 14 require(['jquery', 'bootstrap', 'multiselect'], function ($) { 15 $(function(){ 16 $("#txtInput0").multiselect({ 17 nonSelectedText: '請選擇', 18 enableFiltering: true,//是否顯示過濾 19 filterPlaceholder: '查找', 20 includeSelectAllOption: true,//是否顯示全選 21 selectAllText: '全選', 22 numberDisplayed: 5//顯示條數 23 }); 24 }); 25 }); 26 </script> 27 </div>
MultiSelect在頁面展現以下圖
按照設計,MultiSelect應該可以傳入:一、數據源,也就是下拉列表的全部數據;二、已選項,已選擇的數據;三、下拉列表的展現條數,以避免條數過多,影響頁面;四、是否只讀等屬性,更好的控制控件的展現內容。固然了,還應該包括一個For屬性,這個For屬性的意思是經過頁面的Model綁定,自動生成控件Id和已選擇數據項等內容。
因爲全部的TagHelper都繼承於一個基類TagHelper,咱們首先看下TagHelper抽象類:
1 // 2 // 摘要: 3 // Class used to filter matching HTML elements. 4 public abstract class TagHelper : ITagHelper 5 { 6 protected TagHelper(); 7 8 // 9 // 備註: 10 // Default order is 0. 11 public virtual int Order { get; } 12 13 // 14 public virtual void Init(TagHelperContext context); 15 // 16 // 摘要: 17 // Synchronously executes the Microsoft.AspNetCore.Razor.TagHelpers.TagHelper with 18 // the given context and output. 19 // 20 // 參數: 21 // context: 22 // Contains information associated with the current HTML tag. 23 // 24 // output: 25 // A stateful HTML element used to generate an HTML tag. 26 public virtual void Process(TagHelperContext context, TagHelperOutput output); 27 // 28 // 摘要: 29 // Asynchronously executes the Microsoft.AspNetCore.Razor.TagHelpers.TagHelper with 30 // the given context and output. 31 // 32 // 參數: 33 // context: 34 // Contains information associated with the current HTML tag. 35 // 36 // output: 37 // A stateful HTML element used to generate an HTML tag. 38 // 39 // 返回結果: 40 // A System.Threading.Tasks.Task that on completion updates the output. 41 // 42 // 備註: 43 // By default this calls into Microsoft.AspNetCore.Razor.TagHelpers.TagHelper.Process(Microsoft.AspNetCore.Razor.TagHelpers.TagHelperContext,Microsoft.AspNetCore.Razor.TagHelpers.TagHelperOutput). 44 public virtual Task ProcessAsync(TagHelperContext context, TagHelperOutput output); 45 }
這裏最重要的就是Process方法和ProcessAsync方法。這兩個方法,一個是同步,一個是異步,做用都是輸出頁面html代碼。
咱們再來看封裝後的MultiSelectTagHelper的代碼:
1 [HtmlTargetElement("multiSelect", Attributes = ForAttributeName)] 2 [HtmlTargetElement("multiSelect", Attributes = ValueAttributeName)] 3 [HtmlTargetElement("multiSelect", Attributes = ShowItemCountAttributeName)] 4 [HtmlTargetElement("multiSelect", Attributes = DataSourceAttributeName)] 5 [HtmlTargetElement("multiSelect", Attributes = ReadonlyAttributeName)] 6 public class MultiSelectTagHelper : TagHelper 7 { 8 private readonly IHtmlGenerator generator; 9 10 public MultiSelectTagHelper(IHtmlGenerator generator) 11 { 12 this.generator = generator; 13 } 14 15 private const string ForAttributeName = "asp-for"; 16 private const string ValueAttributeName = "asp-value"; 17 private const string ShowItemCountAttributeName = "asp-showItemCount"; 18 private const string DataSourceAttributeName = "asp-dataSource"; 19 private const string ReadonlyAttributeName = "asp-readonly"; 20 21 [HtmlAttributeNotBound] 22 [ViewContext] 23 public ViewContext ViewContext { get; set; } 24 25 [HtmlAttributeName(ForAttributeName)] 26 public ModelExpression For { get; set; } 27 28 [HtmlAttributeName(ValueAttributeName)] 29 public List<string> Value { get; set; } 30 31 [HtmlAttributeName(ShowItemCountAttributeName)] 32 public int ShowItemCount { get; set; } = 5; 33 34 [HtmlAttributeName(DataSourceAttributeName)] 35 public Dictionary<string, string> DataSource { get; set; } 36 37 [HtmlAttributeName(ReadonlyAttributeName)] 38 public bool Readonly { get; set; } 39 40 public override void Process(TagHelperContext context, TagHelperOutput output) 41 { 42 MicroStrutLibraryExceptionHelper.IsNull(context, this.GetType().FullName, LogLevel.Error, "context參數值爲空"); 43 MicroStrutLibraryExceptionHelper.IsNull(output, this.GetType().FullName, LogLevel.Error, "output"); 44 45 output.TagName = "div"; 46 //output.Attributes.Add("class", "multiselect-drop"); 47 48 MultiSelectList selectList = new MultiSelectList(this.DataSource, "Key", "Value", this.Value); 49 50 HtmlContentBuilder builder = new HtmlContentBuilder(); 51 52 string id; 53 if (For == null) 54 { 55 id = output.Attributes["id"].Value.ToString(); 56 output.Attributes.Remove(output.Attributes["id"]); 57 58 string options = string.Empty; 59 foreach (SelectListItem item in selectList) 60 { 61 options += $"<option value=\"{item.Value}\" {(item.Selected ? "selected" : "")}>{item.Text}</option>"; 62 } 63 64 builder.AppendHtml($"<select id=\"{id}\" name=\"{id}\" multiple=\"multiple\">{options}</select>"); 65 } 66 else 67 { 68 id = For.Name; 69 70 TagBuilder dropDown = generator.GenerateSelect(ViewContext, For.ModelExplorer, For.Name, string.Empty, selectList, true, null); 71 builder.AppendHtml(dropDown); 72 } 73 74 string readOnly = ""; 75 if (this.Readonly) 76 { 77 readOnly = $"$('#{id} +div > button').attr('disabled', true);"; 78 } 79 string script = string.Format(@" 80 <script> 81 require(['jquery', 'bootstrap', 'multiselect'], function ($) {{ 82 $(function(){{ 83 $(""#{0}"").multiselect({{ 84 nonSelectedText: '請選擇', 85 enableFiltering: true,//是否顯示過濾 86 filterPlaceholder: '查找', 87 includeSelectAllOption: true,//是否顯示全選 88 selectAllText: '全選', 89 numberDisplayed: {1}//顯示條數 90 }}); 91 {2} 92 }}); 93 }}); 94 </script> 95 ", id, this.ShowItemCount, readOnly); 96 97 builder.AppendHtml(script); 98 99 output.Content.AppendHtml(builder); 100 101 base.Process(context, output); 102 } 103 }
爲了在編寫程序時不會出錯,咱們定義了五個常量ForAttributeName 、ValueAttributeName、ShowItemCountAttributeName、DataSourceAttributeName、ReadonlyAttributeName,分別表明MultiSelect標籤的asp-for等Attribute。也就是MultiSelect傳入的數據源、已選項、下拉列表的展現條數、是否只讀等屬性、For屬性。
類定義上面[HtmlTargetElement("multiSelect", Attributes = ValueAttributeName)]的做用是在multiSelect標籤上生成ValueAttributeName對應值asp-value的Attribute。
屬性上面
[HtmlAttributeName(ValueAttributeName)]
public List<string> Value { get; set; }
的做用是告訴multiSelect標籤,asp-value傳入的應該是個List<string>類型。這裏須要注意的是,在各類文檔、教程中,大多數狀況下,Attribute傳入的類型都是一些簡單類型,例如字符串、數字等。其實Attribute是能夠傳入複雜的類型的,例如咱們這裏傳入的List<string>。頁面中也就要以下的使用方式:
1 @model Dictionary<string, string> 2 @{ 3 List<string> values = ViewData.Get<List<string>>("Values"); 4 } 5 6 <multiSelect id="txtInput0" asp-dataSource="@Model" asp-value="@values"></multiSelect>
還有一個屬性ViewContext,這個屬性不是顯式傳入的,而是TagHelper建立時,系統自動賦值的,含義是當前的視圖執行上下文Microsoft.AspNetCore.Mvc.Rendering.ViewContext。
[HtmlAttributeNotBound]
[ViewContext]
public ViewContext ViewContext { get; set; }
這裏還有一個須要重要說明的是For方式public ModelExpression For { get; set; }。普通方式下,multiSelect的Id、Value都是從頁面傳入的,可是For方式是與Model綁定的,如同HtmlHelper的TextBox、TextBoxFor的區別同樣。例如,一我的可能有多個職責,人的職責屬性是DutyList,全部職責的數據DutyDictionary,在cshtml頁面中就按照以下方式寫multiselect:
@model UserInfo
<multiSelect asp-dataSource="@DutyDictionary" asp-for="DutyList"></multiSelect>
MultiSelect構造函數中,傳入了一個IHtmlGenerator參數,這個是經過DI容器自動解析出來的,缺省狀況下的實現類是DefaultHtmlGenerator。這個類的主要做用是生成各類html標籤。咱們在For方式用到了自動生成select標籤的方法。TagBuilder dropDown = generator.GenerateSelect(ViewContext, For.ModelExplorer, string.Empty, For.Name, selectList, true, null);
最後就是重寫Process方法。其實這個方法仍是比較簡單的,當For方式時,經過For自動生成select;不然就本身寫select。咱們其中還寫了一段腳本,腳本中直接引用了jquery的MultiSelect腳本,
require(['jquery', 'bootstrap', 'multiselect'], function ($) {{…
這裏咱們使用systemjs這個通用的javascript模塊加載器,其中的jquery、bootstrap、multiselect都是在system.config中定義的。System.config.js的代碼大致以下,至於爲何這麼寫,你們仍是搜網上幫助吧:
1 System.config({ 2 bundles: { 3 }, 4 paths: { 5 "external:": externalUrl+"/" 6 }, 7 map: { 8 "jquery": "external:lib/jquery/jquery.min.js", 9 "bootstrap": "external:lib/bootstrap/js/bootstrap.min.js", 10 11 //-- 12 "jquery-ui": "external:lib/jquery/jquery-ui/jquery-ui.bundle.min.js", 13 14 //--Plugins 15 "multiselect": "external:lib/plugins/multiselect/js/bootstrap-multiselect.min.js" 16 … 17 }, 18 meta: { 19 '*.css': { 20 loader: 'external:lib/system/css-loader/css.js' 21 }, 22 'jquery': { 23 format: 'global', 24 exports: 'jQuery' 25 }, 26 'bootstrap': { 27 format: 'global', 28 deps: ['jquery'] 29 }, 30 'jquery-ui': { 31 format: 'global', 32 deps: ['jquery','./jquery-ui.min.css'] 33 }, 34 'multiselect': { 35 format: 'global', 36 deps: ['../css/bootstrap-multiselect.min.css'] 37 }, 38 … 39 }, 40 packages: { 41 '/js': { 42 format: 'cjs', 43 defaultExtension: 'js' 44 }, 45 46 //externals 47 'external:js': { 48 format: 'cjs', 49 defaultExtension: 'js' 50 } 51 } 52 }); 53 54 //amd require 55 window.require = System.amdRequire;
至此,一個基本的Taghelpr就完成了。
在ASP.NET Core MVC中應該使用 TagHelpers 來替換 HtmlHelpers,由於它們更加的簡潔和容易使用。另外一個巨大的好處就是依賴注入,在HtmlHelpers中是使用不了的,由於HtmlHelpers 擴展的都是靜態內容。 但TagHelpers是一個公共類,咱們能夠很容易的在它的構造函數中注入服務。
進階:資源性視圖的應用
按照上節的慣例,咱們依舊還一個進階,說明下在TagHelper中如何使用cshtml,以及cshtml做爲嵌入的資源該如何寫。
咱們從上面MultiSelectTagHelper類中將Process方法的頁面代碼拼接程序提取出來,寫成cshtml以下
1 @{ 2 string id = ViewData["Id"].ToString(); 3 int showItemCount = (int)ViewData["ShowItemCount"]; 4 bool isReadonly = (bool)ViewData["Readonly"]; 5 } 6 7 <script> 8 require(['jquery', 'bootstrap', 'multiselect'], function ($) { 9 $(function(){ 10 $("#@id").multiselect({ 11 nonSelectedText: '請選擇', 12 enableFiltering: true,//是否顯示過濾 13 filterPlaceholder: '查找', 14 includeSelectAllOption: true,//是否顯示全選 15 numberDisplayed: @(showItemCount),//顯示條數 16 selectAllText: '全選' 17 }); 18 @if (isReadonly) 19 { 20 @:$("#@id +div > button").attr("disabled", true); 21 } 22 }); 23 }); 24 </script>
這樣MultiSelectTagHelper類中就簡化成以下:
1 [HtmlTargetElement("multiSelect", Attributes = ForAttributeName)] 2 [HtmlTargetElement("multiSelect", Attributes = ValueAttributeName)] 3 [HtmlTargetElement("multiSelect", Attributes = ShowItemCountAttributeName)] 4 [HtmlTargetElement("multiSelect", Attributes = DataSourceAttributeName)] 5 [HtmlTargetElement("multiSelect", Attributes = ReadonlyAttributeName)] 6 public class MultiSelectTagHelper : TagHelper 7 { 8 private readonly IHtmlGenerator generator; 9 private readonly IUrlHelperFactory factory; 10 private readonly IHtmlHelper htmlHelper; 11 12 public MultiSelectTagHelper(IHtmlHelper htmlHelper, IHtmlGenerator generator) 13 { 14 this.generator = generator; 15 this.factory = factory; 16 this.htmlHelper = htmlHelper; 17 } 18 19 private const string ForAttributeName = "asp-for"; 20 private const string ValueAttributeName = "asp-value"; 21 private const string ShowItemCountAttributeName = "asp-showItemCount"; 22 private const string DataSourceAttributeName = "asp-dataSource"; 23 private const string ReadonlyAttributeName = "asp-readonly"; 24 25 [HtmlAttributeNotBound] 26 [ViewContext] 27 public ViewContext ViewContext { get; set; } 28 29 [HtmlAttributeName(ForAttributeName)] 30 public ModelExpression For { get; set; } 31 32 [HtmlAttributeName(ValueAttributeName)] 33 public List<string> Value { get; set; } 34 35 [HtmlAttributeName(ShowItemCountAttributeName)] 36 public int ShowItemCount { get; set; } = 5; 37 38 [HtmlAttributeName(DataSourceAttributeName)] 39 public Dictionary<string, string> DataSource { get; set; } 40 41 [HtmlAttributeName(ReadonlyAttributeName)] 42 public bool Readonly { get; set; } 43 44 public override void Process(TagHelperContext context, TagHelperOutput output) 45 { 46 MicroStrutLibraryExceptionHelper.IsNull(context, this.GetType().FullName, LogLevel.Error, "context參數值爲空"); 47 MicroStrutLibraryExceptionHelper.IsNull(output, this.GetType().FullName, LogLevel.Error, "output"); 48 49 output.TagName = "div"; 50 //output.Attributes.Add("class", "multiselect-drop"); 51 52 MultiSelectList selectList = new MultiSelectList(this.DataSource, "Key", "Value", this.Value); 53 54 HtmlContentBuilder builder = new HtmlContentBuilder(); 55 56 string id; 57 if (For == null) 58 { 59 id = output.Attributes["id"].Value.ToString(); 60 output.Attributes.Remove(output.Attributes["id"]); 61 62 string options = string.Empty; 63 foreach (SelectListItem item in selectList) 64 { 65 options += $"<option value=\"{item.Value}\" {(item.Selected ? "selected" : "")}>{item.Text}</option>"; 66 } 67 68 builder.AppendHtml($"<select id=\"{id}\" name=\"{id}\" multiple=\"multiple\">{options}</select>"); 69 } 70 else 71 { 72 id = For.Name; 73 74 TagBuilder dropDown = generator.GenerateSelect(ViewContext, For.ModelExplorer, null, For.Name, selectList, true, null); 75 builder.AppendHtml(dropDown); 76 } 77 78 output.Content.AppendHtml(builder); 79 80 //Contextualize the html helper 81 (htmlHelper as IViewContextAware).Contextualize(ViewContext); 82 83 ViewDataDictionary data = new ViewDataDictionary(this.ViewContext.ViewData); 84 data["Id"] = id; 85 data["ShowItemCount"] = this.ShowItemCount; 86 data["Readonly"] = this.Readonly; 87 88 var content = htmlHelper.Partial("TagHelpers/MultiSelect/MultiSelect", data); 89 output.Content.AppendHtml(content); 90 91 base.Process(context, output); 92 } 93 } 94 }
你們可能注意到構造函數中咱們增長了個參數IHtmlHelper htmlHelper。這個參數是以前MVC的HtmlHelper,咱們經過DI方式直接獲取到htmlhelper。然而,此時DI獲取的htmlhelper還沒法使用,必須經過(htmlHelper as IViewContextAware).Contextualize(ViewContext);將上下文信息傳入HtmlHelper。var content = htmlHelper.Partial("TagHelpers/MultiSelect/MultiSelect", data); output.Content.AppendHtml(content);這兩句話執行cshtml頁面,將最終頁面的內容呈如今TagHelper中。
這裏還有一個問題,就是咱們將全部的控件都存放到一個應用程序集中,控件的cshtml頁面也會以資源方式打包進應用程序集中。咱們控件的項目結構以下:
MultiSelect的內容以下,有2個文件,一個cshtml,一個是taghelper程序。其餘目錄的結構也是相似的。
新的.net core的嵌入資源方式須要在project.json中按照以下方式編寫:
"buildOptions": {
"embed": [ "Components/**/*.cshtml", "TagHelpers/**/*.cshtml" ]
}
這裏的意思是咱們將全部components和taghelpers目錄下的第二級子目錄下的全部cshtml文件以嵌入方式打包進應用程序集中。在.net core中使用應用程序集中嵌入的文件,還算是比較方便。由於.net core已經把許多可擴展的內容開放出來了。
咱們這裏寫了一個擴展方法,在RazorViewEngineOptions(RazorViewEngine程序方式的配置)中增長一個Razor視圖文件的定位器EmbeddedFileProvider。EmbeddedFileProvider就能夠獲取應用程序集中嵌入的cshtml文件,構造函數第一個參數是包含嵌入cshtml文件的應用程序集,第二個參數是命名空間。
1 public static class EmbeddedViewServiceCollectionExtensions 2 { 3 public static IServiceCollection AddEmbeddComponentView(this IServiceCollection services) 4 { 5 if (services == null) 6 { 7 throw new ArgumentNullException(nameof(services)); 8 } 9 10 EmbeddedFileProvider fileProvider = new EmbeddedFileProvider(typeof(EmbeddedViewServiceCollectionExtensions).GetTypeInfo().Assembly, "MicroStrutLibrary.Presentation.Web.Controls"); 11 12 services.Configure<RazorViewEngineOptions>(options => { 13 options.FileProviders.Add(fileProvider); 14 }); 15 16 return services; 17 } 18 }
接下來就是在Startup.cs中使用這個擴展方法:
1 public void ConfigureServices(IServiceCollection services) 2 { 3 services.AddMvc(options => 4 { 5 … 6 }); 7 8 services.AddEmbeddComponentView(); 9 }
如今才發現,其實生成select標籤部分也是應該放到csthml中的,而不是在taghelper中生成,就不改了啊:)。
這裏主要有幾個小技巧再提示下:
一、cshtml頁面中,Taghelper的Attribute能夠傳入各類複雜對象,而不是string\int\bool等簡單類型
二、TagHelper若是使用cshtml,則應該使用IHtmlHelper
三、嵌入資源方式的cshtml,須要使用embeddedfileprovider。