ASP.NET MVC + ADO.NET EF 项目实战(三):引入jQuery

jQuery是一个重要的客户端框架,ASP.NET MVC默认的项目模板中就带了这个框架。掌握这个框架对于更好地编写ASP.NET MVC应用是非常重要的。事实上,网上有很多文章讲述如何在ASP.NET MVC项目中使用jQuery。例如以下文章就是讲关于jqGrid的:

Using jQuery Grid With ASP.NET MVC

Using jqGrid with ASP.NET MVC

另外,在CodePlex上就有很多项目帮助你更方便地应用jQuery,例如:

jQuery UI Extensions for ASP.NET MVC
jQuery Grid for ASP.NET MVC

但是,要更加方便地使用jQuery,仅仅知道以上还是不够的。

一:了解ASP.NET MVC的局限性

ASP.NET MVC有几处是我不太喜欢的,但是似乎也没有太好的解决方案。

第一件事情是冗余的Action。在一个Controller中每个Action的地位是平等的。但是很遗憾,一个带Form的View总是需要两个Action,一个用来GET;另一个用来POST。而我觉得这两个Action是不平等的。用于Post的Action是依赖用于Get的Action的。我告诉你,这两个Action其实是可以压缩成一个的。另一个完全可以放到System.Web.Mvc中,大家公用,通过传递一个或多个delegate来处理。也就是说,形式上是一个,实际上仍然是两个。说这个问题的意图是想说明,我们可以把一个Ajax服务通过传递一个delegate来实现单入口。

第二件事情是Script的Render。我希望任何TagBuilder都可以引用自己的Script,在Html的Head段最后写入。可惜WebFormViewEngine的Render过程中按次序进行的。这意味着,直到整个页面写入Response你都没有机会去更正对Script的引用。其实我以前写过关于ASP.NET MVC中Script的管理,不过实话说非常丑陋。其实我也可以告诉你,这个是可以实现的。在View中可以定义一个特殊的标签,当遇到这个标签的时候,将已经生成的Html写入缓存,清空HtmlWriter,再Render剩余的部分;最后再Render Script部分,最后交这三者合并,最后写入Response。说这个问题的意图是将客户端复杂化以后给应用带来的困难暴露出来。

任何一件事情,如果单独在服务端完成,这个比较好处理。如果单独在客户端完成,这个也好处理。如果需要客户端和服务端配合,就会弄出一些麻烦来。如果你要在一个页面中显示一个jqGrid,你一共至少需要做四件事情:

1.在你的View中加入相关的引用,包括JavaScript和CSS;包括jQuery、jQuery UI、jqGrid;包括相关的主题。

2.在你的View中适当的地方添加一个table标签,并加上id,用于表格的容器;再添加一个div标签,也加上id,用于分页器的容器。

3.加入相关的脚本,将网格与给定的标签进行绑定。其中需要定义网格的列信息。

4.加入一个数据获取的Action,或者XML格式数据或者JSON格式数据。

对于第1件事情,似乎不算麻烦。如果不是过于挑剔的话甚至没有什么问题。

对于第2件事情和第3件事情,就会有冲突了。可以假设这两件事情是两个人做的,但是他们必须确保他们所使用的id是一样的。一旦不一致,到底算谁的责任?

对于第4件事情,就更麻烦了。因为那已经是服务端的事情了。不仅获取数据的Url要正确,连Column表和数据表也必须一致。一旦不一致,到底算谁的责任?

通常对于要求一致的事情,由一个人做比较好。所以,无比聪明的程序员会通过一个 HtmlHelper来根据单一的定义统一生成。这的确是一个好的思路。所以,使用jQuery是需要进行专门包装的。

二:封装jQuery及其插件

