ASP.NET MVC 4 (七) 模板帮助函数
和普通HTML帮助函数不同,模板帮助函数不需要指定所用的HTML类型,MVC会推断选择合适的HTML元素,这让我们有更多的灵活性。
使用模板帮助函数
我们使用《ASP.NET MVC 4 (六) 帮助函数 》中的数据模型和控制器继续后面的例子,使用模板帮助函数后改写编辑输入的视图:
@model HelperMethods.Models.Person @{ ViewBag.Title = "CreatePerson"; } <h2>CreatePerson</h2> @using (Html.BeginRouteForm("FormRoute", new { }, FormMethod.Post,new { @class = "personClass", data_formType = "person" })) { <div class="dataElem"> <label>PersonId</label> @Html.Editor("PersonId") </div> <div class="dataElem"> <label>First Name</label> @Html.Editor("FirstName") </div> <div class="dataElem"> <label>Last Name</label> @Html.EditorFor(m => m.LastName) </div> <div class="dataElem"> <label>Role</label> @Html.EditorFor(m => m.Role) </div> <div class="dataElem"> <label>Birth Date</label> @Html.EditorFor(m => m.BirthDate) </div> <input type="submit" value="Submit" /> }
这里用到模板帮助函数Editor和EditorFor,MVC猜测相应的数据类型生成相应类型的输入HTML标记:
... <h2>CreatePerson</h2> <form action="/app/forms/Home/CreatePerson" class="personClass" data-formtype="person" method="post"> <div class="dataElem"> <label>PersonId</label> <input class="text-box single-line" id="PersonId" name="PersonId" type="number" value="0" /> </div> <div class="dataElem"> <label>First Name</label> <input class="text-box single-line" id="FirstName" name="FirstName" type="text" value="" /> </div> <div class="dataElem"> <label>Last Name</label> <input class="text-box single-line" id="LastName" name="LastName" type="text" value="" /> </div> <div class="dataElem"> <label>Role</label> <input class="text-box single-line" id="Role" name="Role" type="text" value="Admin" /> </div> <div class="dataElem"> <label>Birth Date</label> <input class="text-box single-line" id="BirthDate" name="BirthDate" type="datetime" value="01/01/0001 00:00:00" /> </div> <input type="submit" value="Submit" /> </form> ...
HTML5规范定义了input标签可以编辑通用类型,比如数字、日期,不同浏览器对HTML5的支持有所差别,上面的结果在Opera中number类型会渲染成带spin按钮的编辑空间,datetime为渲染成专用的带可选日历的日期/时间编辑器。
以下是MVC可用的模板帮助函数列表:
帮助函数 | 示例 | 说明 |
Display | Html.Display("FirstName") | 渲染只读的HTML元素,根据数据类型和metadata选择适用的HTML元素 |
DisplayFor | Html.DisplayFor(x => x.FirstName) | Display的强类型形式 |
Editor | Html.Editor("FirstName") | 渲染可编辑的HTML元素,根据数据类型和metadata选择适用的HTML元素 |
EditorFor | Html.EditorFor(x => x.FirstName) | Editor的强类型形式 |
Label | Html.Label("FirstName") | 根据引用的模型对象属性渲染<label>标签 |
LabelFor | Html.LabelFor(x => x.FirstName) | Label的强类型形式 |
这个例子为我们演示如何使用Display和Label:
@model HelperMethods.Models.Person @{ ViewBag.Title = "DisplayPerson"; } <h2>DisplayPerson</h2> <div class="dataElem"> @Html.Label("PersonId") @Html.Display("PersonId") </div> <div class="dataElem"> @Html.Label("FirstName") @Html.Display("FirstName") </div> <div class="dataElem"> @Html.LabelFor(m => m.LastName) @Html.DisplayFor(m => m.LastName) </div> <div class="dataElem"> @Html.LabelFor(m => m.Role) @Html.DisplayFor(m => m.Role) </div> <div class="dataElem"> @Html.LabelFor(m => m.BirthDate) @Html.DisplayFor(m => m.BirthDate) </div>
输出的HTML结果:
... <div class="dataElem"> <label for="PersonId">PersonId</label> 100 </div> <div class="dataElem"> <label for="FirstName">FirstName</label> Adam </div> <div class="dataElem"> <label for="LastName">LastName</label> Freeman </div> <div class="dataElem"> <label for="Role">Role</label> Admin </div> <div class="dataElem"> <label for="BirthDate">BirthDate</label> 01/01/0001 00:00:00 </div> ...
整模型模板帮助函数
上面的模板帮助函数可以处理单个模型对象属性,MVC还提供一组模板帮助函数为整个模型对象生成HTML:
帮助函数 | 示例 | 说明 |
DisplayForModel | Html.DisplayForModel() | 为整个模型对象生成只读的HTML渲染 |
EditorForModel | Html.EditorForModel() | 为整个模型对象生成可编辑的HTML渲染 |
LabelForModel | Html.LabelForModel() | 为整个模型对象生成<label>标签 |
使用整模型帮助函数后编辑Person的视图可以简化为:
@model HelperMethods.Models.Person @{ ViewBag.Title = "CreatePerson"; } <h2>CreatePerson: @Html.LabelForModel()</h2> @using(Html.BeginRouteForm("FormRoute", new {}, FormMethod.Post, new { @class = "personClass", data_formType="person"})) { @Html.EditorForModel() <input type="submit" value="Submit" /> }
MVC会为Person的各个属性生成相应的HTML编辑元素,枚举类型也被渲染为一个简单的编辑框,这也并不是很有用,我们更习惯从下拉框中选择枚举值(后面我们可以看到如何实现)。另外并非所有的属性,比如Address属性,它不是一个c#的元类型,在生成的结果中不可见,我们可以为Address在调用一次EditFor来展开显示它(后面我们可以看到的object模板):
@model HelperMethods.Models.Person @{ ViewBag.Title = "CreatePerson"; } <h2>CreatePerson: @Html.LabelForModel()</h2> @using (Html.BeginRouteForm("FormRoute", new { }, FormMethod.Post,new { @class = "personClass", data_formType = "person" })) { <div class="column"> @Html.EditorForModel() </div> <div class="column"> @Html.EditorFor(m => m.HomeAddress) </div> <input type="submit" value="Submit" /> }
这里使用的是强类型的EditFor,以保证生成HTML元素包含正确的ID和Name,比如HomeAddress.Line1生成id="HomeAddress_Line1",name="HomeAddress.Line1",这能保证数据提交后正确绑定到数据模型上。
使用模型metadata
EditorForModel()生成的HTML并不那么完美,这不能怪罪于模板帮助函数,它已经对要显示的结果做了最好的猜测,我们可以通过metadata给予模板帮助函数更多的提示。比如PersonId我们是不能编辑的,我们可以使用HiddenAttribute标记:
public class Person { [HiddenInput] public int PersonId { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public DateTime BirthDate { get; set; } public Address HomeAddress { get; set; } public bool IsApproved { get; set; } public Role Role { get; set; } }
EditorForModel会渲染一个只读的input元素:
... <div class="editor-field"> 0 <input id="PersonId" name="PersonId" type="hidden" value="0" /> </div> ...
如果我们要完全不显示PersonId:
public class Person { [HiddenInput(DisplayValue=false)] public int PersonId { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public DateTime BirthDate { get; set; } public Address HomeAddress { get; set; } public bool IsApproved { get; set; } public Role Role { get; set; } }
Html.EditorForModel()会生成一个隐藏的input元素,我们看不到它,但是仍然包含在提交的数据中。如果我们要完完全全的忽略一个属性,连隐藏input元素都不要生成:
... [ScaffoldColumn(false)] public int PersonId { get; set; } ...
需要注意的是ScaffoldColumn(false)只对EditorForModel()有作用,单个属性的模板帮助函数比如 @Html.EditorFor(m => m.PersonId)仍然会生成结果,不受ScaffoldColumn影响。
我们还可以通过metadata的方式设定label帮助函数生成的标签内容:
[DisplayName("New Person")] public class Person { [HiddenInput(DisplayValue=false)] public int PersonId { get; set; } [Display(Name="First")] public string FirstName { get; set; } [Display(Name = "Last")] public string LastName { get; set; } [Display(Name = "Birth Date")] public DateTime BirthDate { get; set; } public Address HomeAddress { get; set; } [Display(Name="Approved")] public bool IsApproved { get; set; } public Role Role { get; set; } }
Label帮助函数会使用DisplayName定义的名称“New person”为整个model生成Label标签,使用Display中的Name为各个属性生成Label标签。
我们可以使用DataType特性指定属性的数据类型:
... [Display(Name = "Birth Date")] [DataType(DataType.Date)] public DateTime BirthDate { get; set; } ...
通过指定BirthDate的数据类型为Date,生成的HTML编辑框会只包含日期部分。可用的数据类型包括:DateTime、Date、Time、Text、PhoneNumber、MultilineText、Password、Url、EmailAddress。模板帮助函数根据数据类型的不同选择生成不同的标签元素,比如MultilineText生成textarea多行编辑框。
除了通过DateType指定属性的数据类型,我们还可以使用 UIHint特性明确指定帮助函数生成HTML选用的标签元素:
... [Display(Name="First")] [UIHint("MultilineText")] public string FirstName { get; set; } ...
这里明确指定为FirstName使用一个textarea编辑框,EditorFor和EditorForModel参考这个特性。可以指定的UI模板包括:
UIHint模板 | Editor输出结果 | Display输出结果 |
Boolean | bool值生成一个复选框,bool?生成包含true、false、not set三个选项的选择框 | 和editor相同但是带有disabled属性禁止编辑 |
Collection | 为IEnumerable中每个项目选择合适的目标生成结果,项目不一定是相同的类型 | 等同Editor |
Decimal | 生成单行textbox类型的input元素,格式化带2个小数点的 | 显示带2个小数点的字符串 |
DateTime | 生成type=datetime的input元素,包含日期和时间 | 显示日期和时间 |
Date | 生成type=date的input元素,仅包含日期 | 显示日期 |
EmailAddress | 生成单行textbox的input元素 | 生成a元素,href=mailto: |
HiddenInput | 生成隐藏的input元素 | 生成隐藏Input |
Html | 生成单行textbox的input元素 | 生成a元素标记 |
MultilineText | 生成textarea元素 | 显示数据 |
Number | 生成type=number的input元素 | 显示数据 |
Object | 展开对象,为对象的各个属性生成合适的元素,展开不能递归,也就是说如果某个属性不是一个基本类型就再展开 | |
Password | 生成密码类型的单行textbox | 显示模糊处理后的数据 |
String | 生成单行textbox的input元素 | 显示数据 |
Text | 同string | 同string |
Tel | 生成type=tel的input元素 | 显示数据 |
Time | 生成type=time的input元素,仅显示时间 | 显示时间数据 |
Url | 生成单行input元素 | 生成a元素,内部HTML和href属性都设置为数值 |
需要注意的是如果所选UI模板和数据类型冲突会产生异常,比如为string数据类型选择boolean的UI模板。
我们不需要直接在模型类上编辑metadata属性,特别是那些ORM自动生成的模型类,每次修改数据Schema时就会重建模型类,模型类上的metadata被清除,我们不得不重新编辑metadata,针对这种情况我们可以定义模型类为partial,把metadata放到单独的伙伴类中:
[MetadataType(typeof(PersonMetaData))] public partial class Person { public int PersonId { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public DateTime BirthDate { get; set; } public Address HomeAddress { get; set; } public bool IsApproved { get; set; } public Role Role { get; set; } } [DisplayName("New Person")] public partial class PersonMetaData { [HiddenInput(DisplayValue=false)] public int PersonId { get; set; } [Display(Name="First")] public string FirstName { get; set; } [Display(Name = "Last")] public string LastName { get; set; } [Display(Name = "Birth Date")] public DateTime BirthDate { get; set; } [Display(Name="Approved")] [UIHint("Boolean")] public bool IsApproved { get; set; } [UIHint("Boolean")] public Role Role { get; set; } }
伙伴类中不需要包含每个属性,我们可以只为需要的属性设置metadata。
自定义编辑模板
我们可以通过创建自定义模板进一步控制模板帮助函数生成的HTML结果,MVC在 /Views/Shared/EditorTemplates目录下查找自定义的模板,我们可以创建对应某个数据类型的强分部视图,比如Role枚举类型,我们为它创建Role.cshtml:
@model HelperMethods.Models.Role
@Html.DropDownListFor(m => m, new SelectList(Enum.GetNames(Model.GetType()), Model.ToString()))
这里为Role类型创建了一个下拉选择对话框,MVC会在使用内建模板前搜索到这个自定义的模板并使用它。MVC按照一定的顺序搜索使用适合的模板:
- 帮助函数中指定的模板,比如 Html.EditorFor(m => m.SomeProperty, "MyTemplate")指定的MyTemplate模板
- metadata中UIHint指定的目标
- 数据类型确定的模板,比如DataType特性
- 正在处理的数据类型类的名称
- 对于简单类型使用内建的string模板
- 如果数据类型实现IEnumerable,使用内建的Collection模板
- 以上失败时,使用ojbect模板展开,展开不能递归,也就是不展开子类型的属性
根据上面的模板搜索顺序,可以将Role模板变得更广泛化,我们创建一个Enum类型都适用的模板:
@model Enum @Html.DropDownListFor(m => m, Enum.GetValues(Model.GetType()) .Cast<Enum>() .Select(m => { string enumVal = Enum.GetName(Model.GetType(), m); return new SelectListItem() { Selected = (Model.ToString() == enumVal), Text = enumVal, Value = enumVal }; }))
我们在metadata伙伴类中指定Role属性使用这个Enum模板:
[DisplayName("New Person")] public partial class PersonMetaData1 { ... [UIHint("Enum")] public Role Role { get; set; } }
而如果我们创建了一个和内建同名的模板会怎么样?MVC会使用我们自定义的模板代替内建的模板,比如我们为bool和bool?创建一个替代Boolean内建类型的模板:
@model bool? @if (ViewData.ModelMetadata.IsNullableValueType && Model == null) { @:(True) (False) <b>(Not Set)</b> } else if (Model.Value) { @:<b>(True)</b> (False) (Not Set) } else { @:(True) <b>(False)</b> (Not Set) }
以上为对《Apress Pro ASP.NET MVC 4》第四版相关内容的总结,不详之处参见原版 http://www.apress.com/9781430242369。