• [转]9.2.3 .net core 通过TagHelper封装控件


    本文转自:https://www.cnblogs.com/BenDan2002/p/6170624.html

    .net core 除了继续保留.net framework的HtmlHelper的写法以外,还提供了TagHelper和ViewComponent方式生成控件。

    我们本节说的是使用TagHelper来生成控件。不过严格的说起来,TagHelper是对客户端html元素的辅助类,例如渲染、增加服务端特性等。我们可以使用 taghelper 定义自己的标签或更改已知标签。使用TagHelper,VS.net编译环境也可以自动感知,并提供智能提示。因为TagHelper生成的控件,看起来像一个原生的HTML标签一样,也更利于美工进行页面美化。

    例如一个lable控件 <label asp-for="Email"></label>生成的最终html就是这样:<label for="Email">xxx@xx.com</label>

    如果要使用TagHelper,除了在页面中using 命名空间之外,还需要使用@addTagHelper来使TagHelper可用。由于我们会编写不止一个TagHelper,且在多个cshtml页面使用,因此我们将如下代码

    @using MicroStrutLibrary.Presentation.Web.Controls

    @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

    @addTagHelper *, MicroStrutLibrary.Presentation.Web.Controls

    写在视图文件 Views/_ViewImports.cshtml中。写在Views/_ViewImports.cshtml的意思是,默认所有的在 Views 和Views下级目录中的视图文件都可以使用TagHelper,通配符 ("*") 来指定在特定程序集(Microsoft.AspNetCore.Mvc.TagHelpers和MicroStrutLibrary.Presentation.Web.Controls)中的所有的 TagHelpers 在 Views目录和子目录中的视图文件中都是可用的。第一个程序集是mvc自带的,第二个是我们自己的控件程序集。具体的说明大家还是看.net core文档吧。

    下面,我们仍旧以上一节介绍的多选控件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应该能够传入:1、数据源,也就是下拉列表的所有数据;2、已选项,已选择的数据;3、下拉列表的展示条数,以免条数过多,影响页面;4、是否只读等属性,更好的控制控件的展示内容。当然了,还应该包括一个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中生成,就不改了啊:)。

    这里主要有几个小技巧再提示下:

    1、cshtml页面中,Taghelper的Attribute可以传入各种复杂对象,而不是stringintool等简单类型

    2、TagHelper如果使用cshtml,则应该使用IHtmlHelper

    3、嵌入资源方式的cshtml,需要使用embeddedfileprovider。

    面向云的.net core开发框架

  • 相关阅读:
    正则表达式
    查看当前文件大小
    logging日志快速上手
    kafka消息队列的使用
    修改文件权限给指定的用户
    使用Dockerfile构建镜像
    k8s 常用命令总结
    k8s pod.yaml配置文件参数
    Linux安装依赖包
    Freeswitch配置SIP网关拨打外部
  • 原文地址:https://www.cnblogs.com/freeliver54/p/8350310.html
Copyright © 2020-2023  润新知