閱讀162 返回首頁    go 技術社區[雲棲]


[ASP.NET MVC]通過對HtmlHelper擴展簡化“列表控件”的綁定

在眾多表單元素中,有一類<select>元素用於綁定一組預定義列表。傳統的ASP.NET Web Form中,它對應著一組重要的控件類型,即ListControl,我們經常用到DropDownList, ListBox、CheckBoxList和RadioButtonList都是其子類。ASP.NET MVC通過對HtmlHelper和HtmlHelper<TModel>的擴展實現了對不同類型的<select>元素的綁定,它們以擴展方法的形式定義在SelectExtensions中。當我們在操作這些擴展方法的時候,必須手工地提供以 IEnumerable<SelectListItem>對象表示的列表項。如果我們建立一個獨立的組件來維護這些預定的列表,那麼我們就可以定義一些更加簡單的擴展方法以避免手工地指定列表項。[源代碼從這裏下載]

我們將這些綁定在<select>元素中的預定義列表中的元素稱為Code。作為簡單的演示模擬,我們創建了一個名為CodeManager的組件。我們先來看看用於描述單一Code的CodeDescription類型的定義,如下麵的代碼所示,CodeDescription具有ID、Code、Description、EffectiveStartDate 和EffectiveEndDate。以表示國家的列表為例,Code代表某個國家的代碼(比如CN),Description則是一個可讀性的描述(比如China)。EffectiveStartDate 和EffectiveEndDate決定了Code的有效期限,比如一組表示“稅率”的列表,在不同的時間範圍內可能采用不同的列表。換言之,作為統一類別(通過Category屬性表示)的列表中可能具有“多套”,它們可以共享相同的Code,我們通過ID來區分這些具有相同Code的列表項。

   1: public class CodeDescription
   2: {
   3:     public string Id { get; set; }
   4:     public string Code { get; set; }
   5:     public string Description { get; set; }
   6:     public string Category{get;set;}
   7:     public DateTime EffectiveStartDate { get; set; }
   8:     public DateTime EffectiveEndDate { get; set; }
   9:  
  10:     public CodeDescription(string code, string description, string category)
  11:     {
  12:         this.Id = Guid.NewGuid().ToString();
  13:         this.Code = code;
  14:         this.Description = description;
  15:         this.Category = category;
  16:         this.EffectiveStartDate = DateTime.MinValue;
  17:         this.EffectiveEndDate = DateTime.MaxValue;
  18:     }
  19: }

如下所示的CodeCollection 表示一組CodeDescription列表,它直接繼承自Collection<CodeDescription>類型。由於CodeDescription具有有效期限的概念,我們需要篩選出當前有效的Code,所以我們定義了如下一個GetEffectiveCodes方法。

   1: public class CodeCollection : Collection<CodeDescription>
   2: {
   3:     public IEnumerable<CodeDescription> GetEffectiveCodes()
   4:     {
   5:         return this.Where(code => code.EffectiveStartDate <= DateTime.Today && code.EffectiveEndDate >= DateTime.Today).ToList();
   6:     }
   7: }

在進行Code綁定的時候,我們都是“類別”為單位的。我們總是獲取某一個類別(比如國家、性別、婚姻狀況和政治麵貌等)的Code列表綁定到界麵上。如下所示的CodeManager定義了一個GetCode方法獲取指定類別的Code列表。而作為Code存儲,我們采用了靜態字段的形式,從如下所示的代碼可以看出我們實際定義了三類Code,即Gender、MaritalStatus和Country,分別表示性別、婚姻狀況和國籍。

   1: public static class CodeManager
   2: {
   3:     private static CodeDescription[] codes = new CodeDescription[]
   4:     {
   5:         new CodeDescription("M","Male","Gender"),
   6:         new CodeDescription("F","Female","Gender"),
   7:         new CodeDescription("S","Single","MaritalStatus"),
   8:         new CodeDescription("M","Married","MaritalStatus"),
   9:         new CodeDescription("CN","China","Country"),
  10:         new CodeDescription("US","Unite States","Country"),
  11:         new CodeDescription("UK","Britain","Country"),
  12:         new CodeDescription("SG","Singapore","Country")
  13:     };
  14:     public static CodeCollection GetCodes(string category)
  15:     {
  16:         CodeCollection codeCollection = new CodeCollection();
  17:         foreach(var code in codes.Where(code=>code.Category == category))
  18:         {
  19:             codeCollection.Add(code);
  20:         }
  21:         return codeCollection;
  22:     }
  23: }

