ASP.NET MVC(HelperMethods)

帮助函数封装一些代码,方便我们在应用程序中重用,MVC内建很多帮助函数,可以很方便的生成HTML标记。首先列出后面示例中用到的数据模型类定义:

namespace HelperMethods.Models
{
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; }
}
public class Address
{
public string Line1 { get; set; }
public string Line2 { get; set; }
public string City { get; set; }
[Display(Name = "ZIP CODE")]
public string PostalCode { get; set; }
public string Country { get; set; }
}
public enum Role {
Admin,
User,
Guest
}
}

控制器的定义:

namespace HelperMethods.Controllers
{
public class HomeController : Controller
{
public ActionResult Index()
{
ViewBag.Fruits = new string[] { "Apple", "Orange", "Pear" };
ViewBag.Cities = new string[] { "New York", "London", "Paris" };
string message = "This is an HTML element:
";
return View((object)message);
}
public ActionResult CreatePerson()
{
return View(new Person { Role = Role.Guest});
}
[HttpPost]
public ActionResult CreatePerson(Person person)
{
return View("DisplayPerson", person);
}
}
}

内联帮助函数

我们可以直接在视图中定义内联的帮助函数,使用@helper标记内联函数定义:

@model string 
@{
Layout = null;
}
@helper ListArrayItems(string[] items)
{
foreach (string str in items)
{
@str
}
}


Index
Here are the fruits: @ListArrayItems(ViewBag.Fruits)
Here are the cities: @ListArrayItems(ViewBag.Cities)
Here is the message: @Model

这里定义了一个内联帮助函数ListArrayItems,后续我们可以重用它显示数值内容,内联帮助函数没有返回值。在调用ListArrayItems时我们也没有对参数做类型Cast,MVC会自动实现类型转换。

外部帮助函数

内联帮助函数虽然很方便,但是只能在视图中定义和使用,如果内联函数很复杂会让视图很难读得清楚,对此我们可以定义外部帮助函数,外部帮助函数实际上是对HtmlHelper类的方法扩展,上面的内联帮助函数改写成外部帮助函数是这样的:

namespace HelperMethods.Infrastructure
{
public static class CustomHelpers
{
public static MvcHtmlString ListArrayItems(this HtmlHelper html, string[] list)
{
TagBuilder tag = new TagBuilder("ul");
foreach(string str in list)
{
TagBuilder itemTag = new TagBuilder("li");
itemTag.SetInnerText(str);
tag.InnerHtml += itemTag.ToString();
}
return new MvcHtmlString(tag.ToString());
}
}
}

HtmlHelper暴露一些属性比如RouteCollection、ViewBag、ViewContext方便我们获取当前请求相关的数据。外部帮助函数最后返回一个MvcHtmlString对象,它的内容直接写到输出响应。我们可以这样在视图中使用自定义的外部帮助函数:

@model string
@using HelperMethods.Infrastructure
@{
    Layout = null;
}
<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Index</title>
</head>
<body>
    <div>
        Here are the fruits: @Html.ListArrayItems((string[])ViewBag.Fruits)
    </div>
    <div>
        Here are the cities: @Html.ListArrayItems((string[])ViewBag.Cities)
    </div>
    <div>
        Here is the message:
        <p>@Model</p>
    </div>
</body>
</html> 

我们需要引入定义外部帮助函数的命名空间,也可以定义在/Views/Web.config中供所有的视图使用。使用@html来调用外部帮助函数,它返回一个HtmlHelper类的实例,在调用外部帮助函数时也要做参数类型转化。

帮助函数中的字符串编码

在使用帮助函数时我们需要注意HTML编码的问题,先来看看下面一个问题,如果我们在控制器action中输出一些带HTML标记的字符串到视图:

public ActionResult Index() { 
  string message = "This is an HTML element: <input>"; 
  return View((object)message); 
} 

在视图中直接输出这个字符串:

@model string

@{
    Layout = null;
}

<!DOCTYPE html>
<html>
<head>
    <title>Index</title>
</head>
<body>
    <p>This is the content from the view:</p>
    <div>
        Here is the message:
        <p>@Model</p>
    </div>
</body>
</html>

最后Razor输出的结果是:

... 
<div> 
Here is the message: 
<p>This is an HTML element: &lt;input&gt;</p> 
</div> 
... 

