好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

ASP.NET MVC的Model元数据与Model模板:将”ListControl”引入ASP.N

ASP.NET MVC的Model元数据与Model模板:将”ListControl”引入ASP.NET MVC

我们不仅可以创建相应的模板来根据Model元数据控制种类型的数据在UI界面上的呈现方法,还可以通过一些扩展来控制Model元数据本身。在某些情况下通过这两者的结合往往可以解决很多特殊数据的呈现问题,我们接下来演示的实例就是典型的例子。[本文已经同步到《 How ASP.NET MVC Works? 》中]

传统的ASP.NET具有一组重要的控件类型叫做列表控件(ListControl),它的子类包括DropDownList、ListBox、RadioButtonList和CheckBoxList等。对于ASP.NET MVC来说,我们可以通过HtmlHelper/HtmlHelper<TModel>的扩展方法DropDownList/DropDownListFor和ListBox/ListBox在界面上呈现一个下拉框和列表框,但是我们需要手工指定包含的所有列表选项。在一般的Web应用中,尤其是企业应用中,我们会选择将这些列表进行单独地维护,如果我们在构建“列表控件”的时候能够免去手工提供列表的工作,这无疑会为开发带来极大的遍历,而这实际上很容易实现。[源代码从 这里 下载]

一、实现的效果

我们先来看看通过该扩展最终实现的效果。在通过Visual Studio的ASP.NET MVC项目模板创建的空Web应用中,我们定义一个作为Model表示员工的Employee类型。如下面的代码片断所示,表示性别、学历、部门和技能的属性分别应用了 RadioButtonListAttribute 、 DropdownListAttribute 、 ListBoxAttribute 和 CheckBoxListAttribubte 四个特性。从名称可以看出来,这四个特性分别代表了目标元素呈现在UI界面上的形式,即对应着传统ASP.NET Web应用中的四种类型的列表控件:RadioButtonList、DropdownList、ListBox和CheckBoxList。特性中指定的字符串表示预定义列表的名称。

    1:   public   class  Employee
    2:  {
    3:      [DisplayName( "姓名" )]
    4:       public   string  Name { get; set; }
    5:   
    6:      [RadioButtonList( "Gender" )]
    7:      [DisplayName( "性别" )]
    8:       public   string  Gender { get; set; }
    9:   
   10:      [DropdownList( "Education" )]
   11:      [DisplayName( "学历" )]
   12:       public   string  Education { get; set; }
   13:   
   14:      [ListBox( "Department" )]
   15:      [DisplayName( "所在部门" )]
   16:       public  IEnumerable< string > Departments { get; set; }
   17:   
   18:      [CheckBoxList( "Skill" )]
   19:      [DisplayName( "擅长技能" )]
   20:       public  IEnumerable< string > Skills { get; set; }
   21:  }

在创建的默认HomeController中,我们定义了如下一个Index操作方法。在该方法中,我们创建了一个具体的Employee对象并对它的所有属性进行了相应设置,最终将该对象呈现在默认的View中。

    1:   public   class  HomeController : Controller
    2:  {
    3:       public  ActionResult Index()
    4:      {
    5:          Employee employee =  new  Employee
    6:          {
    7:              Name       =  "张三" ,
    8:              Gender     =  "M" ,
    9:              Education  =  "M" ,
   10:              Departments=  new   string [] {  "HR" ,  "AD"  },
   11:              Skills     =  new   string [] {  "CSharp" ,  "AdoNet"  }
   12:          };
   13:           return  View(employee);
   14:      }
   15:  }

