(转)表单和HTML辅助方法 - ASP.NET MVC 3
Posted hellowzl
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了(转)表单和HTML辅助方法 - ASP.NET MVC 3相关的知识,希望对你有一定的参考价值。
——选自《ASP.NET MVC3 高级编程(第5章) 孙远帅 译》
第5章 表单和html辅助方法
本章内容简介:
* 理解表单
* 如何利用HTML辅助方法
* 编辑和输入的辅助方法
* 显示和渲染的辅助方法
顾名思义,HTML辅助方法是用来辅助HTML开发的。这里可能有一个疑问:诸如向文本编辑器中输入HTML元素如此简单的任务,还需要任何帮助吗?输入标签名称是很容易的事情,但是确保HMTL页面链接中的URL指向正确的位置、表单元素拥有可用于模型绑定的合适的名称和值,以及当模型绑定失败时,其他元素能够显示相应的错误提示消息,这些才是使用HMTL的难点。
实现所有这些方面仅靠HTML标记是远远不够的,还需要视图和运行环境之间的协调配合。学习了本章,就可以很容易地实现他们之间的协调。然而,在学习辅助方法之前,首先要学习表单。应用程序中大部分的困难工作都是在表单中完成的,同事表单也是最需要HTML辅助方法的地方。
5.1 表单的使用
您可能会疑惑面向专业Web开发人员的图书为什么还要浪费笔墨讲解HTML的form标签,难道它不容易理解吗?
这样做有两个原因:
* form标签是强大的:如果没有form标签,Internet将变成一个枯燥文档的只读存储库。您将不能进行网上搜索,也不能在网上购买任何东西(甚至是这本书)。如果一个邪恶的神偷今晚盗取了每一个网站的form标签,那么文明将于明天午餐时分消失殆尽。
* 许多转向MVC框架的开发人员都已经使用过ASP.NET Web Forms:Web Forms没有完全利用form标签的强大功能(也可以说是Web Form为实现自己的目标才管理和利用form标签的)。所以应该原谅那些忘记form标签功能(例如创建HTTP GET请求的功能)的Web Forms开发人员。
5.1.1 action和method特性
表单是包含输入元素的容器,其中包含按钮、复选框、文本框等元素。表单中的这些输入元素使得用户能够向页面输入信息,并把输入的信息提交给服务器。但是提交给什么服务器呢?这些信息又是如何到达服务器的呢?这些问题的答案就在两个非常重要的form标签特性中,即action和method特性。
action特性用以告知Web浏览器信息发往哪里,所以action就顺理成章地包含一个URL。这里的URL可以是相对的,但当向一个不同的应用程序或服务器发送信息时,它也可以是绝对的。下面的form标签将可以从任何应用程序中向站点www.bing.com的search页面发送一个搜索词(输入元素的名称为q):
<form action="http://www.bing.com/search">
<input name="q" type="text"/> <input type="submit" value="Search!"/>
</form>
显而易见,上面代码中的form标签没有method特性。当发送信息时,method特性可以告知浏览器是使用HTTP POST还是使用HTTP GET。现在您可能会认为表单默认的方法是HTTP POST。毕竟经常通过提交表单来更新自己的资料,提交信用卡信息来购物和对YouTube上有趣的动物视频发表评论。然而,尽管如此,默认的方法仍是“get”,所以默认情况下表单发送的是HTTP GET请求。
<form action="http://www.bing.com/search" method="get">
<input name="q" type="text"/>
<input type="submit" value="Search!"/>
</form>
当用户使用HTTP GET请求时,浏览器会提取表单中输入元素的name特性值及其相应的value特性值,并将它们放入到查询字符串中。换句话说,上面的表单将把浏览器导航到URL(假设用户正在搜索关键词love)http://www.bing.com/search?q=love。
5.1.2 GET方法还是POST方法
如果不想让浏览器把输入值放入查询字符串中,而是想放入HTTP请求的主体中,就可以给method特性赋值post。
尽管这样也可以成功地向搜索引擎发送POST请求并能看到相应的搜索结果,但是相对而言,使用HTTP GET请求会更好一些。不像POST请求,GET请求的所有参数都在URL中,因此可以为GET请求建立书签。可以在电子邮件或网页中将这些URL作为超链接来使用,除此之外,还可以保留所有的表单输入值。
更重要的是,因为GET方法代表的是幂等操作和只读操作,所以它是做这些工作的最好选择。换而言之,因为GET不(或应该不)会改变服务器上的状态,所以客户端可以向服务器重复地发送GET请求而不会产生负面影响。
另一方面,POST请求可以用来提交信用卡交易信息、向购物车中添加专辑或者修改密码等。POST请求通常情况下会改变服务器上的状态,重复提交POST请求可能会产生不良的后果(比如购物时,由于重复提交两次POST请求,而产生两个订单)。许多浏览器现在都可以帮助用户避免重复提交POST请求(图 5-1 展示了Chrome浏览器在刷新POST请求时的反应)。
图 5-1
通常情况下,在Web应用程序中,GET请求用于读操作,POST请求用于写操作。为音乐付款就使用了POST请求;像接下来将看到的查询音乐的情形就要使用GET请求。
5.1.2.1 用搜索表单搜索音乐
假设现在想要让音乐商店的顾客可以在音乐商店应用程序的首页搜索音乐。与前面搜索引擎的例子类似,需要一个带有操作和方法的表单。把下面的代码防止HomeController控制器的Index视图中的促销div下面,这样就完成了所需的表单:
<form action="/Home/Search" method="get">
<input name="q" type="text"/>
<input type="submit" value="Search"/>
</form>
可以对上面的代码进行各种完善,但现在还是按原计划顺序介绍示例。下一步就是在HomeController控制器中实现Search方法。下面的代码块对音乐搜索做了最简单的假定,假设用户总是用专辑名称来搜索音乐:
public ActionResult Search(string q)
{
var albums = storeDB.Albums
.Include("Artist")
.Where(a => a.Title.Contains(q) || q == null)
.Take(10);
return View(albums);
}
[注]:这里的Search操作希望接收名为q的字符串参数,当q出现时,ASP.NET MVC框架会自动在查询字符串中找到这个值;即便搜索表单发出的是POST请求而非GET请求,搜索引擎也会在提交的表单中找到这个值。
由控制器告知ASP.NET MVC框架渲染视图,现在就可以在Home视图目录下创建Search.cshtml视图来显示搜索结果:
@model IEnumerable<MvcMusicStore.Models.Album>
@{ ViewBag.Title = "Search"; }
<h2>Results</h2> <table>
<tr>
<th>Artist</th>
<th>Title</th>
<th>Price</th>
</tr>
@foreach(var item in Model)
{
<tr>
<td>@item.Artist.Name</td>
<td>@item.Title</td>
<td>@String.Format("{0:c}", item.Price)</td>
</tr>
}
</table>
假设顾客在搜索输入框中输入搜索关键字“led”,输出的搜索结果将如图 5-2所示。
图 5-2
上面的搜索示例展示了在APS.NET MVC框架中使用HTML表单的简易性。Web浏览器从表单中收集用户输入信息并向MVC应用程序发送一个请求,这里的MVC运行时可以自动地将这些输入值传递给要响应的操作方法的参数。
当然,并非所有的情形都跟搜索表单一样容易。事实上,刚才是将搜索表单简化到了很脆弱的程度。如果刚才的应用程序部署到一个非网站根目录的目录中,或者修改了路由定义,那么刚才手动编写的操作值可能会把用户的浏览器导航到一个网站上并不存在的资源处。请记住,刚才已经把“Home/Search”赋值给了表单的action特性。
<form action="/Home/Search" method="get">
<input name="q" type="text"/>
<input type="submit" value="Search!"/>
</form>
5.1.2.2 通过计算action特性值搜索音乐
更好的办法是通过计算action特性的值来搜索音乐。有一个HTML辅助方法可以代劳这个计算,如下所示。
@using (Html.BeginForm("Search", "Home", FormMethod.Get))
{
<inputname="q" type="text"/>
<inputtype="submit" value="Search"/>
}
BeginForm辅助方法询问路由引擎如何找到HomeController控制器的Search操作。在后台它使用RouteTable中Routes属性上名为GetVirtualPath的方法。如果不采用HTML辅助方法,将不得不编写下面的所有代码:
@{ var context = this.ViewContext.RequestContext; var values = newRouteValueDictionary { {"controller", "home"}, {"action", "index"} }; var path = RouteTable.Routes.GetVirtualPath(context, values); }
<form action="@path.VirtualPath" method="get">
<input type="text" name="q"/>
<input type="submit" value="Search2"/>
</form>
最后一个例子展示了HTML辅助方法的本质:它们不是夺去了程序员的控制权,而是让他们从大量的编码工作中解脱出来。
5.2 HTML辅助方法
HTML辅助方法是可以通过视图的Html属性调用的方法。相应的也可以通过Url属性调用URL辅助方法,通过Ajax属性调用AJAX辅助方法。所有这些方法都有一个共同的目标:使视图编码变得更容易。
大部分的辅助方法输出HTML标记,尤其是HTML辅助方法都如此。例如,刚才提到的BeginForm辅助方法就是在为搜索表单而构建强壮的form标签,但这并没有太多的编码:
@using (Html.BeginForm("Search", "Home", FormMethod.Get))
{
<input type="text" name="q"/>
<input type="submit" value="Search" />
}
BeginForm辅助方法很可能会输出与前面第一次实现搜索表单时同样的标记。然而,在后台这个辅助方法与路由引擎协调以生成合适的URL,从而使代码在应用程序部署位置发生改变时更富有弹性。
注意:BeginForm辅助方法输出的是起始<form>和结束</form>标签。辅助方法在BeginForm的调用期间生成一个起始标签,并且这个调用返回一个实现了IDisposable的对象。当视图中的代码执行到using语句的结束花括号位置时,此时由于隐式调用了Dispose方法,因此辅助方法会生成一个结束</form>标签。这里using的使用使得代码简洁而优雅。如果发现这样不适合自己,也可以使用下面的方法,这个方法的代码看起来前后对称:
@{Html.BeginForm("Search", "Home", FormMethod.Get);} <input type="text" name="q"/> <input type="submit" value="Search"/> @{Html.EndForm();}
乍一看,辅助方法(比如BeginForm)好像使程序员远离了王牌——许多程序员想控制的低级HTML。一旦开始使用辅助方法,就会意识到它们在使您保持高效率的同时还与王牌保持近距离接触。换句话说,就是可以仍然完全控制HTML而不用编写很多代码来处理细节问题。辅助方法不仅能生成尖括号,还能正确的编码特性,构建指向正确资源的合适URL,设置输入元素的名称以简化模型绑定。总之,辅助方法是程序员的好朋友!
5.2.1 自动编码
想任何其他好朋友一样,HTML辅助方法可以帮助您摆脱困境。将在本章中看到的许多辅助方法都可以用来输出模型值。所有这些输出模型值的辅助方法都会在渲染之前对值进行HTML编码。例如,后面将看到的TextArea辅助方法,用来输出HTML元素textarea:
@Html.TextArea("text","hello <br/> world")
TextArea辅助方法中的第二个参数是要渲染的值。上面的例子是向它的值中嵌入一些HTML标记,但TextArea辅助方法将产生下面的标记:
<textarea cols="20"id="text" name="text" rows="2">hello < br/> world</textarea>
[注]:输出值是经过HTML编码的。默认的编码可以帮助避免跨站点脚本攻击(Cross Site Scripting,XSS)。在第7章中将更深一步讲解跨站点脚本攻击。
5.2.2 辅助方法的使用
在保护代码的同时,辅助方法也给出了所需程度的控制。为了展示辅助方法的作用,下面列出了BeginForm辅助方法的另外一个重载版本:
@using (Html.BeginForm("Search", "Home", FormMethod.Get, new { target = "_blank" })) { <input type="text" name="q"/> <input type="submit" value="Search"/> }
在这段代码中,向BeginForm方法的htmlAttributes参数传递了一个匿名类型的对象。几乎ASP.NET MVC框架中的每一个HTML辅助方法在它的某个重载版本中都包含一个htmlAttributes参数。有时可以在不同的重载版本中发现htmlAttributes参数的类型是Idictionary<string,object>。辅助方法采用字典条目(在对象参数的情形下,就是对象的属性名称和属性值)并利用这些条目创建辅助方法生成的元素的特性。例如,上面的代码生成了下面的起始form标签:
<form action="/Home/Search/" method="get" target="_blank">
可以看到已经使用htmlAttributes参数设置了target=“_blank”。事实上,可以使用htmlAttributes参数设置许多必要的特性值。一开始可能会觉得一些特性是有问题的。
例如,设置一个元素的class特性就要求在匿名类型对象上有一个名为class的属性,或者值的字典中有一个名为class的键。在字典中有一个“class”的键值不是问题,问题在于对象中带一个名为class的属性。因为class是C#语言中的一个保留关键字,不能用作属性名或标识符,所以必须在class前面加一个@符号作为前缀:
@using (Html.BeginForm("Search", "Home", FormMethod.Get, new { target = "_blank", @class = "editForm", data_validatable = true }))
将会生成如下的HTML代码:
<form action="/Home/Search" class="editForm" data-validatable="true" method="get" target="_blank">
接下来的一节将阐述辅助方法的工作原理以及其他的一些内置辅助方法。
5.2.3 HTML辅助方法的工作原理
每一个Razor视图都继承了各自基类的Html属性。Html属性的类型是System.Web.Mvc.HtmlHelper<T>,这里的T是一个泛型类型的参数,代表传递给视图的模型类型(默认是dynamic)。这个属性提供了一些可以在视图中调用的实例方法,像EnableClientValidation(选择性的开启或关闭视图中的客户端验证)。然而,上一小节中使用的BegionForm方法并不在这些实例方法之中。事实上,框架定义的大多数辅助方法都是扩展方法。
图 5-3
在只能感知窗口中,当在方法名称左边有一个向下的蓝色箭头(如图5-3所示)时,就说明这个方法是一个扩展方法。从图5-3可以看出,AntiForgeryToken是一个实例方法,BeginForm是一个扩展方法。
为了构建HTML辅助方法体系,扩展方法是一种极其美妙的构建方式,这主要有两个原因。首先,在C#中的扩展方法中只有当在其名称空间范围内,才能调用。ASP.NET MVC所有的HtmlHelper扩展方法都在名称空间System.Web.Mvc.Html中(缘于文件View/web.config中使用的一个名称空间条目,默认情况下都是在该名称空间中)。如果不喜欢这些内置的扩展方法。可以构建自己的扩展方法来代替或增强内置的辅助方法。在第14章中将会学习如何构建自定义的辅助方法。
5.2.4 设置专辑编辑表单
如果需要创建一个视图,用来让用户编辑专辑信息,可以从下面的视图代码开始:
@using (Html.BeginForm())
{
@Html.ValidationSummary(excludePropertyErrors:true)
<fieldset>
<legend>Edit Album</legend>
<p>
<input type="submit" value="Save"/>
</p>
</fieldset>
}
这段代码中有两个辅助方法:Html.BeginForm和Html.ValidationSummary。下面分别对它们进行介绍,首先从Html.BeginForm开始。
5.2.4.1 Html.BegionForm
前面已经使用了BeginForm辅助方法。在上面的代码中,不带参数的BeginForm辅助方法向当前URL发送一个HTTP POST请求,如果视图响应了/StoreManager/Edit/52,那么起始form标签的代码如下所示:
<form action="/StoreManager/Edit/52" method="post">
在这种情形下,HTTP POST将是理想的请求类型,因为这里将要修改服务器上的专辑信息。
5.2.4.2 Html.ValidationSummary
ValidationSummary辅助方法可以用来显示ModelState字典中所有验证错误的无序列表。使用布尔类型参数(值为true)来告知辅助方法排除属性级别的错误。换言之,就是告诉ValidationSummary方法只显示ModelState中与模型本身有关的错误,而去除那些与具体模型属性相关的错误。这里将分开显示属性级别的错误。
假设在控制器操作中的某处有如下用来渲染编辑视图的代码:
ModelState.AddModelError("", "This is all wrong!"); ModelState.AddModelError("Title", "What a terrible name!");
第一个是模型级别的错误,因为代码中没有提供相关错误与特定属性的键。第二个是与Title属性相关联的错误,因此,在视图中的验证摘要区域不会显示这个错误(除非辅助方法中删除参数“Title”或者把方法ValidationSummary的参数值改为false)。在这种情形下,辅助方法将渲染下面的HTML标记:
<div class="validation-summary-errors"> <ul> <li>This is all wrong!</li> </ul> </div>
ValidationSummary辅助方法的其他重载版本可以提供标题文本,并且跟所有辅助方法一样可以设置特定的HTML特性。
《提示:如按照惯例,ValidationSummary辅助方法会将CSS类validation-summary-errors和提供的任何特定CSS类一起渲染。默认的ASP.NET MVC 项目模板包含 一些样式, 用于使这些项以红色显示,如果不喜欢这些样式,可以在文件style.css中进行修改。想了解更多信息的话,请参阅第9章。》
5.2.5 添加输入元素
一旦表单和验证摘要设计完成,就可以在视图中添加一些输入元素让用户来输入专辑信息。下面的代码展示了其中一种方法(刚开始可以只编辑专辑的标题和流派,但是下面的代码处理的是真实音乐商店的Edit操作)
@using(Html.BeginForm())
{
@Html.ValidationSummary(excludePropertyErrors: true)
<fieldset>
<legend>Edit Album</legend>
<p>
@Html.Label("GenreId")
@Html.DropDownList("GenreId", ViewBag.Genres asSelectList)
</p>
<p>
@Html.Label("Title")
@Html.TextBox("Title", Model.Title)
@Html.ValidationMessage("Title")
<input type="submit" value="Save"/>
</p>
</fieldset>
}
新的辅助方法将向用户展示如下界面(如图 5-4所示):
图 5-4
从上述代码中可以看出,在视图中有4个新的辅助方法:Label、DropDownList、TextBox和ValidationMessage。下面首先介绍TextBox辅助方法。
5.2.5.1 Html.TextBox(和Html.TextArea)
TextBox辅助方法渲染type特性为text的input标签。一般用TextBox辅助方法接收用户自由形式的输入。例如,下面形式的调用:
@Html.TextBox("Title", Model.Title)
会生成如下所示的HTML标记:
<input id="Title" name="Title" type="text" value="For Those About To Rock We Salute You"/>
与每一个其他的HTML辅助方法类似,TextBox辅助方法也为个别的HTML特性设置(正如本章前面展示的)提供了重载。TextBox辅助方法的一个兄弟方法就是TextArea辅助方法。下面的代码展示了使用TextArea方法渲染一个能够显示多行文本的<textarea>元素:
@Html.TextArea("text", "hello <br /> world")
上述代码渲染的HTML标记如下:
<textarea cols="20" id="text" name="text" rows="2">hello <br /> world</textarea>
再次注意辅助方法如何将值编码为输出形式(所有的辅助方法都对模型值和特性值进行编码)。TextArea辅助方法的其他重载版本可以通过指定显示的行数和列数控制文本区的大小:
@Html.TextArea("text", "hello <br /> world", 10, 80, null)
这行代码将生成如下所示的HTML标记:
<textarea cols="80" id="text" name="text" rows="10">hello <br /> world</textarea>
5.2.5.2 Html.Label
Label辅助方法将返回一个<label/>元素,并用String类型的参数决定渲染的文本和for特性值。这个辅助方法的一个重载版本允许独立地设置for特性和要渲染的文本。在上面的代码中,调用Html.Label(“GenreId”)将生成如下所示的HTML标记:
<label for="GenreId">Genre</label>
如果以前没有使用过label元素,那么现在可能极想知道这个元素是否有存在的价值。其实,label的作用就是为其他输入元素(比如文本输入元素)显示附加信息,这样可以为用户提供人性化的界面,从而增强应用程序的可访问性。Label的for特性应该包含相关输入元素的ID(在这个例子的HTML标记中,紧跟其后的输入元素是Genre的下拉列表)。呈现的界面可以用label的文本为用户提供有关输入的更好描述。另外一点,如果用户单击label,那么浏览器会把焦点传送给相关的输入控件。这一点对于复选框和单选按钮特别有用,因为这样可以为用户提供更大的单击区域,而不只是复选框和单选框本身。
细心的读者可能已经注意到label渲染的文本不是“GenreId”(传递给辅助方法的字符串),而是“Genre”。在可能的情况下,辅助方法使用任何可用的模型元数据来生成显示内容。下面探讨表单剩余的其他辅助方法,之后再回到这个主题。
5.2.5.3 Html.DropDownList(和Html.ListBox)
DrowpDownList和ListBox辅助方法都返回一个<select />元素。DropDownList允许进行单项选择,而ListBox支持多项选择(通过在要渲染的标记中将multiple特性的值设置为multiple)。
通常情况下,select元素有两个作用:
*展示可选项的列表
*展示字段的当前值
MVC Music Store中的Album类有一个GenreId属性。可以用select元素来显示GenreId属性的值和所有其他可选项。
由于这些辅助方法都需要一些特定的信息,因此当在控制器中使用它们时,还需要做一点设置工作。下拉列表也不例外,它需要一个包含所有可选项的SelectListItem对象集合,其中每一个SelectListItem 对象中又包含Text、Value和Selected三个属性。可以根据需要构建自己的SelectListItem对象集合,也可以使用框架中的SelectList或MultiSelectList辅助方法类来构建。这些类可以查看任何类型的Enumera
ble对象并将其转换为SelectListItem对象序列。例如,StoreManager控制器中的Edit操作:
public ActionResult Edit(int id)
{
var album = storeDB.Albums.Single(a => a.AlbumId == id);
ViewBag.Genres = storeDB.Genres
.OrderBy(g => g.Name)
.AsEnumerable()
.Select(g => newSelectListItem
{
Text = g.Name,
Value = g.GenreId.ToString(),
Selected = album.GenreId == g.GenreId
});
return View(album);
}
5.2.5.4 Html.ValidationMessage
当ModelState字典中的某一特定字段出现错误时,可以使用ValidationMessage辅助方法来显示相应的错误提示消息。例如,在下面的控制器中,为了说明问题,故意在模型状态中为Tittle属性添加一个错误
public ActionResult Edit(int id, FormCollection collection)
{
var album = storeDB.Albums.Find(id);
ModelState.AddModelError("Title", "Whate a terrible name!");
return View(album);
}
在视图中可以用下面这行代码显示错误提示消息:
@Html.ValidationMessage("Title")
执行后生成的HTML标记如下:
<span class="field-validation-error" data-valmsg-for="Title" data-valmsg-replace="true">What a terrible name!</span>
这条消息只有当键值“Title”在模型状态中出现错误时才会出现。也可以调用@Html.ValidationMessage的一个重写方法来重写视图中的错误提示消息:
@Html.ValidationMessage("Title","Something is wrong with your titile")
上述代码将渲染的HTML形式为:
<span class="field-validation-error" data-valmsg-for="Title" data-valmsg-replace="false">Something is wrong with your titile</span>
《提示:如按照惯例,当出现错误时,这个辅助方法会将CSS类field-validation-error和提供的任何特定CSS类一起渲染。默认的ASP.NET MVC项目模板自带了一些样式,使得能够以红色显示这些项, 如果不喜欢,可以在style.css文件中修改这些样式。》
到目前为止,已经描述了辅助方法的一些共同的特性,如HTML编码和HTML特性设置,除此之外,当谈到处理模型值和模型状态时,所有的表单输入特性还有一些共同的行为。
5.2.6 辅助方法、模型和视图数据
辅助方法提供了对HTML细粒度控制的同时带走了构建UI(要在合适的位置显示控件、标签、错误消息和值)的乏味工作。辅助方法如Html.TextBox和Html.DropDownList(以及所有其他的表单辅助方法)检查ViewData对象以获得用于显示的当前值(在ViewBag对象中的所有值也可以通过ViewData得到)。
现在先不考虑要创建的编辑表单,而是看一个简单的例子。如果想在一个表单中设置专辑的价格,可以使用下面的控制器代码。
public ActionResult Edit(int id) { ViewBag.Price = 10.0; return View(); }
& 以上是关于(转)表单和HTML辅助方法 - ASP.NET MVC 3的主要内容,如果未能解决你的问题,请参考以下文章 如何在 ASP.NET MVC 中执行辅助操作(即计算字段)? ASP.NET Core 中的 IHttpActionResult 和辅助方法