<input>中的“<>”标签被编码为相应的字符而非HTML标签,这是出于安全的考虑,防止动态嵌入HTML标记甚至Javascript脚本。如果是从帮助函数中输出HTML标签又是怎样的结果呢?看下面的例子:

public static MvcHtmlString DisplayMessage(this HtmlHelper html, string msg) { 
    string result = String.Format("This is the message: <p>{0}</p>", msg); 
  return new MvcHtmlString(result); 
}

视图文件中调用这个帮助函数输出HTML标记:

<p>This is the content from the helper method:</p> 
<div style="border: thin solid black; padding: 10px"> 
@Html.DisplayMessage("This is an HTML element: <input>") 
</div> 

这次得到的就是HTML的<input>编辑框,这是因为我们的HTML帮助函数返回的MvcHtmlString被信任为安全的,对输出的结果不再做HTML编码。如果帮助函数返回的不是MvcHtmlString,只是普通的String,比如:

public staticstring DisplayMessage(this HtmlHelper html, string msg) { 
  return String.Format("This is the message: <p>{0}</p>","This is an HTML element: <input>"); } 

这时候字符串又会被HTML编码,不会输出input标签。但这样的结果是<p>标记也会被HTML编码,这不是我们想要的结果,正确的做法是:

public static MvcHtmlStringDisplayMessage(this HtmlHelper html) { 
  string encodedMessage = html.Encode("This is an HTML element: <input>"); 
  string result = String.Format("This is the message: <p>{0}</p>", encodedMessage); 
  return new MvcHtmlString(result); 
} 

我们使用html.Encode()对动态内容编码,结果仍然返回MvcHtmlString对象,这样就避免了动态内容嵌入带来的安全漏洞,也使真正要显示的HTML标记被正确的渲染。

内建的Form帮助函数

我们可以直接使用HTML的Form标记创建一个表单来编辑要提交的数据:

复制代码

@model HelperMethods.Models.Person
@{
    ViewBag.Title = "CreatePerson";
}
<h2>CreatePerson</h2>
<form action="/Home/CreatePerson" method="post">
    <div class="dataElem">
        <label>PersonId</label>
        <input name="personId" value="@Model.PersonId" />
    </div>
    <div class="dataElem">
        <label>First Name</label>
        <input name="FirstName" value="@Model.FirstName" />
    </div>
    <div class="dataElem">
        <label>Last Name</label>
        <input name="lastName" value="@Model.LastName" />
    </div>
    <input type="submit" value="Submit" />
</form> 

这样的问题是必须硬编码表单提交的URL,实际上我们可以使用内建的BeginForm帮助函数创建一个表单:

@Html.BeginForm()
<div class="dataElem">
    <label>PersonId</label>
    <input name="personId" value="@Model.PersonId" />
</div>
<div class="dataElem">
    <label>First Name</label>
    <input name="FirstName" value="@Model.FirstName" />
</div>
<div class="dataElem">
    <label>Last Name</label>
    <input name="lastName" value="@Model.LastName" />
</div>
<input type="submit" value="Submit" />
@{Html.EndForm();} 

BeginForm开始一个表单,EndForm结束表单,实际上更常用的写法是是使用Razor代码块: 

@using (Html.BeginForm())
{
    <div class="dataElem">
        <label>PersonId</label>
        <input name="personId" value="@Model.PersonId" />
    </div>
    <div class="dataElem">
        <label>First Name</label>
        <input name="FirstName" value="@Model.FirstName" />
    </div>
    <div class="dataElem">
        <label>Last Name</label>
        <input name="lastName" value="@Model.LastName" />
    </div>
    <input type="submit" value="Submit" />
} 

@using创建一个自闭合的表单,不再需要调用EndForm。不带参数的BeginForm将表单提交到当前控制器的当前action方法,除此之外BeginForm还有很多重载形式,下面给出一些使用的例子:

@using (Html.BeginForm("CreatePerson", "Home",new { id = "MyIdValue" }, FormMethod.Post,new { @class = "personClass", data_formType="person"})) { 
...
}

