三、在控制器中加入AJAX支持技术
在此,我们要做的第一件事情是从AjaxController类(而不是直接从Controller类)中派生类TaskListController。
AjaxController是我刚刚添加的一个类,此类引入了一个新的属性IsAjaxRequest。我在自己的Action方法中就使用这个属性来完成诸如生成不同视图之类的任务。此外,它还引入了一些成员函数,例如RenderPartial。这个RenderPartial函数可以用于生成定义在一个部分视图中的用户接口的一部分。下面给出修改后的控制器以及新修改的Add方法(其中修改部分及添加部分均以粗体显示):
public class TaskListController : AjaxController {
public void Add(string name) {
Task newTask = null;
if (String.IsNullOrEmpty(name) == false) {
newTask = _taskDB.AddTask(name);
}
if (IsAjaxRequest) {
if (newTask != null) {
RenderPartial("TaskView", newTask);
}
}
else {
if (newTask != null) {
RedirectToAction("List");
}
else {
Dictionary<string, object> viewData = new Dictionary<string, object>();
viewData["Tasks"] = _taskDB.GetTasks();
viewData["ShowAddTaskError"] = true;
RenderView("List", viewData);
}
}
}
}
接下来,我把TaskView重新定义为一个自定义控件TaskView.ascx(位于/Views/TaskList文件夹下),代码如下所示:
<div id="taskItem<%= Task.ID %>" class="taskPanel">
<form method="post" action='<%= Url.Action("CompleteTask") %>'>
<input type="hidden" name="taskID" value="<%= Task.ID %>" />
<input type="submit" name="completeTask" value="Done!" />
<input type="submit" name="deleteTask" value="Delete" />
<span><%= Html.Encode(task.Name) %></span>
</form>
</div>
其实,上面的代码仅仅是前面介绍的List.aspx的简单重构(所以,你可能看上去十分熟悉这段代码)。相应于这个用户控件的ViewData是一个Task类的实例。现在,既然我们已经定义了这一部分,那么接下来我们就可以从本例主要的视图页面List.aspx中使用它了。这一点是借助于前面我提供的RenderPartial扩展方法实现的。一旦做到这些,视图List.aspx的任务列表部分将变为:
<div id="taskList">
<% foreach (Task task in Tasks) { %>
<div>
<% this.RenderPartial("TaskView", task); %>
</div>
<% } %>
</div>
接下来,我需要让此视图发出XMLHttp请求而不是一个传统的表单提交。再次,我提供了一些扩展方法:
⑴RenderBeginForm描述的是一个普通的表单标签;
⑵RenderBeginAjaxForm将负责生成一个支持AJAX功能的表单(这正是我们的兴趣点所在);
⑶RenderEndForm。
借助于这些方法,实现添加任务的UI表单标签部分看上去如下加粗部分所示:
<% RenderBeginAjaxForm(Url.Action("Add"),
new { Update="taskList, UpdateType="appendBottom",
Highlight="True",
Starting="startAddTask", Completed="endAddTask" }); %>
<input type="text" name="name" />
<input type="submit" name="addTask" value="Add Task" />
<% RenderEndForm(); %>
如你所见,表单的内容并没有发生变化,仅仅是声明的形式发生了变化。在上面的代码中,RenderBeginAjaxForm接收当提交表单时描述要调用的行为的URL,后面跟着的是如下的一些Ajax特定参数:
? Update:此参数相应于使用结果进行更新的DOM 元素的id值。在本例中,它对应于描述存放所有任务项的容器。
? UpdateType:此参数取值可以为“none”,“replace”,“replaceContent”,“insertTop”,或者“appendBottom”—在上面的例子中,我们给它的赋值是最后面的值“appendBottom”,此值将使得新渲染生成的任务显示于整个任务列表的底部。
? Highlight:此参数是可选的。当设置此参数时,新添加的项将会高亮显示一会儿,呈现微微带点黄色的渐隐效果。
? Starting和Completed:这两个参数实质上都是事件。我们可以编写一段Javascript代码实现例如禁用按钮,显示进度指示器, 在发出的请求中添加额外内容或预处理到来的响应,等等
下面是Javascript代码(位于文件TaskList.js中,此文件位于示例程序的/Views/Scripts文件夹下)。
function startAddTask() {
$('addTaskGroup').disabled = true;
return true;
}
function endAddTask() {
$('addTaskGroup').disabled = false;
return true;
}
在上面的startAddTask方法中,我们进行了校验操作。在此,请注意如果相应的形式无效,那么为了避免在这样情况下也发出请求需要返回false。注意,这里的代码仅仅展示了一些基本形式的校验编码。
最后一步是添加进脚本TaskList以及提供相应核心功能的脚本框架。现在,我们来打开位于文件夹/Views/Layouts下的示例程序的母版页面,然后添加一些指令以初始化Ajax功能、注册脚本并在最终生成的HTML代码的最后输出脚本。这是通过调用我添加到Ajax对象中的扩展方法实现的:
<% Ajax.Initialize(); %>
<% Ajax.RegisterScript("~/Views/Scripts/TaskList.js"); %>
<!—UI部分定义在此-->
<% Ajax.RenderScripts(); %>
实际上,我要实现的另一项任务就是添加一个测试用例。于是,我添加一个测试用例用于测试我的控制器中Ajax化的Action方法Add。
接下来,我们可以针对完成和删除两个任务添加同样相似的测试用例。其中,完成任务将导致相应于此任务的UI重新生成,使用新的HTML代替现有的HTML。而相比之下,删除任务更为有趣些:不是更新HTML,而是原有内容从DOM结构中移除,改以使用HighlightLeave 效果(一种红色渐隐效果)造成视觉上更为引人注目。你可以进一步分析本示例源码来了解其中的原理(特别是文件TaskList.js和TaskView.ascx,以及相关联的控制器中的Action方法)。
四、添加其他的AJAX技术
我们完全可以实现类似于包含在TaskView.ascx中的<form>部分。而且,我们同样可以其中描述每一个任务项,但是却能够把一个常规的基于提交的表单转换成一个支持AJAX技术的表单。这样以来,任务项的编辑与删除操作就可以在局部刷新状态下实现。示例代码中对此作了解释,在此不再赘述。
接下来,我想介绍的是如何添加一些脚本并把它添加到我们的示例程序的UI中创建其他基于AJAX的交互而生成的HTML。具体地说,我想在文本框中添加一个水印效果,此效果为用户输入提供了极为友好的用户直观性提示。只要没有用户输入,此水印效果就会显示出来,而当用户把输入焦点定位于文本框中时即水印效果消失。
当然,篇幅所限,我们也不会过于细致地去讨论脚本本身。有关此脚本详细内容,请参考本文源码,但是需要指出的是这个水印效果被实现为大家可能熟悉的ASP.NET AJAX框架的一个客户端行为(Behavior)组件。就像任何其他行为组件一样,我们的示例中所使用的文本框也是与DOM元素相关联,而且它实现了对此元素引发的相关事件的订阅。
在传统的web表单页面中,我经常会直接使用支持AJAX功能的服务器控件,例如WatermarkExtender,并使之关联到一个服务器控件。但是,在本例中,我使用了另一种扩展方法来实现渲染效果。通过此方法,我也可以实现创建并初始化脚本行为组件的一个实例。下面给出了我更新以后的视图关键部分的代码片断:
<% RenderBeginAjaxForm(Url.Action("Add"),
new { Update="taskList, UpdateType="appendBottom",
Highlight="True",
Starting="startAddTask", Completed="endAddTask" }); %>
<input type="text" name="name" id="nameTextBox" />
<% Ajax.Watermark("nameTextBox",
new { watermarkText="[What do you need to do?]",
watermarkCssClass="watermark"}); %>
<input type="submit" name="addTask" value="Add Task" />
<% RenderEndForm(); %>
上面的扩展方法实现相当简单。其实,它也就是调用了现成的Ajax框架。下面是我定义的WatermarkBehavior类相应的代码:
public static class WatermarkBehavior {
public static void Watermark(this AjaxHelper ajaxHelper, string id, object watermarkOptions) {
ajaxHelper.RegisterScript("~/Views/Scripts/Watermark.js");
ajaxHelper.RegisterScriptBehavior(id, "Ajax.Watermark", watermarkOptions);
}
}
当然,我们还可以更细致地控制上面的编码,但这里仅展示了提供搜集注册的脚本功能核心部分的代码片断,以及把它们生成到页面中,然后实例化行为对象,并使其与相应的DOM元素建立关联,以及传递进视图提供的选择以便定制具体的实例。
五、结论
归纳来看,本文也只不过是蹭了蹭基于MVC框架进行ASP.NET页面编程中所涉及的局部更新,行为和扩展器控件等Ajax功能的核心方面。我相信,除了上面这两种情况外还有大量的其他内容需要进行Ajax化(校验,同期性刷新,通过脚本代理以及web服务等技术进一步简化调用控制器方法等)。最后,读者可以详细研读我提供的示例代码并给予相应的改进