【译】MVC3 20个秘方-(13)使用Ajax Helper 提高用户体验
问题
当你点击链接时,整个的网页都被重新加载。尤其是你仅仅一小点内容需要被更新时,这将被感觉是一个很慢的过程。
解决方案
更新之前创建的HTML.ActionLink 去调用ajax 帮助类。Ajax.ActionLink 仅仅去重新加载那些发生变化的内容。
讨论
MVC提供了几个给力的帮助类。到目前为止,这本书中已经广泛的应用了HTML Helper。在过去创建的所有view中,HTML helper至少都使用过一次。在这个秘方中,我们将使用AjaxHelper类替换掉Book/Index中的HtmlHelper 类。
实现Ajax需要一点额外的设置才可以使用。通常情况下我发现这个额外的工作,可以打消开发人员使用它的念头。我要让大家知道,额外的安装设置是值得的,因为带来的好处是获得了更好的用户体验,这是非常值得努力去做的。
步骤从web.config开始。2个关键的地方要被设置成true. ClientValidationEnabled 和UnobtrusiveJavaScriptEnabled。
译者:原书中代码引入了整个web.config文件。我们只需要到appSettings节点下即可找到这两个keys。
<appSettings>
<add key="webpages:Version" value="1.0.0.0" />
<add key="ClientValidationEnabled" value="true" />
<add key="UnobtrusiveJavaScriptEnabled" value="true" />
</appSettings>
接下来的步骤是我们要引入几个javascript 文件。我们要在里shared 的layout文件夹里完成这件事,因为几乎我们创建所有的view时都会引用它(布局模板)。在Views/Shared/_Layout.cshtml 文件的<head>标签中。我们引入2个javascript 文件,代码如下:
<head>
<title>@ViewBag.Title</title>
<link href="@Url.Content("~/Content/Site.css")" rel="stylesheet" type="text/css" />
<script src="@Url.Content("~/Scripts/jquery-1.5.1.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.unobtrusive-ajax.min.js")"type="text/javascript"></script>
</head>
这些文件被自动的包含在基础MVC3应用程序中。(译者:你可以从scripts文件夹中找到他们)。以上的步骤完成AJAX的核心配置。接下来,我们要更新Book/index view。在下边的例子里,三个filter link和sortable header link将用Ajax.ActionLink 替换Html.ActionLink.代码如下:
@model PagedList.IPagedList<MvcApplication.Models.Book>
@if (IsAjax)
{
Layout = null;
}
<h2>
Title</h2>
<p>
@Html.ActionLink("Create New", "Create")
</p>
<p>
Show:
@if (ViewBag.CurrentFilter != "")
{
@Ajax.ActionLink("All", "Index",
new
{
sortOrder = ViewBag.CurrentSortOrder,
Keyword = ViewBag.CurrentKeyword
},
new AjaxOptions { UpdateTargetId = "main" })
}
else
{
@:All
}
|
@if (ViewBag.CurrentFilter != "NewReleases")
{
@Ajax.ActionLink("New Releases", "Index", new
{
filter = "NewReleases",
sortOrder = ViewBag.CurrentSortOrder,
Keyword = ViewBag.CurrentKeyword
},
new AjaxOptions { UpdateTargetId = "main" })
}
else
{
@:New Releases
}
|
@if (ViewBag.CurrentFilter != "ComingSoon")
{
@Ajax.ActionLink("Coming Soon", "Index", new
{
filter = "ComingSoon",
sortOrder = ViewBag.CurrentSortOrder,
Keyword = ViewBag.CurrentKeyword
},
new AjaxOptions { UpdateTargetId = "main" })
}
else
{
@:Coming Soon
}
</p>
@using (Html.BeginForm())
{
@:Search: @Html.TextBox("Keyword")<input type="submit" value="Search" />
}
@Html.Partial("_Paging")
<table>
<tr>
<th>
@Ajax.ActionLink("Title", "Index", new
{
sortOrder = ViewBag.TitleSortParam,
filter = ViewBag.CurrentFilter,
Keyword = ViewBag.CurrentKeyword
},
new AjaxOptions { UpdateTargetId = "main" })
</th>
<th>
@Ajax.ActionLink("Isbn", "Index", new
{
sortOrder = ViewBag.IsbnSortParam,
filter = ViewBag.CurrentFilter,
Keyword = ViewBag.CurrentKeyword
},
new AjaxOptions { UpdateTargetId = "main" })
</th>
<th>
Summary
</th>
<th>
@Ajax.ActionLink("Author", "Index", new
{
sortOrder = ViewBag.AuthorSortParam,
filter = ViewBag.CurrentFilter,
Keyword = ViewBag.CurrentKeyword
},
new AjaxOptions { UpdateTargetId = "main" })
</th>
<th>
Thumbnail
</th>
<th>
@Ajax.ActionLink("Price", "Index", new
{
sortOrder = ViewBag.PriceSortParam,
filter = ViewBag.CurrentFilter,
Keyword = ViewBag.CurrentKeyword
},
new AjaxOptions { UpdateTargetId = "main" })
</th>
<th>
@Ajax.ActionLink("Published", "Index", new
{
sortOrder = ViewBag.PublishedSortParam,
filter = ViewBag.CurrentFilter,
Keyword = ViewBag.CurrentKeyword
},
new AjaxOptions { UpdateTargetId = "main" })
</th>
<th>
</th>
</tr>
@foreach (var item in Model)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.Isbn)
</td>
<td>
@Html.DisplayFor(modelItem => item.Summary)
</td>
<td>
@Html.DisplayFor(modelItem => item.Author)
</td>
<td>
@Html.DisplayFor(modelItem => item.Thumbnail)
</td>
<td>
@Html.DisplayFor(modelItem => item.Price)
</td>
<td>
@Html.DisplayFor(modelItem => item.Published)
</td>
<td>
@Html.ActionLink("Edit","Edit", new { id = item.ID }) |
@Html.ActionLink("Details","Details", new { id = item.ID }) |
@Html.ActionLink("Delete","Delete", new { id = item.ID })
</td>
</tr>
}
</table>
@Html.Partial("_Paging")
译者:上边代码标绿的地方就是我们更新的地方。
我们做的关键的事就是在ActionLink的最后一个参数添加了AjaxOptions。这意味着一旦用户点击了这个AJAX link,AJAX请求的结果将会更新id 是“main”的html元素。如果你看看share文件夹中早期我们修改的layout文件,你会注意到有一个<div>的id 是“main”。实际上,这个div就是@Renderbody()的容器,它是一个view输出的地方。
另外一个重要的事就是我们在view的顶端加了一个AJAX是否完成的检测。如果请求通过AJAX完成,layout设置成null。这是一个非常重要的因素,因为如果它没完成,Ajax请求的结果将会包含不仅仅是view的result,也会包含整个layout,而没必要再去替换掉layout了。
为了完成这个示例,share文件夹下的_Paging也要使用Ajax helper更新,代码如下:
<p>
@if (Model.HasPreviousPage)
{
@Ajax.ActionLink("<< First", "Index", new
{
page = 1,
sortOrder = ViewBag.CurrentSortOrder,
filter = ViewBag.CurrentFilter
},
new AjaxOptions { UpdateTargetId ="main" })
@Html.Raw(" ");
@Html.ActionLink("< Prev", "Index", new
{
page = Model.PageNumber - 1,
sortOrder = ViewBag.CurrentSortOrder,
filter = ViewBag.CurrentFilter
}, new AjaxOptions { UpdateTargetId ="main" })
}
else
{
@:<< First
@Html.Raw(" ");
@:< Prev
}
@if (Model.HasNextPage)
{
@Ajax.ActionLink("Next >", "Index", new
{
page = Model.PageNumber + 1,
sortOrder = ViewBag.CurrentSortOrder,
filter = ViewBag.CurrentFilter
}, new AjaxOptions { UpdateTargetId ="main" })
@Html.Raw(" ");
@Ajax.ActionLink("Last >>", "Index", new
{
page = Model.PageCount,
sortOrder = ViewBag.CurrentSortOrder,
filter = ViewBag.CurrentFilter
}, new AjaxOptions { UpdateTargetId ="main" })
}
else
{
@:Next >
@Html.Raw(" ")
@:Last >>
}
</p>
现在,当用户点击一个link 改变book 列表时,整个的页面不会被重新载入并且仅仅是图书列表被更新。这提供了一个更好、更快的用户体验。
而且,如果客户端不支持javascript(例如,一个搜索引擎访问),这个link将像以前一样工作。它将重新载入整个页面。
另请参见