技术开发 频道

为ASP.NET MVC框架添加AJAX支持

IT168 专稿

一、引言

   本文中,我们将向你展示如何在基于ASP.NET MVC框架构建的ASP.NET应用程序中添加一些基本的Ajax特征(例如局部更新及行为组件等概念)。

   【说明】本文向你提供了有关于ASP.NET MVC框架的完整应用源码及测试示例。在本文方案中,共有两个工程:一个是TaskList(Web应用程序),另一个是AjaxMVC(一个提供了扩展的Ajax支持的类库)。请注意,类库AjaxMVC中提供的函数实现了一些基本的Ajax功能,例如不依赖于页面回寄的局部更新以及关联到DOM元素的类似于ASP.NET AJAX框架中行为(Behavior)的扩展。实际上,最新的ASP.NET MVC框架版本(Preview 4)中就已经提供了现成的Ajax支持功能。所以,你可以把这里提供的功能作为早期ASP.NET MVC框架版本的试验品学习。

二、构建简单任务列表示例程序

   为了简化问题的表面而专注于讨论本文的主题,本文中提供了一个基本的任务列表案例应用程序。尽管此程序非常简单,但是它却让我们专注于讨论我们更感兴趣的Ajax特征。下面给出了本文示例应用程序的一个运行时刻快照。


 

   有关MVC框架的经典入门级教程,请读者参考Scott Guthrie的博客(http://weblogs.asp.net/scottgu/archive/2007/11/13/asp-net-mvc-framework-part-1.aspx)。我们不想在此重复这些内容,但是就像Scott Guthrie提供的产品目录应用程序一样,本文中提供的这个TaskList应用程序使用一个控制器来处理来自客户端的请求,使用一组类形成模型,用于描述一个任务项的集合,还有一组视图用于生成用户接口。

   首先,在开始为示例添加一些Ajax功能之前,我们来分析本文示例应用程序的基本组成。

(一)模型

我们先来分析一下本示例的模型部分,主要由下面两部分组成:
 

public class Task {
public int ID { get; }
public bool IsComplete { get; set; }
public string Name { get; set; }
}
public interface ITaskDB {
Task AddTask(string name);
void CompleteTask(Task task);
Task GetTask(int taskID);
ICollection<Task> GetTasks();
void RemoveTask(Task task);
}


   这里的代码没有多少意思。但要注意的是,在本例中,为了简化问题起见,我直接使用了一个内存中的数据集来模拟任务列表数据(而没有使用数据库示例)。

   另外,需要一提的是,这里使用了接口(ITaskDB)的方法,以方便添加测试代码(后面讨论)。
 

 

(二)控制器

   本示例中提供了一个简单的控制器TaskListController。控制器中定义了几个Action方法。下面列出了其中的两个方法:
 

public class TaskListController : Controller {
private ITaskDB _taskDB;
public TaskListController() : this(GlobalTaskDB) { }
public TaskListController(ITaskDB taskDB)
{
_taskDB = taskDB;
}

//示例URL: /TaskList或者/TaskList/List
[ControllerAction]
public void List() {
Dictionary<string, object> viewData = new Dictionary<string, object>();
viewData["Tasks"] = _taskDB.GetTasks();

RenderView("List", viewData);
}

//示例URL: /TaskList/Add
[ControllerAction]
public void Add(string name) {
Task newTask = null;
if (String.IsNullOrEmpty(name) == false) {
newTask = _taskDB.AddTask(name);
}

if (newTask != null) {
RedirectToAction("List");
}
else {
Dictionary<string, object> viewData = new Dictionary<string, object>();
viewData["Tasks"] = _taskDB.GetTasks();
viewData["ShowAddTaskError"] = true;
RenderView("List", viewData);
}
}

//其他的Action方法:例如DeleteTask, CompleteTask
}

 
(三)视图

   接下来,让我们讨论本示例的视图部分。本示例的视图页面是List.aspx,定义于示例程序的/Views/TaskList文件夹下。下面给出了一些令人感兴趣的标记代码:
 

<div id="taskList">
<% foreach (Task task in Tasks) { %>
<div>
<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>
</div>
<% } %>
</div>

<form method="post" action='<%= Url.Action("Add") %>'>
<input type="text" name="name" />
<input type="submit" name="addTask" value="Add Task" />
</form>

 
(四)单元测试

   最后,我们来看一下本示例中的单元测试部分的编码。在本例中,测试用例定义于一个单独的Test工程。下面给出了本示例控制器中定义的Add方法相关的一组测试:
 

public void TestAddEmptyName() {
TestTaskDB taskDB = new TestTaskDB();
taskDB.AddTask("Test Task 1");
taskDB.AddTask("Test Task 2");

TestTaskListController controller = new TestTaskListController(taskDB);

controller.Add(null);

Assert.AreEqual("List", controller.RenderedView);
Assert.AreEqual(true, controller.GetRenderedViewData("ShowAddTaskError"));
Assert.AreEqual(2, taskDB.Count);
}

[TestMethod]
public void TestAddValidName() {
TestTaskDB taskDB = new TestTaskDB();
taskDB.AddTask("Test Task 1");
taskDB.AddTask("Test Task 2");

TestTaskListController controller = new TestTaskListController(taskDB);

controller.Add("New Task");

Assert.AreEqual("List", controller.RedirectedView);
Assert.AreEqual(3, taskDB.Count);
}

 
   好,至此我已经较完整地向你介绍了本文中编写的简单的TaskList应用程序。接下来,让我们讨论如何在这个示例中添加一些Ajax支持功能。下面列出了我们计划要添加的一些功能:

   1. 在任务列表中添加新任务(位于列表的最后)而不必进行完整的页面回送;
   2. 编辑和删除任务,同样不必进行完整的页面回送;
   3. 在文本框中添加水印效果。

   为了实现上面的既定目标和最大限度地把新添加的功能与原有ASP.NET MVC框架融合到一起,我们使用了System.Web.Mvc命名空间中新增加的一些类,例如Ajax扩展方法等。
 

三、在控制器中加入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服务等技术进一步简化调用控制器方法等)。最后,读者可以详细研读我提供的示例代码并给予相应的改进。
 

0
相关文章