5.5 使用HTML帮助器来截短文字
使用我们的StoreManager控制器中的Index方法添加视图的时候,有一个潜在的问题。事实上,我们的书名与作者名属性的长度值可能超出页面上 所定义的这两个列的长度。我们将要专门创建一个HTML帮助器来很轻松地在页面上截短从这两个属性或其他属性中读取出来的文字。
另外,此处所讲的完全是一个小的技巧,所以你如果不想使用这个技巧的话也无关紧要。学习书写自己的HTML帮助器可以帮助简化你的代码书写工作,但它不是一个必须要掌握的基础知识。
在解决方案资源管理器中追加一个名为Helpes的新的文件夹,并且在该文件夹中追加一个名为HtmlHelpers.cs的类,如图5-4所示。
图5-4 追加Html帮助器类及其存放的文件夹
我们的HTML帮助器将要针对ASP.NET MVC中的视图模板中的追加一个新的”Truncate”方法。我们将要通过对ASP.NET MVC中内置的System.Web.Mvc.HtmlHelper类来实现一个“扩展方法”来实现这个处理。我们的帮助器类与方法必须声明为static类型的。方法的书写代码十分简单,如代码清单5-6所示。
代码清单5-6 Truncate方法的实现代码
using System.Web.Mvc;
namespace MvcBookStore.Helpers
{
public static class HtmlHelpers
{
public static string Truncate(this HtmlHelper helper, string input,
int length)
{
if (input.Length <= length)
{
return input;
}
else
{
return input.Substring(0, length) + "...";
}
}
}
}
在视图代码中需要向Truncate方法中需要传入两个参数,第一个参数为需要截短的字符串,第二个参数为将字符串截短后的长度。如果字符串本身长度小于等于给定长度,方法返回该字符串本身,否则返回截短后的字符串,后面加上“…”,提示用户该文字被截短。
为了使用我们自定义的HTML帮助器,我们需要在视图中加入对于命名空间的引用,与控制器代码中一样,使用using语句,追加在StoreManager控制器的Index视图模板中的文件头部的@model行的下面,方法如下所示。
@model IEnumerable<MvcBookStore.Models.Book>
@using MvcBookStore.Helpers
现在我们可以使用我们的Html.Truncate帮助器来确保书名的长度不超过五个字符,作者名的长度不超过四个字符(仅做临时测试用途)。使用我们新的Truncate帮助器之后的完整的视图代码如代码清单5-7所示。
代码清单5-7 使用我们新的Truncate帮助器之后的完整的视图代码
@model IEnumerable<MvcBookStore.Models.Book>
@using MvcBookStore.Helpers
@{
ViewBag.Title = "书籍列表";
}
<h2>书籍列表</h2>
<p>
@Html.ActionLink("添加书籍", "Create")
</p>
<table>
<tr>
<th></th>
<th>书名</th>
<th>作者</th>
<th>种类</th>
</tr>
@foreach (var item in Model) {
<tr>
<td>
@Html.ActionLink("编辑","Edit", new { id=item.BookId }) |
@Html.ActionLink("删除","Delete", new { id=item.BookId })
</td>
<td>@Html.Truncate(item.Title,5) </td>
<td>@Html.Truncate(item.Author.Name,4)</td>
<td>@item.Genre.Name</td>
</tr>
}
</table>
现在我们可以运行应用程序,在浏览器中键入“/StoreManager/”这个URL地址,作者名与书名都被限定在指定字符串长度之下,如图5-5所示。
图5-5 书名与作者名都被截短
5.6 创建数据编辑视图
接下来,让我们创建一个带表单的网页,以便网站管理员编辑书籍信息。表单中将要包括一本书的书名、单价、书籍种类等信息。稍后,我们将要追加几个下拉框,以便管理员可以直接从中选择作者与书籍种类信息。5.6.1 实现Edit(编辑)action方法
当用户输入“/StoreManager/Edit/[id]”这个URL地址时(id值表示要编辑书籍的ID号),StoreManager这个控制器应该实现让管理员可以编辑书籍信息的处理。
当管理员首次访问这个地址时,我们应该运行应用程序中的有关代码来取得数据库中的书籍信息,然后创建一个Book对象来将这些信息以及一个作者列表、一个 种类列表进行封装,然后将Book对象传入一个新的视图模板,以便向用户显示一个Html网页。在这个Html网页中将要包含一 个<form>元素,在这个表单中包括用来编辑书籍信息的文本框。
管理员可以修改这个表单中的书籍信息,然后点击保存按钮来向应用程序提交修改后的表单值(即表单中各控件的值),并且将其保存在数据库中。当用户点击保存 按钮时,表单将会向当前的“/StoreManager/Edit/[id]”这个URL地址实行一个HTTP-POST提交,并且将表单值作为提交内容 的一部分。
ASP.NET MVC允许我们很轻松地将这种针对同一个URL地址的两种调用方式(首次访问与编辑后提交)通过在StoreManagerController控制器类中实现两个不同的Edit方法来进行区分,一个方法用来处理首次访问时的操作,另一个用来处理表单修改后提交时的操作。
两种不同的Edit方法的实现代码类似如下所示。
//
// GET: /StoreManager/Edit/5
public ActionResult Edit(int id)
{
//Display Edit form
}
//
// POST: /StoreManager/Edit/5
[HttpPost]
public ActionResult Edit(int id, FormCollection formValues)
{
//Save Book
}
ASP.NET MVC将会自动根据对于“/StoreManager/Edit/[id]”这个URL地址的访问是一个HTTP-GET请求还是HTTP-POST请求来决定到底调用哪个方法。如果是一个HTTP-POST请求,则调用第二个方法,否则的话则调用第一个方法。
5.6.2 书写对于HTTP-GET请求的Edit方法
通过如下所示的代码来实现处理HTTP-GET请求的Edit方法。它从数据库中进行书籍的查询,然后将查询到的数据传入视图模板进行显示。
//
// GET: /StoreManager/Edit/5
public ActionResult Edit(int id)
{
Book book = storeDB.Books.Find(id);
return View(book);
}
5.6.3 创建编辑数据用视图模板
在Edit方法中点击鼠标右键,然后点击“添加视图”命令。在添加视图对话框中选取“创建强类型视图”复选框,在支架模板下拉框中选择“Edit”(选择编辑数据用模板),如图5-6所示。
图5-6 添加编辑数据用视图
点击添加按钮后,Visual Web Developer自动创建的视图中的代码如代码清单5-8所示(笔者已将其中文化)。
代码清单5-8 Visual Web Developer自动创建的编辑数据用视图中的代码
@model MvcBookStore.Models.Book
@{
ViewBag.Title = "编辑书籍";
}
<h2>编辑书籍</h2>
<script src="@Url.Content("~/Scripts/jquery.validate.min.js")"
type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")"
type="text/javascript"></script>
@using (Html.BeginForm()) {
@Html.ValidationSummary(true)
<fieldset>
<legend>书籍信息</legend>
@Html.HiddenFor(model =>model.BookId)
<div class="editor-label">
书籍种类
</div>
<div class="editor-field">
@Html.EditorFor(model =>model.GenreId)
@Html.ValidationMessageFor(model=> model.GenreId)
</div>
<div class="editor-label">
作者编号
</div>
<div class="editor-field">
@Html.EditorFor(model =>model.AuthorId)
@Html.ValidationMessageFor(model=> model.AuthorId)
</div>
<div class="editor-label">
书名
</div>
<div class="editor-field">
@Html.EditorFor(model =>model.Title)
@Html.ValidationMessageFor(model=> model.Title)
</div>
<div class="editor-label">
单价
</div>
<div class="editor-field">
@Html.EditorFor(model =>model.Price)
@Html.ValidationMessageFor(model=> model.Price)
</div>
<p>
<input type="submit" value="保存" />
</p>
</fieldset>
}
<div>
@Html.ActionLink("返回书籍列表", "Index")
</div>
现在让我们运行我们的应用程序,访问“/StoreManager/Edit/5”这个URL地址。浏览器中显示结果如图5-7所示。
图5-7 书籍的信息编辑页面
5.6.4 使用一个编辑器模板
我们已经看到了Visual Web Developer自动创建的编辑数据用视图中的代码,也看到了它在浏览器中的运行结果。接下来,我们通过修改这个视图,使其使用内置的 Html.EditorFor()帮助器方法来看一下另一种编辑数据用视图的书写方法。这个方法有几个好处,包括可以将表单重用在应用程序的其他视图中。
将我们的编辑数据用视图中的代码修改为如代码清单5-9中所示的代码(笔者已对其进行过中文化处理)。
代码清单5-9 另一种编辑数据用视图的代码书写方法
@model MvcBookStore.Models.Book
@{
ViewBag.Title = "编辑 - " + Model.Title;
}
<h2>编辑书籍</h2>
<script src="@Url.Content("~/Scripts/jquery.validate.min.js")"
type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")"
type="text/javascript"></script>
@using (Html.BeginForm()) {
@Html.ValidationSummary(true)
<fieldset>
<legend>编辑书籍</legend>
@Html.EditorForModel()
<p>
<input type="submit" value="保存" />
</p>
</fieldset>
}
<div>
@Html.ActionLink("返回书籍列表", "Index")
</div>
再次重新运行应用程序并访问“/StoreManager/Edit/5”这个URL地址,浏览器中所示结果如图5-8所示(默认的编辑模板中为直接显示模型的属性名,稍后介绍如何修改成中文)。
图5-8 第二种方法创建的编辑数据用视图的显示结果
我们已经介绍了创建编辑数据用视图的两种方法:
1) 我们可以使用添加数据对话框来为模型中的所有属性添加编辑控件,而这些编辑控件的代码全部被书写在Edit.cshtml视图模板文件中。
2) 我们也可以使用ASP.NET MVC中的模板来书写视图,编辑控件的代码全部被封装在模板中,浏览器通过运行时解析模板来显示表单及其控件。
这两种方法在不同的情况下各有各的优势。接下来,我们继续使用Html.EditForModel()这个模板特性,以便可以在一个视图中引用其他视图。
5.6.5 创建一个公共的书籍编辑器模板
我们的书籍编辑视图与书籍添加视图中的表单中的内容是完全一致的,所以我们可以使用一个公共的书籍编辑器模板。在我们的应用程序中多处重用一个编辑器模板可以给用户一种操作连贯的感觉。同样也由于不用在多处复制同一段代码而提高了代码的可管理性与可维护性。
ASP.NET MVC遵循一种对于模板访问方法的默认约定,所以文件夹与文件名是十分重要的。当我们在视图模板中使用Html.EditorForModel()方法 时,MVC在运行的时候也会在Views文件夹下的EditorTemplates文件夹中寻找名字与模型名相同的模板文件,如果找到则使用它来进行视图 的显示。这意味着我们可以通过修改该模板文件中的代码来定制任何类型的模型的页面显示结果。
接下来,我们在Views文件夹下的Shared文件夹中添加一个EditorTemplates文件夹,然后追加一个新的视图模板,如图5-9所示。
图5-9 在EditorTemplates文件夹中追加视图模板
这个视图与我们之前所创建的视图略有不同。它是一个分部视图,表示它的用途是用来被引用在其他视图中。在添加视图对话框中,将视图命名为Book,在视图模板下拉框中选择“Edit”,选取“创建分部视图复选框”然后点击追加按钮,如图5-10所示。
图5-10 添加分部视图
点击添加按钮,然后查看Visual Web Developer中自动创建的该视图文件(Book.cshtml文件)的代码,该文件中被默认追加了一个form标签,但是由于我们在其他视图中引用这个视图,而其他视图中已经追加了form标签,所以我们可以删除此处的form标签。代码如代码清单5-10所示(笔者已对其进行中文化处理)。
代码清单5-10 在EditorTemplates文件夹中追加的编辑模板中的代码
@model MvcBookStore.Models.Book
@Html.HiddenFor(model => model.Id)
<div class="editor-label">
种类编号
</div>
<div class="editor-field">
@Html.EditorFor(model =>model.GenreId)
@Html.ValidationMessageFor(model=> model.GenreId)
</div>
<div class="editor-label">
作者编号
</div>
<div class="editor-field">
@Html.EditorFor(model =>model.AuthorId)
@Html.ValidationMessageFor(model=> model.AuthorId)
</div>
<div class="editor-label">
书名
</div>
<div class="editor-field">
@Html.EditorFor(model =>model.Title)
@Html.ValidationMessageFor(model=> model.Title)
</div>
<div class="editor-label">
单价
</div>
<div class="editor-field">
@Html.EditorFor(model =>model.Price)
@Html.ValidationMessageFor(model=> model.Price)
</div>
运行应用程序,并且访问“/StoreManager/Edit/5”这个URL地址,可以看出我们在页面中使用了编辑书籍用表单,而书籍ID编号再次被隐藏起来,如图5-11所示。
图5-11 编辑模板的显示结果
接下来,让我们来看一下如何修改这个编辑书籍信息时所用的编辑模板。
5.6.6 使用ViewBag来将附加信息传入视图
接下来,让我们对这个编辑模板中的表单做出局部修改,使用下拉框来进行书籍与书籍种类的显示,而不是使用文本框来进行显示。为了能看出效果,需要向我们的视图中传入一些对象及其所带数据。此处,它需要如下对象:
1) 一个代表当前需要被编辑的书籍的Book对象
2) 一个代表所有书籍种类的Genres对象列表,我们使用这个列表来设置书籍种类下拉框中的内容。
3) 一个代表所有作者的Authors对象列表,我们使用这个列表来设置作者下拉框中的内容。
通过使用ViewBag对象,控制器可以来将这两个代表所有种类的种类列表与代表所有书籍的书籍列表传入视图中。
因此,我们将StoreManager控制器类中的Edit方法(第一个)中的代码修改为如下所示的代码。
public ActionResult Edit(int id)
{
ViewBag.Genres = storeDB.Genres.OrderBy(g=> g.Name).ToList();
ViewBag.Authors = storeDB.Authors.OrderBy(a => a.Name).ToList();
var book = storeDB.Books.Single(a => a.Id == id);
return View(book);
}
现在,我们的控制器已经将一本书的信息作为模型对象传入了编辑视图中,编辑视图使用编辑模板来显示这本书的信息。同时也将编辑模板中的书籍下拉框与书籍种 类下拉框中所需内容通过ViewBag对象传入了编辑模板中。现在,我们准备在作者编辑模板中写入书籍下拉框与书籍种类下拉框的页面代码。
5.6.7 在书籍编辑模板中使用下拉框
我们将使用HTML帮助器—Html.DropDownList来创建我们的下拉框。首先让我们来看一下需要向帮助器中传入什么参数。
- 模型中的属性名(AuthorId)
- 下拉框中的内容,使用SelectList对象来进行设置
- 用模型中的什么属性来设置每一个下拉框选项的value值时,需要传入该属性名,提交时用该属性来代表下拉框选项的value值
- 用模型中的什么属性来设置每一个下拉框选项的页面显示文字,需要传入该属性名
- 用模型中的什么属性来设置显示表单时下拉框的当前被选中的选项的value值,需要传入该属性名
调用Html.DropDownList帮助器的方法时所写代码类似如下所示。
@Html.DropDownList("AuthorId",new SelectList(ViewBag.Artists as
System.Collections.IEnumerable,"AuthorId", "Name", Model.AuthorId))
现在我们的完整的Book.cshtml编辑模板中的代码如代码清单5-11所示(笔者已做中文化处理)。
代码清单5-11 完整的Book.cshtml编辑模板中的代码
@model MvcBookStore.Models.Book
<p>
书籍种类
@Html.DropDownList("GenreId",new SelectList(ViewBag.Genres as
System.Collections.IEnumerable,"GenreId", "Name", Model.GenreId))
</p>
<p>
作者
@Html.DropDownList("AuthorId",new SelectList(ViewBag.Authors as
System.Collections.IEnumerable,"AuthorId", "Name", Model.AuthorId))
</p>
<p>
书名
@Html.TextBoxFor(model =>model.Title)
@Html.ValidationMessageFor(model=> model.Title)
</p>
<p>
单价
@Html.TextBoxFor(model =>model.Price)
@Html.ValidationMessageFor(model=> model.Price)
</p>
现在我们可以通过StoreManager控制器来编辑书籍信息,而页面中的作者与种类文本框也被下拉框所代替,如图5-12所示。
图5-12 使用下拉框的编辑模板
5.6.8 实现处理HTTP-POST请求的Edit方法
接下来,我们来看一下第二个场景,当管理员点击保存按钮,将表单值使用HTTP-POST请求提交给服务器,要求将这些值保存到数据库中。我们需要使用另外Edit方法,该方法使用一个ID参数与一个FormCollection对象参数(从HTML FORM读取)。我们使用“HttpPost”属性来对这个方法进行注解,标示该方法被调用在对于“/StoreManager/Edit/[id]”这个URL地址进行HTTP-POST的时候。
该方法中将要执行如下三步处理:
1.根据传入的ID编号读取数据库中该书籍的信息。
2.尝试使用客户端提交上来的表单值来对书籍进行更新操作,使用控制器中内置的UpdateModel方法。
3.将执行结果返回给用户,包括更新出错时重新显示编辑表单,或者更新成功后将页面转到书籍列表页面。
该方法中的代码如下所示。
//
// POST: /StoreManager/Edit/5
[HttpPost]
public ActionResult Edit(int id, FormCollection collection)
{
var book = storeDB.Books.Find(id);
if(TryUpdateModel(book))
{
storeDB.SaveChanges();
return RedirectToAction("Index");
}
else
{
ViewBag.Genres = storeDB.Genres.OrderBy(g => g.Name).ToList();
ViewBag.Authors = storeDB.Authors.OrderBy(a => a.Name).ToList();
return View(book);
}
}
我们使用了“HttpPost”属性,表示该方法只被调用在客户端发出HTTP-POST请求的时候。
现在我们可以对书籍进行编辑了。运行应用程序,输入“/StoreManager/Edit/5”这个URL地址,然后修改该书籍信息,如图5-13所示。
图5-13 修改书籍信息
修改完成后点击保存按钮,服务器端对该书籍进行更新,成功后页面转到书籍列表显示页面,如图5-14所示。
图5-14 书籍更新成功后页面转到书籍列表显示页面