現在我們來定義針對HtmlHelper的擴展方法通過從CodeManager獲取的Code列表來進行“列表控件”的綁定。表示列表項的SelectListItem具有Text和Value兩個屬性,分別表示顯示的文本和對應的值。在默認的情況下,它們應該對應於CodeDescription的Description和Code,但是有時候卻需要進行相應的定製。比如說,有時候我們希望通過CodeDescription的ID來作為SelectListItem的值,或者說通過將SelectListItem顯示為Code和Description的組合,比如“CN-China”。為此我們定義了如下一個BindingOption類型。

   1: public class BindingOption
   2: {
   3:     public string OptionalLabel { get;  set; }
   4:     public string TextTemplate { get; set; }
   5:     public string ValueTemplate { get; set; }
   6:  
   7:     public BindingOption()
   8:     {
   9:         this.OptionalLabel = null;
  10:         this.TextTemplate = "{Description}";
  11:         this.ValueTemplate = "{Code}";
  12:     }
  13: }

OptionalLabel表示添加的提示性的文本(比如“請選擇一個Xxx”),而TextTemplate 和ValueTemplate 表示最終作為SelectListItem的Text和Value屬性的模板,模板中包含相應的站位符({Id}、{Code}和{Description})。

我們為HtmlHelper編寫了如下4個擴展方法用於針對DropDownList和ListBox的綁定,在參數中我們無須提供SelectListItem列表,而隻需要提供Code和類別即可。而BindingOption 決定了最終作為SelectListItem的Text和Value屬性,以及是否需要添加一個提示性的文字和文字內容。在真正的項目中,我們可以將BindingOption的設置定義在配置文件中。

   1: public static class SelectExtensions
   2: {
   3:     public static MvcHtmlString DropDownList(this HtmlHelper htmlHelper, string name, string codeCategory, BindingOption bindingOption = null)
   4:     {
   5:         bindingOption = bindingOption ?? new BindingOption();
   6:         var listItems = GenerateListItems(codeCategory, bindingOption);
   7:         return htmlHelper.DropDownList(name, listItems, bindingOption.OptionalLabel);
   8:     }   
   9:     public static MvcHtmlString DropDownListFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, string codeCategory, BindingOption bindingOption = null)
  10:     {
  11:         bindingOption = bindingOption ?? new BindingOption();
  12:         var listItems = GenerateListItems(codeCategory, bindingOption);
  13:         return htmlHelper.DropDownListFor<TModel, TProperty>(expression, listItems,bindingOption.OptionalLabel);
  14:     }
  15:  
  16:     public static MvcHtmlString ListBox(this HtmlHelper htmlHelper, string name, string codeCategory, BindingOption bindingOption = null)
  17:     {
  18:         bindingOption = bindingOption ?? new BindingOption();
  19:         var listItems = GenerateListItems(codeCategory, bindingOption);
  20:         return htmlHelper.ListBox(name, listItems, bindingOption.OptionalLabel);
  21:     }
  22:     public static MvcHtmlString ListBoxFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, string codeCategory, BindingOption bindingOption = null)
  23:     {
  24:         bindingOption = bindingOption ?? new BindingOption();
  25:         var listItems = GenerateListItems(codeCategory, bindingOption);
  26:         return htmlHelper.ListBoxFor<TModel, TProperty>(expression, listItems);
  27:     }
  28:  
  29:     public static IEnumerable<SelectListItem> GenerateListItems(string codeCategory, BindingOption bindingOption)
  30:     {
  31:         var items = new List<SelectListItem>();
  32:         foreach (var code in CodeManager.GetCodes(codeCategory).GetEffectiveCodes())
  33:         {
  34:             var item = new SelectListItem
  35:             {
  36:                 Text = FormatTemplate(bindingOption.TextTemplate, code),
  37:                 Value = FormatTemplate(bindingOption.ValueTemplate, code)
  38:             };
  39:             items.Add(item);
  40:         }
  41:         return items;
  42:     }
  43:  
  44:     private static string FormatTemplate(string template, CodeDescription code)
  45:     {
  46:         return template.Replace("{Id}", code.Id)
  47:             .Replace("{Code}", code.Code)
  48:             .Replace("{Description}", code.Description);
  49:     }
  50: }