如下所示的是上面的Index操作对应的View定义,这是一个以Model类型为Employee的强类型View,我们通过调用HtmlHelper<TModel>的模板方法EditorFor将作为Model的Employee对象的所有属性以编辑模式呈现出来。

    1:  @model Employee
    2:   <  table  > 
    3:       <  tr  > 
    4:           <  td  > @Html.LabelFor(m = >  m.Name) </  td  ><  td  > @Html.EditorFor(m = >  m.Name) </  td  > 
    5:       </  tr  > 
    6:         <  tr  > 
    7:           <  td  > @Html.LabelFor(m = >  m.Gender) </  td  ><  td  > @Html.EditorFor(m = >  m.Gender) </  td  > 
    8:       </  tr  > 
    9:         <  tr  > 
   10:           <  td  > @Html.LabelFor(m = >  m.Education) </  td  ><  td  > @Html.EditorFor(m = >  m.Education) </  td  > 
   11:       </  tr  > 
   12:        <  tr  > 
   13:           <  td  > @Html.LabelFor(m = >  m.Departments) </  td  ><  td  > @Html.EditorFor(m = >  m.Departments) </  td  > 
   14:       </  tr  > 
   15:        <  tr  > 
   16:           <  td  > @Html.LabelFor(m = >  m.Skills) </  td  ><  td  > @Html.EditorFor(m = >  m.Skills) </  td  > 
   17:       </  tr  >     
   18:   </  table  > 

下图体现了该Web应用运行时的效果。我们可以看到,四个属性分别以四种不同的“列表控件”呈现出来,并且对应在它们上面的四个字定义的列表特性(RadioButtonListAttribute、DropdownListAttribute、ListBoxAttribute和CheckBoxListAttribubte)。

二、ListItem与ListProvider

现在对体现在上面演示实例的基于列表数据的UI定制的设计进行简单地介绍。我们首先来定义如下一个表示列表中某个条目(列表项)的类型ListItem,简单起见,我们紧紧定义Text和Value两个属性,它们分别表示显示的文字和代表的值。比如对于一组表示国家的列表,列表项的Text属性表示成国家名称(比如“中国”),具体的值则可能是国家的代码(比如“CN”)。

    1:   public   class  ListItem
    2:  {
    3:       public   string  Text { get; set; }
    4:       public   string  Value { get; set; }
    5:  }

我们将提供列表数据的组件称为ListProvider,它们实现了IListProvider接口。如下面的代码片断所示,IListProvider具有唯一的方法GetListItems根据指定的列表名称获取所有的列表项。通过实现IListProvider,我们定义了一个默认的DefaultListProvider。简单起见,DefaultListProvider直接通过一个静态字段模拟列表的存储,在真正的项目中一般会保存在数据库中。DefaultListProvider维护了四组列表,分别表示性别、学历、部门和技能,它们正好对应着Employee的四个属性。

    1:   public   interface  IListProvider
    2:  {
    3:      IEnumerable<ListItem> GetListItems( string  listName);
    4:  }
    5:   public   class  DefaultListProvider : IListProvider
    6:  {
    7:       private   static  Dictionary< string , IEnumerable<ListItem>> listItems =  new  Dictionary< string , IEnumerable<ListItem>>();
    8:       static  DefaultListProvider()
    9:      {
   10:          var items =  new  ListItem[]{
   11:           new  ListItem{ Text =  "男" , Value= "M" },
   12:           new  ListItem{ Text =  "女" , Value= "F" }};
   13:          listItems.Add( "Gender" , items);
   14:   
   15:          items =  new  ListItem[]{            
   16:           new  ListItem{ Text =  "高中" , Value= "H" }  , 
   17:           new  ListItem{ Text =  "大学本科" , Value= "B" },
   18:           new  ListItem{ Text =  "硕士" , Value= "M" } ,                
   19:           new  ListItem{ Text =  "博士" , Value= "D" }};
   20:          listItems.Add( "Education" , items);
   21:   
   22:          items =  new  ListItem[]{            
   23:           new  ListItem{ Text =  "人事部" , Value= "HR" }  , 
   24:           new  ListItem{ Text =  "行政部" , Value= "AD" },
   25:           new  ListItem{ Text =  "IT部" , Value= "IT" }};
   26:          listItems.Add( "Department" , items);
   27:   
   28:          items =  new  ListItem[]{            
   29:           new  ListItem{ Text =  "C#" , Value= "CSharp" }  , 
   30:           new  ListItem{ Text =  "ASP.NET" , Value= "AspNet" },
   31:           new  ListItem{ Text =  "ADO.NET" , Value= "AdoNet" }};
   32:          listItems.Add( "Skill" , items);
   33:      }
   34:       public  IEnumerable<ListItem> GetListItems( string  listName)
   35:      {
   36:          IEnumerable<ListItem> items;
   37:           if  (listItems.TryGetValue(listName,  out  items))
   38:          {
   39:               return  items;
   40:          }
   41:           return   new  ListItem[0];
   42:      }
   43:  }