这里CreatePerson指定了表单要提交到的控制器,Home为要提交到的action,new { id = “MyIdValue” }为额外的路径映射参数,FormMethod.Post指定使用HTTP post方法提交数据,new { @class = “personClass”, data_formType=”person”}指定Form标记的属性,得到的表单HTML结果是这样的:

... 
<form action="/Home/CreatePerson/MyIdValue" class="personClass" data-formType="person" 
method="post"> 
... 

我们还可以指定表单使用的路径映射生成提交到的链接,比如我们注册了这样一条路径映射:

routes.MapRoute( name: "FormRoute", url: "app/forms/{controller}/{action}" ); 

我们使用BeginRouteForm创建表单并指定要使用的路径映射记录:

@using(Html.BeginRouteForm("FormRoute", new {}, FormMethod.Post, new { @class = "personClass", data_formType="person"})) { 
...
}

最后得到的结果是:

... 
<form action="/app/forms/Home/CreatePerson"class="personClass" data-formType="person" method="post"> 
... 

内建输入帮助函数

实际上不需要使用诸如<input>的HTML标记来创建数据的编辑框,MVC内建提供了大量的输入帮助函数:

HTML元素 示例 输出结果
Checkbox Html.CheckBox(“myCheckbox”, false) <input id=”myCheckbox” name=”myCheckbox” type=”checkbox” value=”true” /> 
<input name=”myCheckbox” type=”hidden” value=”false” />
Hidden Html.Hidden(“myHidden”, “val”) <input id=”myHidden” name=”myHidden” type=”hidden” value=”val” /> 
Radio Html.RadioButton(“myRadiobutton”, “val”, true)  <input checked=”checked” id=”myRadiobutton” name=”myRadiobutton” 
type=”radio” value=”val” />
Password Html.Password(“myPassword”, “val”) Html.Password(“myPassword”, “val”)
Text area Html.TextArea(“myTextarea”, “val”, 5, 20, null) <textarea cols=”20″ id=”myTextarea” name=”myTextarea” rows=”5″> 
val</textarea>
Text box  Html.TextBox(“myTextbox”, “val”) <input id=”myTextbox” name=”myTextbox” type=”text” value=”val” /> 

每一种输入标记函数都有多种重载形式,可以提供额外的参数指定标签的特性。需要注意的是CheckBox,它会生成两条input标记,其中一个类型为hidden,这是因为浏览器在checkbox未选中时不会提交数据,这条hidden的标记使得MVC即使未在复选框未选中时也能获得相应的数据。

使用输入帮助函数前面的表单可以这样写:

@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.TextBox("personId", @Model.PersonId)
    </div>
    <div class="dataElem">
        <label>First Name</label>
        @Html.TextBox("firstName", @Model.FirstName)
    </div>
    <div class="dataElem">
        <label>Last Name</label>
        @Html.TextBox("lastName", @Model.LastName)
    </div>
    <input type="submit" value="Submit" />
} 

我们为每个输入字段指定了名称,如果指定的名称和视图模型的某个属性名称不一致,MVC将不能成功的绑定和创建模型对象,我们可以使用这种替代的方式:

我们不需要再给出字段值,MVC自动根据传入的字符串从View data、viewbag及view model中查找对应的值,比如调用@Html.TextBox(“DataValue”),MVC视图从ViewBag.DataValue、@Model.DataValue获取数值,使用第一个找到的结果。更复杂点的@Html.TextBox(“DataValue.First.Name”),MVC搜索路径也更多:

•  ViewBag.DataValue.First.Name
•  ViewBag.DataValue["First"].Name
•  ViewBag.DataValue["First.Name"]
•  ViewBag.DataValue["First"]["Name"]

同样,在找到第一个可用值后搜索停止,这势必造成性能上的一些损失,但是使得我们创建编辑表单更为简单。

强类型输入帮助函数

上节中每一种基本输入帮助函数都有对应的强类型形式,强类型输入帮助函数只能用在强类型视图中。