現在我們創建一個簡單的ASP.NET MVC應用來演示對DropDownList和ListBox的綁定。為此我們定義了如下一個Person類型,其Gender、MaritalStatus 和Country 屬性分別對應於CodeManager維護的三組Code。在創建的HomeController中,我們將初始化Person對象的呈現定義在Index操作中。

   1: public class Person
   2: {
   3:     public string Name { get; set; }
   4:     public string Gender { get; set; }
   5:     [Display(Name = "MaritalStatus")]
   6:     public string MaritalStatus { get; set; }
   7:     public string Country { get; set; }
   8: }
   9:  
  10: public class HomeController : Controller
  11: {
  12:     public ActionResult Index()
  13:     {
  14:         return View(new Person
  15:         {
  16:             Name = "Zhan San",
  17:             Gender = "M",
  18:             Country = "CN",
  19:             MaritalStatus = "S"
  20:         });
  21:     }
  22: }

我們定義的擴展方法是用在Index操作定義的Index.cshtml視圖中,下麵是Index.cshtml的定義:

   1: @model CodeBinding.Models.Person
   2: @{
   3:     ViewBag.Title = "Index";
   4: }
   5:  
   6: <table>
   7:     <tr>
   8:         <td>@Html.LabelFor(m=>m.Name)</td>
   9:         <td>@Html.TextBoxFor(m=>m.Name)</td>
  10:     </tr>
  11:     <tr>
  12:         <td>@Html.LabelFor(m=>m.Gender)</td>
  13:         <td>@Html.DropDownListFor(m => m.Gender, "Gender", new BindingOption
  14:        {
  15:            OptionalLabel = "Please select your gender...",
  16:            TextTemplate = "{Code}-{Description}",
  17:            ValueTemplate = "{Code}"
  18:        })</td>
  19:     </tr>
  20:      <tr>
  21:         <td>@Html.LabelFor(m=>m.MaritalStatus)</td>
  22:         <td>@Html.DropDownListFor(m => m.MaritalStatus, "MaritalStatus",new BindingOption
  23:        {
  24:            OptionalLabel = "Please select your marital status...",
  25:            TextTemplate = "{Code}-{Description}",
  26:            ValueTemplate = "{Code}"
  27:        })</td>
  28:     </tr>
  29:      <tr>
  30:         <td>@Html.LabelFor(m=>m.Country)</td>
  31:         <td>@Html.ListBoxFor(m => m.Country, "Country")</td>
  32:     </tr>
  33: </table>

最後看看最終呈現出來的效果:

image

通過對HtmlHelper擴展簡化“列表控件”的綁定
為HtmlHelper添加一個RadioButtonList擴展方法
在ASP.NET MVC中使用“RadioButtonList”和“CheckBoxList”


作者:蔣金楠
微信公眾賬號:大內老A
微博:www.weibo.com/artech
如果你想及時得到個人撰寫文章以及著作的消息推送,或者想看看個人推薦的技術資料,可以掃描左邊二維碼(或者長按識別二維碼)關注個人公眾號(原來公眾帳號蔣金楠的自媒體將會停用)。
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁麵明顯位置給出原文連接,否則保留追究法律責任的權利。
原文鏈接

最後更新:2017-10-26 12:04:27

  上一篇:go  ASP.NET MVC是如何運行的(4): Action的執行
  下一篇:go  [ASP.NET MVC]為HtmlHelper添加一個RadioButtonList擴展方法