接下来我们定义如下一个ListProviders类型,它的静态只读属性Current表示当前的ListProvider,而对当前ListProvider的注册通过静态方法SetListProvider来实现。如果没有对当前ListProvider进行显式注册,则默认采用DefaultListProvider。

    1:   public   static   class  ListProviders
    2:  {
    3:       public   static  IListProvider Current { get;  private  set; }
    4:       static  ListProviders()
    5:      {
    6:          Current =  new  DefaultListProvider();
    7:      }
    8:       public   static   void  SetListProvider(Func<IListProvider> providerAccessor)
    9:      {
   10:          Current = providerAccessor();
   11:      }
   12:  }

三、通过对HtmlHelper/HtmlHelper<TModel>的扩展生成“ListControl”的HTML

基于四种“列表控件”的HTML生成是通过定义HtmlHelper的扩展方法来实现的,如下面的代码所示,定义在ListControlExtensions中的四个扩展方法实现了针对这四种列表控件的UI呈现。参数listName表示使用的预定义列表的名称,而value和values则表示绑定的值。RadioButtonList/DropdownList只允许单项选择,而ListBox/CheckBoxList允许多项选择,所以对应的值类型分别是string和IEnumerable<string>。

    1:   public   static   class  ListControlExtensions
    2:  {
    3:       //其他成员 
    4:      public   static  MvcHtmlString RadioButtonList(  this  HtmlHelper htmlHelper, string  name,  string  listName,  string   value )
    5:      {
    6:           return  RadioButtonCheckBoxList(htmlHelper, listName, item => 
    7:             htmlHelper.RadioButton(name, item.Value,  value  == item.Value));
    8:      }        
    9:   
   10:       public   static  MvcHtmlString CheckBoxList( this  HtmlHelper htmlHelper,  string  name,  string  listName, IEnumerable< string > values)
   11:      {
   12:           return  RadioButtonCheckBoxList(htmlHelper, listName, item => CheckBoxWithValue(htmlHelper, name,  values.Contains(item.Value), item.Value));
   13:      }
   14:   
   15:       public   static  MvcHtmlString ListBox( this  HtmlHelper htmlHelper,  string  name,  string  listName, IEnumerable< string > values)
   16:      {
   17:          var listItems = ListProviders.Current.GetListItems(listName);
   18:          List<SelectListItem> selectListItems =  new  List<SelectListItem>();
   19:           foreach  (var item  in  listItems)
   20:          {
   21:              selectListItems.Add( new  SelectListItem { Value = item.Value, 
   22:                  Text = item.Text, 
   23:                  Selected = values.Any( value  =>  value  == item.Value) });
   24:          }
   25:           return  htmlHelper.ListBox(name, selectListItems);
   26:      }
   27:   
   28:       public   static  MvcHtmlString DropDownList( this  HtmlHelper htmlHelper,  string  name,  string  listName,  string   value )
   29:      {
   30:          var listItems = ListProviders.Current.GetListItems(listName);
   31:          List<SelectListItem> selectListItems =  new  List<SelectListItem>();
   32:           foreach  (var item  in  listItems)
   33:          {
   34:              selectListItems.Add( new  SelectListItem { Value = item.Value, 
   35:                  Text = item.Text, Selected =  value  == item.Value});
   36:          }
   37:           return  htmlHelper.DropDownList(name, selectListItems);
   38:      }
   39:  }

