• 9.2.1 .net framework下的MVC 控件的封装(上)


    在写.net core下mvc控件的编写之前,我先说一下.net framework下我们MVC控件的做法。

    MVC下控件的写法,主要有如下三种,最后一种是泛型的写法,mvc提供的控件都是基本控件。

    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脚本。

    就以下拉多选列表为例,其实这个控件包含了一个标签(例如"请选择人员:",不过我后来发现截图没截上),一个用于展示的select控件,一个用于存放实际选择值的隐藏控件,以及一个JQuery的MultiSelect.js脚本,和调用MultiSelect.js的脚本。

    如果在各个使用到MultiSelect的cshtml页面中写上这么一堆重复性的html标签和对应的脚本总不是一个好的技术人员该做的事情,封装成一个MultiSelect控件是一个比较好的选择。

    既然做控件的封装,首先就是抽象,将一个个的控件抽象、提取形成控件基类。这个基类,我们叫MvcControlBase。

    但是,只是这样的话,如果在页面中使用,例如MultiSelect就得写成这样:

    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()

    这就涉及到另外一个封装基类,我们叫控件构建器,MvcControlBuilderBase。

    下面就具体介绍下这两个基类。

    基控件类MvcControlBase是所有MVC控件的抽象,那么应该包含名称Name,Id(通过Name自动生成的),控件的标签名称,值,属性集合,标签单元格数,控件单元格数,显示状态等,这些属性应该是所有控件都会用到的,以及一个前台呈现(Render)方法。

    对于选择日期控件 ,Name和Id都是theDate,标签名称是"日期:",值是"2016/10/17",属性集合包含了cssclass、Style等,显示状态有普通、只读、隐藏三个状态。对于单元格数,我们使用了BootStrap作为css的框架集,如果设置了单元格数是3,那么会自动生成col-md-3col-xs-3col-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()的语句就搞定了。

    面向云的.net core开发框架

  • 相关阅读:
    JQuery中$.ajax()方法参数详解
    overload和override的区别
    linux 安装jdk和tomcat
    linux链接外网手动设置
    RISC与CISCCPU构架
    32位与64位内存区别
    system 系统调用、gcc编译过程
    c helloworld
    C语言中 有符号数、无符号数、整数溢出 (转)
    samba安装
  • 原文地址:https://www.cnblogs.com/BenDan2002/p/6112711.html
Copyright © 2020-2023  润新知