如果要一个基于jQuery的Total Solution,还真有比较大的困难。jQuery本身的功能是相当有限的,其丰富的功能要依赖一大堆的插件。几乎每个插件要用在ASP.NET MVC中都需要包装一下。好在jQuery插件有一些约定,所以封装起来相对比较简单。在封装的时候有一点通常容易被忽略的是:作为独立于应用的封装模块中是可以带自己的Controller的。举个例子来说:如果你封装了一个jQuery UI的主题管理器,主题管理的ModalDialog的Action,包括Get和Post你都是可以包装在一个单独的项目中的。如果你的控制器叫 jQueryController,两个Action分别是ShowThemes和SetTheme,那你可以通过以下代码来实现:

        var currTheme = "";
        $(document).ready(function() {
            var doClick = function() {
                $("#themeGallery td img.currentTheme").removeClass();
                this.setAttribute("class", "currentTheme");
                currTheme = this.getAttribute("title");
            }
            var doOK = function() {
                $.post('/jQuery/SetTheme', { theme: currTheme }, function(data) {
                    $("#themeDialog").dialog("close");
                    eval(data);
                });
                return false;
            }
            var doCancel = function() {
                $("#themeDialog").dialog("close");
            }
            $("#themeDialog").load("/jQuery/ShowThemes");
            $("#ThemeButton").click(function() {
                $("#themeGallery td img").click(doClick);
                var dialogOpts = {
                    modal: true,
                    width: "662px",
                    height: "420px",
                    resizable: false,
                    buttons: { "OK": doOK, "Cancel": doCancel }
                };
                $("#themeDialog").dialog(dialogOpts);
            });
        });

这样,在任何需要设置主题的地方放一个<a>元素,id定义为ThemeButton即可。基于同样的原理,我们在封装jqGrid的时候,可以使用如下定义的委托:

这样,单一的入口就是:

代码
public ActionResult Employees()
{
var model
= new GridModel
{
Caption
= "Employees",
Loader
= (ctx, page, rows, sidx, sord) =>
{
...... 实现数据访问
return new GridDataModel
{
Total
= q0.Count(),
Page
= page,
Records
= q.Count(),
Rows
= q.ToArray()
};
}
};
model.Columns.Add(
new ColumnModel
{
Name
= "Id",
Width
= "80px",
Align
= ColumnAlign.Right,
Caption
= "Id",
Index
= "Id",
IsKey
= true,
Sortable
= true,
Hidden
= true
});
...... 加入其他列
return View(model);
}

 

这样,在View中使用的扩展,生成的JavaScript代码中,Url是由应用无关的模块提供的,以View名称+View中Table的id为标识,存贮在服务端。获取数据时提交该标识,找出数据加载器,从而实现异步数据加载。

jQuery的插件,除了jqGrid是必须封装的以外,jQuery UI也是必须要封装的。虽然很多人包括我在内,对jQuery UI有诸多不满,主要的不满都是基于功能,项目进度严重拖后于Roadmap上的承诺。另外,其代码质量也不如jQuery。不过,毕竟jQuery UI是jQuery官方的产品,受到很多插件依赖,所以,暂时没有可替代的。

此外,还有以下插件需要封装:

  • 带CheckBox的TreeView;
  • 导航Menu和上下文菜单;
  • 封装Google Maps API的gMap;
  • tinyMCE的jQuery插件;
  • 用于布局的Panel插件;
  • 动态Form插件。

三:如何避免拼凑脚本

在ASP.NET MVC中需要经常拼凑脚本,这一点也非常令人讨厌。讨厌的并不是“拼凑脚本”,而是“经常”。需要“经常”拼凑脚本的原因是服务端的内容是动态输出到客户端的。如果通过某种固定的机制,智能地将服务端的单一代码自动生成为客户端内容那将是非常令人高兴的事情。换句话说,你在服务端直接写C#代码,然后有一个专门的工具将这些代码翻译成客户端代码。

我研究过JavaScript#,发现这条路完全行不通。首先,要么翻译源码、要么翻译编译后的代码,但必须是整个文件地翻译,而不能是某个代码段。其次,JavaScript#必须依赖一整套程序集,而这些程序集是特有的。

我建立了一个新的项目,叫jQuery#,基于一套专门的库,然后在运行时以较小的代价来实现反编译,并翻译成JavaScript代码。如果仅仅只是生成jQuery所需要的客户端代码是非常简单的,jQuery API毕竟比较少,每个插件的API也都相当有限。麻烦的是需要插入大量的客户端Event Handler。这才是难点所在。按我的计划,今年6月初会发布jQuery#的第一个版本。 目标是可以这样写View:

 

代码
<% Html.jQuery(document => {
new jdGrid{
container
= "main",
clientId
= jQuery.CreateTable("employees"),
pager
= jQuery.CreateDiv("empPager"),
data
= Model,
open
= () => {
jQuery.Element(
"OpenButton").Disable();
return false;
},
caption
= "Employees"
});
%>
posted @ 2010-03-08 22:54 双鱼座 阅读(...) 评论(...) 编辑 收藏