从上面的代码片断可以看到,在ListBox和DropDownList方法中我们通过当前的ListProvider获取指定列表名称的所有列表项并生成相应的SelectListItem列表,最终通过调用HtmlHelper现有的扩展方法ListBox和DropDownList实现HTML的呈现。而RadioButtonList和MvcHtmlString最终调用了辅助方法RadioButtonCheckBoxList显示了最终的HTML生成,该方法定义如下。

    1:   public   static   class  ListControlExtensions
    2:  {
    3:       public   static  MvcHtmlString CheckBoxWithValue( this  HtmlHelper htmlHelper,  string  name,  bool  isChecked,  string   value )
    4:      {
    5:           string  fullHtmlFieldName = htmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(name);
    6:          ModelState modelState;
    7:   
    8:           //将ModelState设置为表示是否勾选布尔值 
    9:           if  (htmlHelper.ViewData.ModelState.TryGetValue(fullHtmlFieldName,  out  modelState))
   10:          {
   11:              htmlHelper.ViewData.ModelState.SetModelValue(fullHtmlFieldName,  new  ValueProviderResult(isChecked, isChecked.ToString(), CultureInfo.CurrentCulture));
   12:          }
   13:          MvcHtmlString html;
   14:           try 
   15:          {
   16:              html = htmlHelper.CheckBox(name, isChecked);
   17:          }
   18:           finally 
   19:          {
   20:               //将ModelState还原 
   21:               if  ( null  != modelState)
   22:              {
   23:                  htmlHelper.ViewData.ModelState[fullHtmlFieldName] = modelState;
   24:              }
   25:          }
   26:           string  htmlString = html.ToHtmlString();
   27:          var index = htmlString.LastIndexOf( '<' );
   28:           //过滤掉类型为"hidden"的<input>元素 
   29:          XElement element = XElement.Parse(htmlString.Substring(0, index));
   30:          element.SetAttributeValue( "value" ,  value );
   31:           return   new  MvcHtmlString(element.ToString());
   32:      }
   33:   
   34:       private   static  MvcHtmlString RadioButtonCheckBoxList(HtmlHelper htmlHelper,  string  listName, Func<ListItem, MvcHtmlString> elementHtmlAccessor)
   35:      {
   36:          var listItems = ListProviders.Current.GetListItems(listName);
   37:          TagBuilder table =  new  TagBuilder( "table" );
   38:          TagBuilder tr =  new  TagBuilder( "tr" );
   39:           foreach  (var listItem  in  listItems)
   40:          {
   41:              TagBuilder td =  new  TagBuilder( "td" );
   42:              td.InnerHtml += elementHtmlAccessor(listItem).ToHtmlString();
   43:              td.InnerHtml += listItem.Text;
   44:              tr.InnerHtml += td.ToString();
   45:          }
   46:          table.InnerHtml = tr.ToString();
   47:           return   new  MvcHtmlString(table.ToString());
   48:      }
   49:  }

方法RadioButtonCheckBoxList在生成RadioButtonList和CheckBoxList的时候才用<table>进行布局。组成RadioButtonList的单个RadioButton最终是调用HtmlHelper现有的扩展方法RadioButton生成的,而CheckBoxList中的CheckBox则是通过调用我们自定义的CheckBoxWithValue方法生成的。CheckBoxWithValue最终还是调用HtmlHelper现有的扩展方法CheckBox生成单个CheckBox对应的HTML,但是方法值支持布尔值的绑定,并且会生成一个在这里不需要的Hidden元素,所以我们不得不在调用该方法的前后作一些手脚。

四、ListAttribute

