一
引言
本文中我们将向你展示如何在基于ASPNET MVC框架构建的ASPNET应用程序中添加一些基本的Ajax特征(例如局部更新及行为组件等概念)
【说明】本文向你提供了有关于ASPNET MVC框架的完整应用源码及测试示例在本文方案中共有两个工程一个是TaskList(Web应用程序)另一个是AjaxMVC(一个提供了扩展的Ajax支持的类库)请注意类库AjaxMVC中提供的函数实现了一些基本的Ajax功能例如不依赖于页面回寄的局部更新以及关联到DOM元素的类似于ASPNET AJAX框架中行为(Behavior)的扩展实际上最新的ASPNET MVC框架版本(Preview )中就已经提供了现成的Ajax支持功能所以你可以把这里提供的功能作为早期ASPNET MVC框架版本的试验品学习
二构建简单任务列表示例程序
为了简化问题的表面而专注于讨论本文的主题本文中提供了一个基本的任务列表案例应用程序尽管此程序非常简单但是它却让我们专注于讨论我们更感兴趣的Ajax特征下面给出了本文示例应用程序的一个运行时刻快照
有关MVC框架的经典入门级教程请读者参考Scott Guthrie的博客/scottgu/archive////aspnetmvcframeworkpartaspx)我们不想在此重复这些内容但是就像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] = _taskDBGetTasks();
RenderView(List viewData);
}
//示例URL: /TaskList/Add
[ControllerAction]
public void Add(string name) {
Task newTask = null;
if (StringIsNullOrEmpty(name) == false) {
newTask = _taskDBAddTask(name);
}
if (newTask != null) {
RedirectToAction(List);
}
else {
Dictionary<string object> viewData = new Dictionary<string object>();
viewData[Tasks] = _taskDBGetTasks();
viewData[ShowAddTaskError] = true;
RenderView(List viewData);
}
}
//其他的Action方法例如DeleteTask CompleteTask
}
(三)视图
接下来让我们讨论本示例的视图部分本示例的视图页面是Listaspx定义于示例程序的/Views/TaskList文件夹下下面给出了一些令人感兴趣的标记代码
<div id=taskList>
<% foreach (Task task in Tasks) { %>
<div>
<div id=taskItem<%= taskID %> class=taskPanel>
<form method=post action=<% UrlAction(CompleteTask) %>>
<input type=hidden name=taskID value=<%= taskID %> />
<input type=submit name=completeTask value=Done! />
<input type=submit name=deleteTask value=Delete />
<span><%= HtmlEncode(taskName) %></span>
</form>
</div>
</div>
<% } %>
</div>
<form method=post action=<%= UrlAction(Add) %>>
<input type=text name=name />
<input type=submit name=addTask value=Add Task />
</form>
(四)单元测试
最后我们来看一下本示例中的单元测试部分的编码在本例中测试用例定义于一个单独的Test工程下面给出了本示例控制器中定义的Add方法相关的一组测试
public void TestAddEmptyName() {
TestTaskDB taskDB = new TestTaskDB();
taskDBAddTask(Test Task );
taskDBAddTask(Test Task );
TestTaskListController controller = new TestTaskListController(taskDB);
controllerAdd(null);
AssertAreEqual(List controllerRenderedView);
AssertAreEqual(true controllerGetRenderedViewData(ShowAddTaskError));
AssertAreEqual( taskDBCount);
}
[TestMethod]
public void TestAddValidName() {
TestTaskDB taskDB = new TestTaskDB();
taskDBAddTask(Test Task );
taskDBAddTask(Test Task );
TestTaskListController controller = new TestTaskListController(taskDB);
controllerAdd(New Task);
AssertAreEqual(List controllerRedirectedView);
AssertAreEqual( taskDBCount);
}
好至此我已经较完整地向你介绍了本文中编写的简单的TaskList应用程序接下来让我们讨论如何在这个示例中添加一些Ajax支持功能下面列出了我们计划要添加的一些功能
在任务列表中添加新任务(位于列表的最后)而不必进行完整的页面回送
编辑和删除任务同样不必进行完整的页面回送
在文本框中添加水印效果
为了实现上面的既定目标和最大限度地把新添加的功能与原有ASPNET MVC框架融合到一起我们使用了SystemWebMvc命名空间中新增加的一些类例如Ajax扩展方法等
三在控制器中加入AJAX支持技术
在此我们要做的第一件事情是从AjaxController类(而不是直接从Controller类)中派生类TaskListController
AjaxController是我刚刚添加的一个类此类引入了一个新的属性IsAjaxRequest我在自己的Action方法中就使用这个属性来完成诸如生成不同视图之类的任务此外它还引入了一些成员函数例如RenderPartial这个RenderPartial函数可以用于生成定义在一个部分视图中的用户接口的一部分下面给出修改后的控制器以及新修改的Add方法(其中修改部分及添加部分均以粗体显示)
public class TaskListController : AjaxController {
public void Add(string name) {
Task newTask = null;
if (StringIsNullOrEmpty(name) == false) {
newTask = _taskDBAddTask(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] = _taskDBGetTasks();
viewData[ShowAddTaskError] = true;
RenderView(List viewData);
}
}
}
}
接下来我把TaskView重新定义为一个自定义控件TaskViewascx(位于/Views/TaskList文件夹下)代码如下所示
<div id=taskItem<%= TaskID %> class=taskPanel>
<form method=post action=<%= UrlAction(CompleteTask) %>>
<input type=hidden name=taskID value=<%= TaskID %> />
<input type=submit name=completeTask value=Done! />
<input type=submit name=deleteTask value=Delete />
<span><%= HtmlEncode(taskName) %></span>
</form>
</div>
其实上面的代码仅仅是前面介绍的Listaspx的简单重构(所以你可能看上去十分熟悉这段代码)相应于这个用户控件的ViewData是一个Task类的实例现在既然我们已经定义了这一部分那么接下来我们就可以从本例主要的视图页面Listaspx中使用它了这一点是借助于前面我提供的RenderPartial扩展方法实现的一旦做到这些视图Listaspx的任务列表部分将变为
<div id=taskList>
<% foreach (Task task in Tasks) { %>
<div>
<% thisRenderPartial(TaskView task); %>
</div>
<% } %>
</div>
接下来我需要让此视图发出XMLHttp请求而不是一个传统的表单提交再次我提供了一些扩展方法
⑴RenderBeginForm描述的是一个普通的表单标签
⑵RenderBeginAjaxForm将负责生成一个支持AJAX功能的表单(这正是我们的兴趣点所在)
⑶RenderEndForm
借助于这些方法实现添加任务的UI表单标签部分看上去如下加粗部分所示
<% RenderBeginAjaxForm(UrlAction(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此参数取值可以为nonereplacereplaceContentinsertTop或者appendBottom—在上面的例子中我们给它的赋值是最后面的值appendBottom此值将使得新渲染生成的任务显示于整个任务列表的底部
; Highlight此参数是可选的当设置此参数时新添加的项将会高亮显示一会儿呈现微微带点黄色的渐隐效果
; Starting和Completed这两个参数实质上都是事件我们可以编写一段Javascript代码实现例如禁用按钮显示进度指示器 在发出的请求中添加额外内容或预处理到来的响应等等
下面是Javascript代码(位于文件TaskListjs中此文件位于示例程序的/Views/Scripts文件夹下)
function startAddTask() {
$(addTaskGroup)disabled = true;
return true;
}
function endAddTask() {
$(addTaskGroup)disabled = false;
return true;
}
在上面的startAddTask方法中我们进行了校验操作在此请注意如果相应的形式无效那么为了避免在这样情况下也发出请求需要返回false注意这里的代码仅仅展示了一些基本形式的校验编码
最后一步是添加进脚本TaskList以及提供相应核心功能的脚本框架现在我们来打开位于文件夹/Views/Layouts下的示例程序的母版页面然后添加一些指令以初始化Ajax功能注册脚本并在最终生成的HTML代码的最后输出脚本这是通过调用我添加到Ajax对象中的扩展方法实现的
<% AjaxInitialize(); %>
<% AjaxRegisterScript(~/Views/Scripts/TaskListjs); %>
<!—UI部分定义在此>
<% AjaxRenderScripts(); %>
实际上我要实现的另一项任务就是添加一个测试用例于是我添加一个测试用例用于测试我的控制器中Ajax化的Action方法Add
接下来我们可以针对完成和删除两个任务添加同样相似的测试用例其中完成任务将导致相应于此任务的UI重新生成使用新的HTML代替现有的HTML而相比之下删除任务更为有趣些不是更新HTML而是原有内容从DOM结构中移除改以使用HighlightLeave 效果(一种红色渐隐效果)造成视觉上更为引人注目你可以进一步分析本示例源码来了解其中的原理(特别是文件TaskListjs和TaskViewascx以及相关联的控制器中的Action方法)
四添加其他的AJAX技术
我们完全可以实现类似于包含在TaskViewascx中的<form>部分而且我们同样可以其中描述每一个任务项但是却能够把一个常规的基于提交的表单转换成一个支持AJAX技术的表单这样以来任务项的编辑与删除操作就可以在局部刷新状态下实现示例代码中对此作了解释在此不再赘述
接下来我想介绍的是如何添加一些脚本并把它添加到我们的示例程序的UI中创建其他基于AJAX的交互而生成的HTML具体地说我想在文本框中添加一个水印效果此效果为用户输入提供了极为友好的用户直观性提示只要没有用户输入此水印效果就会显示出来而当用户把输入焦点定位于文本框中时即水印效果消失
当然篇幅所限我们也不会过于细致地去讨论脚本本身有关此脚本详细内容请参考本文源码但是需要指出的是这个水印效果被实现为大家可能熟悉的ASPNET AJAX框架的一个客户端行为(Behavior)组件就像任何其他行为组件一样我们的示例中所使用的文本框也是与DOM元素相关联而且它实现了对此元素引发的相关事件的订阅
在传统的web表单页面中我经常会直接使用支持AJAX功能的服务器控件例如WatermarkExtender并使之关联到一个服务器控件但是在本例中我使用了另一种扩展方法来实现渲染效果通过此方法我也可以实现创建并初始化脚本行为组件的一个实例下面给出了我更新以后的视图关键部分的代码片断
<% RenderBeginAjaxForm(UrlAction(Add)
new { Update=taskList UpdateType=appendBottom
Highlight=True
Starting=startAddTask Completed=endAddTask }); %>
<input type=text name=name id=nameTextBox />
<% AjaxWatermark(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) {
ajaxHelperRegisterScript(~/Views/Scripts/Watermarkjs);
ajaxHelperRegisterScriptBehavior(id AjaxWatermark watermarkOptions);
}
}
当然我们还可以更细致地控制上面的编码但这里仅展示了提供搜集注册的脚本功能核心部分的代码片断以及把它们生成到页面中然后实例化行为对象并使其与相应的DOM元素建立关联以及传递进视图提供的选择以便定制具体的实例
五结论
归纳来看本文也只不过是蹭了蹭基于MVC框架进行ASPNET页面编程中所涉及的局部更新行为和扩展器控件等Ajax功能的核心方面我相信除了上面这两种情况外还有大量的其他内容需要进行Ajax化(校验同期性刷新通过脚本代理以及web服务等技术进一步简化调用控制器方法等)最后读者可以详细研读我提供的示例代码并给予相应的改进