标签:
原文:Working with Forms
作者:Rick Anderson、Dave Paquette、Jerrie Pelser
翻译:姚阿勇(Dr.Yao)
校对:孟帅洋(书缘)
这篇文章演示了如何使用表单以及表单中常用的 HTML 元素。HTML 的 Form 元素提供了 Web 应用向服务器回发数据的主要机制。本文的大部分在描述 Tag Helpers 以及它们如何能帮你有效地构建健壮的表单。在阅读本文之前,我们建议你阅读一下 Tag Helpers 。
在很多情况下,HTML Helpers 都提供了对某个 Tag Helper 的替代方法,但重要的是必须意识到 Tag Helper 不是要取代 HTML Helper,而且也并不是每个 HTML Helper 都有对应的 Tag Helper。当一个 HTML Helper 作为替代方案存在时,是有意为之的。
章节:
表单 Form 的 Tag Helper:
[ValidateAntiForgeryToken]
特性时)。asp-route-<参数名>
属性, <参数名>
是路由里面添加过的值。 Html.BeginForm
和 Html.BeginRouteForm
的 routeValues
参数提供了类似的功能。Html.BeginForm
和 Html.BeginRouteForm
示例:
<form asp-controller="Demo" asp-action="Register" method="post">
<!-- Input and Submit elements -->
</form>
上面的 Form Tag Helper 生成如下的 HTML :
<form method="post" action="/Demo/Register">
<!-- Input and Submit elements -->
<input name="__RequestVerificationToken" type="hidden" value="<removed for brevity>" />
</form>
MVC 运行时(runtime)根据 Form Tag Helper 的属性 asp-controller
和 asp-action
生成 action
属性值。Form Tag Helper 也会生成一个隐藏的 请求验证标记 来防止跨站请求伪装(当在HTTP Post 方法上应用了 [ValidateAntiForgeryToken]
特性时)。要保护纯 HTML 避免跨站请求伪装是非常困难的,Form Tag Helper 为你提供了这个服务。
Tag Helper 属性 asp-route
也能为 HTML action
属性生成标记。一个应用含有名为 register
的 路由可以在注册页面使用如下标记:
<form asp-route="register" method="post">
<!-- Input and Submit elements -->
</form>
Views/Account 文件夹下的很多视图(在你创建一个带有 个人用户账户 的新 Web 应用时生成的)都含有asp-route-returnurl 属性:
<form asp-controller="Account" asp-action="Login"
asp-route-returnurl="@ViewData["ReturnUrl"]"
method="post" class="form-horizontal" role="form">
注意
采用内建的模版,只有在你尚未经过验证或授权的情况下去尝试访问需授权的资源时,returnUrl
才会被自动填入。当你尝试一个未授权的访问,安全中间件会根据returnUrl
的设置将你重定向到登录页面。
Input Tag Helper将 HTML <input>
元素绑定到 Razor 视图中的模型表达式上。
语法:
<input asp-for="<Expression Name>" />
Input Tag Helper:
asp-for
属性中指定的表达式名称生成 id
和 name
HTML 属性。 asp-for="Property1.Property2"
等价于 m => m.Property1.Property2
,就是说属性值实际上是表达式的一部分。 asp-for
属性值所使用的就是表达式的名称。type
的属性值。type
属性已被指定,则不会覆盖它。Html.TextBoxFor
and Html.EditorFor
功能重叠。详情可参见 Input Tag Helper 的 HTML Helper 替代方法 一节。An error occurred during the compilation of a resource required to process
this request. Please review the following specific error details and modify
your source code appropriately.
Type expected
‘RegisterViewModel‘ does not contain a definition for ‘Email‘ and no
extension method ‘Email‘ accepting a first argument of type ‘RegisterViewModel‘
could be found (are you missing a using directive or an assembly reference?)
Input
Tag Helper基于 .NET 类型来设置 HTML type
属性。下表列出了一些常见的 .NET 类型和生成出的 HTML 类型(并非所有 .NET 类型都在列)。
.NET 类型 | Input 类型 |
---|---|
Bool | type="checkbox" |
String | type="text" |
DateTime | type="datetime" |
Byte | type="number" |
Int | type="number" |
Single, Double | type="number" |
下表列出了 Input Tag Helper会将其映射到指定 Input 类型的一些常见 数据注释 特性(并非所有特性都在列)。
Attribute | Input Type |
---|---|
[EmailAddress] | type="email" |
[Url] | type="url" |
[HiddenInput] | type="hidden" |
[Phone] | type="tel" |
[DataType(DataType.Password)] | type="password" |
[DataType(DataType.Date)] | type="date" |
[DataType(DataType.Time)] | type="time" |
示例:
using System.ComponentModel.DataAnnotations;
namespace FormsTagHelper.ViewModels
{
public class RegisterViewModel
{
[Required]
[EmailAddress]
[Display(Name = "Email Address")]
public string Email { get; set; }
[Required]
[DataType(DataType.Password)]
public string Password { get; set; }
}
}
@model RegisterViewModel
<form asp-controller="Demo" asp-action="RegisterInput" method="post">
Email: <input asp-for="Email" /> <br />
Password: <input asp-for="Password" /><br />
<button type="submit">Register</button>
</form>
上述代码生成如下的 HTML :
<form method="post" action="/Demo/RegisterInput">
Email:
<input type="email" data-val="true"
data-val-email="The Email Address field is not a valid e-mail address."
data-val-required="The Email Address field is required."
id="Email" name="Email" value="" /> <br>
Password:
<input type="password" data-val="true"
data-val-required="The Password field is required."
id="Password" name="Password" /><br>
<button type="submit">Register</button>
<input name="__RequestVerificationToken" type="hidden" value="<removed for brevity>" />
</form>
Email
和 Password
属性上应用的数据注释在该模型上生成元数据。Input Tag Helper读取模型元数据并生成 HTML5 data-val-*
属性(详见 Model Validation)。这些属性对验证器进行描述使其附加到 Input 字段上。这提供了 unobtrusive 的 HTML5 和 jQuery 验证。
Html.TextBox
、Html.TextBoxFor
、Html.Editor
和 Html.EditorFor
有着与 Input Tag Helper 重复的功能。Input Tag Helper 会自动设置 type
属性;Html.TextBox
和 Html.TextBoxFor
则不会。Html.Editor
和 Html.EditorFor
会处理集合、复杂对象以及模版;Input Tag Helper 则不会。Input Tag Helper 、Html.EditorFor
和 Html.TextBoxFor
是强类型的(它们使用 lambda 表达式);Html.TextBox
和 Html.Editor
则不是(它们使用表达式名称)。
asp-for
属性值是一个 ModelExpression 同时也是 lambda 表达式右边的部分。因此,你不需要使用 Model
前缀,因为 asp-for="Property1"
在生成的代码中会变成 m => m.Property1
。
@{
var joe = "Joe";
}
<input asp-for="@joe" />
生成以下代码:
<input type="text" id="joe" name="joe" value="Joe" />
你还可以通过视图模型的属性路径定位到子属性。考虑这个更复杂的模型,它包含了一个 Address
子属性。
public class AddressViewModel
{
public string AddressLine1 { get; set; }
}
public class RegisterAddressViewModel
{
public string Email { get; set; }
[DataType(DataType.Password)]
public string Password { get; set; }
public AddressViewModel Address { get; set; }
}
在视图中,我们绑定了 Address.AddressLine1
:
@model RegisterAddressViewModel
<form asp-controller="Demo" asp-action="RegisterAddress" method="post">
Email: <input asp-for="Email" /> <br />
Password: <input asp-for="Password" /><br />
Address: <input asp-for="Address.AddressLine1" /><br />
<button type="submit">Register</button>
</form>
以下 HTML 是根据 Address.AddressLine1
生成的:
<input type="text" id="Address_AddressLine1" name="Address.AddressLine1" value="" />
示例,包含一个 Colors
数组的模型:
public class Person
{
public List<string> Colors { get; set; }
public int Age { get; set; }
}
Action 方法:
public IActionResult Edit(int id, int colorIndex)
{
ViewData["Index"] = colorIndex;
return View(GetPerson(id));
}
下面的 Razor 代码展示了如何访问指定的 Color
元素:
@model Person
@{
var index = (int)ViewData["index"];
}
<form asp-controller="ToDo" asp-action="Edit" method="post">
@Html.EditorFor(m => m.Colors[index])
<label asp-for="Age"></label>
<input asp-for="Age" /><br />
<button type="submit">Post</button>
</form>
Views/Shared/EditorTemplates/String.cshtml 模版:
@model string
<label asp-for="@Model"></label>
<input asp-for="@Model" /> <br />
使用 List<T>
的例子:
public class ToDoItem
{
public string Name { get; set; }
public bool IsDone { get; set; }
下面的 Razor 代码展示了如何遍历一个集合:
@model List<ToDoItem>
<form asp-controller="ToDo" asp-action="Edit" method="post">
<table>
<tr> <th>Name</th> <th>Is Done</th> </tr>
@for (int i = 0; i < Model.Count; i++)
{
<tr>
@Html.EditorFor(model => model[i])
</tr>
}
</table>
<button type="submit">Save</button>
</form>
@model ToDoItem
<td>
<label asp-for="@Model.Name"></label>
@Html.DisplayFor(model => model.Name)
</td>
<td>
<input asp-for="@Model.IsDone" />
</td>
@*
This template replaces the following Razor which evaluates the indexer three times.
<td>
<label asp-for="@Model[i].Name"></label>
@Html.DisplayFor(model => model[i].Name)
</td>
<td>
<input asp-for="@Model[i].IsDone" />
</td>
*@
注意
应始终使用for
(而 不是foreach
)遍历列表。在 LINQ 表达式中执行索引器会产生开销应当尽量减少。
注意
上面示例中被注释的代码演示了应当如何使用@
操作符代替 lambda 表达式去访问列表中的每一个ToDoItem
。
Textarea Tag Helper 与 Input Tag Helper类似。
<textarea>
元素生成 id
和 name
属性,以及数据验证属性。Html.TextAreaFor
示例:
using System.ComponentModel.DataAnnotations;
namespace FormsTagHelper.ViewModels
{
public class DescriptionViewModel
{
[MinLength(5)]
[MaxLength(1024)]
public string Description { get; set; }
}
}
@model DescriptionViewModel
<form asp-controller="Demo" asp-action="RegisterTextArea" method="post">
<textarea asp-for="Description"></textarea>
<button type="submit">Test</button>
</form>
生成以下代码:
<form method="post" action="/Demo/RegisterTextArea">
<textarea data-val="true"
data-val-maxlength="The field Description must be a string or array type with a maximum length of '1024'."
data-val-maxlength-max="1024"
data-val-minlength="The field Description must be a string or array type with a minimum length of '5'."
data-val-minlength-min="5"
id="Description" name="Description">
</textarea>
<button type="submit">Test</button>
<input name="__RequestVerificationToken" type="hidden" value="<removed for brevity>" />
</form>
<label>
元素上生成标签文字和 for
属性。Html.LabelFor
。Label Tag Helper 相对于纯 HTML label 元素具有以下优势:
Display
特性自动获得描述性的 Label 值。随着时间推移,预期的显示名称可能会变化,而结合使用 Display
特性与 Label Tag Helper将会在所有使用它的地方应用 Display
。示例:
using System.ComponentModel.DataAnnotations;
namespace FormsTagHelper.ViewModels
{
public class SimpleViewModel
{
[Required]
[EmailAddress]
[Display(Name = "Email Address")]
public string Email { get; set; }
}
}
@model SimpleViewModel
<form asp-controller="Demo" asp-action="RegisterLabel" method="post">
<label asp-for="Email"></label>
<input asp-for="Email" /> <br />
</form>
以下是为 <label>
元素生成的 HTML :
<label for="Email">Email Address</label>
Label Tag Helper生成了 "Email" 的 for
属性值,也就是与 <input>
元素关联的 ID 。Tag Helper生成一致的 id
和 for
元素,因此它们可以正确地关联起来。本例中的标签文本来自于 Display
特性。如果模型没有 Display
特性,标签文本则会是表达式的属性名称。
有两种验证Tag Helper。Validation Message Tag Helper(用来显示模型上单个属性的验证信息),和Validation Summary Tag Helper (用来显示验证错误汇总)。Input Tag Helper 根据模型类的数据注释给 input 元素添加 HTML5 客户端验证属性。验证也在服务端执行。Validation Tag Helper会在验证发生错误的时候显示这些错误信息。
data-valmsg-for="property"
属性到 span 元素,使验证错误信息附加到指定模型属性的 input 字段上。当客户端验证发生错误,jQuery 会在 <span>
元素里显示错误信息。Html.ValidationMessageFor
Validaton Message Tag Helper 与 HTML span 元素上的 asp-validation-for
属性一起使用。
<span asp-validation-for="Email"></span>
Validation Message Tag Helper将生成以下 HTML :
<span class="field-validation-valid"
data-valmsg-for="Email"
data-valmsg-replace="true"></span>
通常在模型属性相同的 Input
Tag Helper后面使用 Validation Message Tag Helper 。这样可以在发生验证错误的 input 旁边显示错误信息。
注意
必须有一个正确引用了 JavaScript 和 jQuery 脚本的视图进行客户端验证。详见: Model Validation 。
当服务端验证发生了错误(比如你有自定义的服务端验证或者客户端验证被禁用),MVC 会把错误信息放在 <span>
元素的正文中。
<span class="field-validation-error" data-valmsg-for="Email"
data-valmsg-replace="true">
The Email Address field is required.
</span>
asp-validation-summary
属性的 <div>
元素。@Html.ValidationSummary
。Validation Summary Tag Helper 用来显示验证信息的摘要。 asp-validation-summary
属性值可以是下面任意一种:
asp-validation-summary | Validation messages displayed |
---|---|
ValidationSummary.All | Property and model level |
ValidationSummary.ModelOnly | Model |
ValidationSummary.None | None |
在以下示例中,数据模型装饰了 DataAnnotation
特性,用以在 <input>
元素上生成验证错误信息。当发生验证错误的时候, Validation Tag Helper显示错误信息:
using System.ComponentModel.DataAnnotations;
namespace FormsTagHelper.ViewModels
{
public class RegisterViewModel
{
[Required]
[EmailAddress]
[Display(Name = "Email Address")]
public string Email { get; set; }
[Required]
[DataType(DataType.Password)]
public string Password { get; set; }
}
}
@model RegisterViewModel
<form asp-controller="Demo" asp-action="RegisterValidation" method="post">
<div asp-validation-summary="ValidationSummary.ModelOnly"></div>
Email: <input asp-for="Email" /> <br />
<span asp-validation-for="Email"></span><br />
Password: <input asp-for="Password" /><br />
<span asp-validation-for="Password"></span><br />
<button type="submit">Register</button>
</form>
生成的 HTML (当模型有效时):
<form action="/DemoReg/Register" method="post">
<div class="validation-summary-valid" data-valmsg-summary="true">
<ul><li style="display:none"></li></ul></div>
Email: <input name="Email" id="Email" type="email" value=""
data-val-required="The Email field is required."
data-val-email="The Email field is not a valid e-mail address."
data-val="true"> <br>
<span class="field-validation-valid" data-valmsg-replace="true"
data-valmsg-for="Email"></span><br>
Password: <input name="Password" id="Password" type="password"
data-val-required="The Password field is required." data-val="true"><br>
<span class="field-validation-valid" data-valmsg-replace="true"
data-valmsg-for="Password"></span><br>
<button type="submit">Register</button>
<input name="__RequestVerificationToken" type="hidden" value="<removed for brevity>" />
</form>
Select Tag Helper 的 asp-for
为 select 元素指定模型的属性名称,而 asp-items
则指定 option 元素。例如:
<select asp-for="Country" asp-items="Model.Countries"></select>
示例:
using Microsoft.AspNetCore.Mvc.Rendering;
using System.Collections.Generic;
namespace FormsTagHelper.ViewModels
{
public class CountryViewModel
{
public string Country { get; set; }
public List<SelectListItem> Countries { get; } = new List<SelectListItem>
{
new SelectListItem { Value = "MX", Text = "Mexico" },
new SelectListItem { Value = "CA", Text = "Canada" },
new SelectListItem { Value = "US", Text = "USA" },
};
}
}
Index
方法初始化 CountryViewModel
,设置已选国家然后把它传给 Index
视图。
public IActionResult Index()
{
var model = new CountryViewModel();
model.Country = "CA";
return View(model);
}
HTTP POST Index
方法显示选择的项:
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Index(CountryViewModel model)
{
if (ModelState.IsValid)
{
var msg = model.Country + " selected";
return RedirectToAction("IndexSuccess", new { message = msg});
}
// If we got this far, something failed; redisplay form.
return View(model);
}
Index
视图:
@model CountryViewModel
<form asp-controller="Home" asp-action="Index" method="post">
<select asp-for="Country" asp-items="Model.Countries"></select>
<br /><button type="submit">Register</button>
</form>
生成以下 HTML (选择了 "CA" ):
<form method="post" action="/">
<select id="Country" name="Country">
<option value="MX">Mexico</option>
<option selected="selected" value="CA">Canada</option>
<option value="US">USA</option>
</select>
<br /><button type="submit">Register</button>
<input name="__RequestVerificationToken" type="hidden" value="<removed for brevity>" />
</form>
注意
我们不推荐将ViewBag
或ViewData
用于 Select Tag Helper 。视图模型在提供 MVC 元数据方面更加健壮并且通常来说问题更少。
asp-for
属性值是一个特例,不需要 Model
前缀,而其他的 Tag Helper 属性则需要(比如 asp-items
)。
<select asp-for="Country" asp-items="Model.Countries"></select>
将 enum
属性用于 <select>
并根据 enum
的值生成 `SelectListItemselectlistitem] 元素通常是很方便的。
示例:
public class CountryEnumViewModel
{
public CountryEnum EnumCountry { get; set; }
}
using System.ComponentModel.DataAnnotations;
namespace FormsTagHelper.ViewModels
{
{
Mexico,
[Display(Name = "United States of America")]
USA,
Canada,
France,
Germany,
Spain
}
}
GetEnumSelectList 方法生产一个 SelectList 枚举对象.
@model CountryEnumViewModel
<form asp-controller="Home" asp-action="IndexEnum" method="post">
<select asp-for="EnumCountry"
asp-items="Html.GetEnumSelectList<CountryEnum>()"> >
</select>
<br /><button type="submit">Register</button>
</form>
你可以使用 Display
特性装饰你的枚举数从而获得更丰富的 UI :
using System.ComponentModel.DataAnnotations;
namespace FormsTagHelper.ViewModels
{
public enum CountryEnum
{
[Display(Name = "United Mexican States")]
Mexico,
[Display(Name = "United States of America")]
USA,
Canada,
France,
Germany,
Spain
}
}
生成以下的 HTML :
<form method="post" action="/Home/IndexEnum">
<select data-val="true" data-val-required="The EnumCountry field is required."
id="EnumCountry" name="EnumCountry">
<option value="0">United Mexican States</option>
<option value="1">United States of America</option>
<option value="2">Canada</option>
<option value="3">France</option>
<option value="4">Germany</option>
<option selected="selected" value="5">Spain</option>
</select>
<br /><button type="submit">Register</button>
<input name="__RequestVerificationToken" type="hidden" value="<removed for brevity>" />
</form>
当视图模型包含一个或多个 SelectListGroup 对象时,会生成 HTML <optgroup>
元素。
CountryViewModelGroup
把 SelectListItem
元素分到 "North America" 和 "Europe" 分组中:
public class CountryViewModelGroup
{
public CountryViewModelGroup()
{
var NorthAmericaGroup = new SelectListGroup { Name = "North America" };
var EuropeGroup = new SelectListGroup { Name = "Europe" };
Countries = new List<SelectListItem>
{
new SelectListItem
{
Value = "MEX",
Text = "Mexico",
Group = NorthAmericaGroup
},
new SelectListItem
{
Value = "CAN",
Text = "Canada",
Group = NorthAmericaGroup
},
new SelectListItem
{
Value = "US",
Text = "USA",
Group = NorthAmericaGroup
},
new SelectListItem
{
Value = "FR",
Text = "France",
Group = EuropeGroup
},
new SelectListItem
{
Value = "ES",
Text = "Spain",
Group = EuropeGroup
},
new SelectListItem
{
Value = "DE",
Text = "Germany",
Group = EuropeGroup
}
};
}
public string Country { get; set; }
public List<SelectListItem> Countries { get; }
}
下面展示了这两个分组:
生成的 HTML :
<form method="post" action="/Home/IndexGroup">
<select id="Country" name="Country">
<optgroup label="North America">
<option value="MEX">Mexico</option>
<option value="CAN">Canada</option>
<option value="US">USA</option>
</optgroup>
<optgroup label="Europe">
<option value="FR">France</option>
<option value="ES">Spain</option>
<option value="DE">Germany</option>
</optgroup>
</select>
<br /><button type="submit">Register</button>
<input name="__RequestVerificationToken" type="hidden" value="<removed for brevity>" />
</form>
如果 asp-for
属性中指定的模型属性是一个 IEnumerable
类型, Select Tag Helper 将会自动生成multiple = "multiple"。例如,已知以下模型:
using Microsoft.AspNetCore.Mvc.Rendering;
using System.Collections.Generic;
namespace FormsTagHelper.ViewModels
{
public class CountryViewModelIEnumerable
{
public IEnumerable<string> CountryCodes { get; set; }
public List<SelectListItem> Countries { get; } = new List<SelectListItem>
{
new SelectListItem { Value = "MX", Text = "Mexico" },
new SelectListItem { Value = "CA", Text = "Canada" },
new SelectListItem { Value = "US", Text = "USA" },
new SelectListItem { Value = "FR", Text = "France" },
new SelectListItem { Value = "ES", Text = "Spain" },
new SelectListItem { Value = "DE", Text = "Germany"}
};
}
}
使用以下视图:
@model CountryViewModelIEnumerable
<form asp-controller="Home" asp-action="IndexMultiSelect" method="post">
<select asp-for="CountryCodes" asp-items="Model.Countries"></select>
<br /><button type="submit">Register</button>
</form>
生成如下 HTML :
<form method="post" action="/Home/IndexMultiSelect">
<select id="CountryCodes"
multiple="multiple"
name="CountryCodes"><option value="MX">Mexico</option>
<option value="CA">Canada</option>
<option value="US">USA</option>
<option value="FR">France</option>
<option value="ES">Spain</option>
<option value="DE">Germany</option>
</select>
<br /><button type="submit">Register</button>
<input name="__RequestVerificationToken" type="hidden" value="<removed for brevity>" />
</form>
想要允许无选择,可添加一个 “未选择” 项到选择列表。如果该模型属性是一个 值类型,则需要使其为可空值 nullable 。
@model CountryViewModel
<form asp-controller="Home" asp-action="IndexEmpty" method="post">
<select asp-for="Country" asp-items="Model.Countries">
<option value=""><none></option>
</select>
<br /><button type="submit">Register</button>
</form>
如果你在多个页面里使用“未选择”项,可以创建一个模版避免重复的 HTML:
@model CountryViewModel
<form asp-controller="Home" asp-action="IndexEmpty" method="post">
@Html.EditorForModel()
<br /><button type="submit">Register</button>
</form>
Views/Shared/EditorTemplates/CountryViewModel.cshtml 模版:
@model CountryViewModel
<select asp-for="Country" asp-items="Model.Countries">
<option value="">--none--</option>
</select>
添加 HTML <option>
元素并不局限于 无选择 的情况。比如,下面的视图和 Action 方法会生成和上面类似的 HTML :
public IActionResult IndexOption(int id)
{
var model = new CountryViewModel();
model.Country = "CA";
return View(model);
}
@model CountryViewModel
<form asp-controller="Home" asp-action="IndexEmpty" method="post">
<select asp-for="Country">
<option value=""><none></option>
<option value="MX">Mexico</option>
<option value="CA">Canada</option>
<option value="US">USA</option>
</select>
<br /><button type="submit">Register</button>
</form>
<option>
元素将会根据当前的 Country
值被正确选中(加上 selected="selected"
属性)。
<form method="post" action="/Home/IndexEmpty">
<select id="Country" name="Country">
<option value=""><none></option>
<option value="MX">Mexico</option>
<option value="CA" selected="selected">Canada</option>
<option value="US">USA</option>
</select>
<br /><button type="submit">Register</button>
<input name="__RequestVerificationToken" type="hidden" value="<removed for brevity>" />
</form>
标签:
原文地址:http://www.cnblogs.com/Leo_wl/p/5870034.html