现在我们来介绍应用在Employee属性上的四个特性的定义。如下面的代码片断所示,基于四种“列表控件”的特性均继承自抽象特性ListAttribute。ListAttribute实现了 IMetadataAware 接口,在实现的OnMetadataCreated方法中将在构造函数中指定的代表列表名称的ListName属性添加到表示Model元数据的ModelMetadata对象的AdditionalValues属性中。四个具体的列表特性重写了OnMetadataCreated方法,并在此基础上将ModelMetadata的TemplateHint分别设置为DropdownList、ListBox、RadioButtonList和CheckBoxList。

    1:  [AttributeUsage(AttributeTargets.Property)]
    2:   public   abstract   class  ListAttribute : Attribute, IMetadataAware
    3:  {
    4:       public   string  ListName { get;  private  set; }        
    5:       public  ListAttribute( string  listName)
    6:      {
    7:           this .ListName = listName;
    8:      }
    9:       public   virtual   void  OnMetadataCreated(ModelMetadata metadata)
   10:      {
   11:          metadata.AdditionalValues.Add( "ListName" ,  this .ListName);
   12:      }
   13:  }
   14:   
   15:  [AttributeUsage(AttributeTargets.Property)]
   16:   public   class  DropdownListAttribute : ListAttribute
   17:  {
   18:       public  DropdownListAttribute( string  listName)
   19:          :  base (listName)
   20:      { }
   21:       public   override   void  OnMetadataCreated(ModelMetadata metadata)
   22:      {
   23:           base .OnMetadataCreated(metadata);
   24:          metadata.TemplateHint =  "DropdownList" ;
   25:      }
   26:  }
   27:   
   28:  [AttributeUsage(AttributeTargets.Property)]
   29:   public   class  ListBoxAttribute : ListAttribute
   30:  {
   31:       public  ListBoxAttribute( string  listName)
   32:          :  base (listName)
   33:      { }
   34:       public   override   void  OnMetadataCreated(ModelMetadata metadata)
   35:      {
   36:           base .OnMetadataCreated(metadata);
   37:          metadata.TemplateHint =  "ListBox" ;
   38:      }
   39:  }
   40:   
   41:  [AttributeUsage(AttributeTargets.Property)]
   42:   public   class  RadioButtonListAttribute : ListAttribute
   43:  {
   44:       public  RadioButtonListAttribute( string  listName)
   45:          :  base (listName)
   46:      { }
   47:   
   48:       public   override   void  OnMetadataCreated(ModelMetadata metadata)
   49:      {
   50:           base .OnMetadataCreated(metadata);
   51:          metadata.TemplateHint =  "RadioButtonList" ;
   52:      }
   53:  }
   54:   
   55:  [AttributeUsage(AttributeTargets.Property)]
   56:   public   class  CheckBoxListAttribute : ListAttribute
   57:  {
   58:       public  CheckBoxListAttribute( string  listName)
   59:          :  base (listName)
   60:      { }
   61:   
   62:       public   override   void  OnMetadataCreated(ModelMetadata metadata)
   63:      {
   64:           base .OnMetadataCreated(metadata);
   65:          metadata.TemplateHint =  "CheckBoxList" ;
   66:      }
   67:  }

五、模板View的定义

由于四个具体的ListAttribute已经对表示模板名称的ModelMetadata的TemplateHint进行了设置,那么我们针对它们定义相应的分部View作为对应的模板,那么在调用HtmlHelper/HtmlHelper<TModel>相应模板方法的时候就会按照这些模板对目标元素进行呈现。实现如上图所示的效果的四个模板定义如下,它们被保存在View\Shared\EditorTemplates目录下面。

    1:  DropdownList.cshtml:
    2:  @model string
    3:  @{    
    4:      string listName = (string)ViewData.ModelMetadata.AdditionalValues["ListName"];
    5:      @Html.DropDownList("",listName,Model)
    6:  }
    7:   
    8:  ListBox.cshtml:
    9:  @model IEnumerable <  string  > 
   10:  @{    
   11:      string listName = (string)ViewData.ModelMetadata.AdditionalValues["ListName"];
   12:      @Html.ListBox("",listName,Model)   
   13:  }
   14:   
   15:  RadioButtonList.cshtml:
   16:  @model string
   17:  @{    
   18:      string listName = (string)ViewData.ModelMetadata.AdditionalValues["ListName"];
   19:      @Html.RadioButtonList("",listName,Model)
   20:  }
   21:   
   22:  CheckBoxList.cshtml:
   23:  @model IEnumerable <  string  > 
   24:  @{    
   25:      string listName = (string)ViewData.ModelMetadata.AdditionalValues["ListName"];
   26:      @Html.CheckBoxList("", listName, Model)
   27:   }

ASP.NET MVC的Model元数据与Model模板:预定义模板  
ASP.NET MVC的Model元数据与Model模板:模板的获取与执行策略  
ASP.NET MVC的Model元数据与Model模板:将ListControl引入ASP.NET MVC

作者: Artech
出处: http://artech.cnblogs.com/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

作者: Leo_wl

    

出处: http://www.cnblogs.com/Leo_wl/

    

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

版权信息

查看更多关于ASP.NET MVC的Model元数据与Model模板:将”ListControl”引入ASP.N的详细内容...

  阅读:43次