HTML元素 示例 输出结果
Checkbox Html.CheckBoxFor(x => x.IsApproved) <input id=”IsApproved” name=”IsApproved” type=”checkbox” value=”true” /> 
<input name=”IsApproved” type=”hidden” value=”false” />
Hidden Html.HiddenFor(x => x.FirstName) <input id=”FirstName” name=”FirstName” type=”hidden” value=”” />
Radio Html.RadioButtonFor(x => x.IsApproved, “val”) input id=”IsApproved” name=”IsApproved” type=”radio” value=”val” />
Password Html.PasswordFor(x => x.Password)  <input id=”Password” name=”Password” type=”password” /> 
Text area  Html.TextAreaFor(x => x.Bio, 5, 20, new{}) <textarea cols=”20″ id=”Bio” name=”Bio” rows=”5″>Bio value</textarea>
Text box Html.TextBoxFor(x => x.FirstName)  <input id=”FirstName” name=”FirstName” type=”text” value=”” />

强类型输入帮助函数使用Lambda表达式从视图模型中抽取字段或者属性,所生成的HTML结果和基本输入没有什么不同,只是帮助我们编程时减少输入的错误。上面的例子使用强类型输入函数是这样的:

@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.TextBoxFor(m => m.PersonId)
    </div>
    <div class="dataElem">
        <label>First Name</label>
        @Html.TextBoxFor(m => m.FirstName)
    </div>
    <div class="dataElem">
        <label>Last Name</label>
        @Html.TextBoxFor(m => m.LastName)
    </div>
    <input type="submit" value="Submit" />
} 

选择元素帮助函数

MVC提供以下帮助函数用以生成HTML的select元素: 

HTML元素 示例 输出结果
Drop-down list  Html.DropDownList(“myList”, new SelectList(new [] {“A”, “B”}), “Choose”)  <select id=”myList” name=”myList”> 
<option value=””>Choose</option> 
<option>A</option> 
<option>B</option> 
</select>
Html.DropDownListFor(x => x.Gender, new SelectList(new [] {“M”, “F”}))  <select id=”Gender” name=”Gender”> 
<option>M</option> 
<option>F</option> 
</select>
Multiple-select Html.ListBox(“myList”, new MultiSelectList(new [] {“A”, “B”}))  <select id=”myList” multiple=”multiple” name=”myList”> 
<option>A</option> 
<option>B</option> 
</select>
Html.ListBoxFor(x => x.Vals, new MultiSelectList(new [] {“A”, “B”})) <select id=”Vals” multiple=”multiple” name=”Vals”> 
<option>A</option> 
<option>B</option> 
</select>

Select帮助函数使用SelectList或者MultilSelectList参数,两者的区别在于后者创建的允许多选的select。Select帮助函数所用的数值为IEnumerbale类型,比如我们要创建一个枚举类型的选择编辑框:

<div class="dataElem"> 
<label>Role</label> 
@Html.DropDownListFor(m => m.Role, new SelectList(Enum.GetNames(typeof(HelperMethods.Models.Role)))) 
</div>

HelperMethods.Models.Role是一个C#的枚举类型,输出的结果是:

<div class="dataElem"> 
  <label>Role</label> 
  <select data-val="true" data-val-required="The Role field is required."  id="Role" name="Role"> 
    <option selected="selected">Admin</option> 
    <option>User</option> 
    <option>Guest</option> 
  </select> 
</div> 

data-val和data-val-required两个特性是和数据验证相关的,后文中再来详细分析。

使用模板帮助函数

数据模型和控制器继续后面的例子,使用模板帮助函数后改写编辑输入的视图:

@model HelperMethods.Models.Person
@{
ViewBag.Title = "CreatePerson";
}
CreatePerson
@using (Html.BeginRouteForm("FormRoute", new { }, FormMethod.Post,new { @class = "personClass", data_formType = "person" }))
{

PersonId @Html.Editor("PersonId")

First Name @Html.Editor("FirstName")

Last Name @Html.EditorFor(m => m.LastName)

Role @Html.EditorFor(m => m.Role)

Birth Date @Html.EditorFor(m => m.BirthDate)



}

这里用到模板帮助函数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按照一定的顺序搜索使用适合的模板:

  1. 帮助函数中指定的模板,比如 Html.EditorFor(m => m.SomeProperty, “MyTemplate”)指定的MyTemplate模板
  2. metadata中UIHint指定的目标
  3. 数据类型确定的模板,比如DataType特性
  4. 正在处理的数据类型类的名称
  5. 对于简单类型使用内建的string模板
  6. 如果数据类型实现IEnumerable,使用内建的Collection模板
  7. 以上失败时,使用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)
}

Leave a Comment