WCF Data Services是SharePoint 2010中一个极具吸引力的新特性。然而,因为它的强大,直接对其进行编程仍然会有点痛苦。幸运的是,一个新的相关技术 —— ASP.Net AJAX模板 – 可以完美的与WCF Data Service进行集成,并允许我们快速构建优雅的,可维护的和反应迅速的AJAX应用程序。
在本文中,我将详细描述ASP.Net AJAX模板,并在Visual Studio 2010中一步一步的构建一个非常简单的应用程序页面。接着使用ASP.Net AJAX模板来显示SharePoint 2010中通过WCF Data Services发布的JSON格式的列表数据。
什么不是模板?
单纯从技术层面解释什么是ASP.Net AJAX模板可能并不容易。不妨先假设没有这项技术,看看传统方式是如何解决问题的。如果SharePoint传递给你一个JavaScript中的JSON对象的数组,现在的你会如何将其转换成HTML?
最常见的答案似乎就是JQuery的.append()语句了。代码大致如下:
$("#userStories").append("<div class='userStoryCard'>" +
userStories[i].Title + "</div>");
}
这种类型的解决方案的问题,或者说任何没有使用模板引擎的解决方案,事实上都无法清楚的分离数据访问逻辑和界面展现。事实上,这种情形很像回到了经典的Active Server Pages风格,可维护性很差,代码一塌糊涂。随着我们不断的从SharePoint获取数据,整个代码会变得更加杂乱。
模板的目标是使你不必编写上面那样的代码,使用模板后的代码应该像下面这样::
整洁,漂亮。理想状态下模板应提供这样一个的解决方案:
- 最大限度地减少管道(plumbing)代码
- 清晰的分离数据访问逻辑和界面展示
- 简化存回服务器端的数据
- 并且没有ViewState!
ASP.Net AJAX模板的昨天,今天和明天
Visual Studio 2010和SharePoint 2010
在进入模板学习前,让我们先来快速看一下如何在Visual Studio 2010中构建一个应用程序页面。这是我们将来编写代码的基础。当然你可以把所有代码都放在一个内容编辑器Web部件里,但是Visual Studio提供了更好的智能提示和调试的支持,因此我们选择在应用程序页中运行。
为了在Visual Studio 2010中建立一个简单的应用程序页面,先选择“新建项目”,然后导航到SharePoint 2010模板分类,选择“空白SharePoint项目”
点击确定后,Visual Studio会弹出SharePoint自定义向导:
该窗口要求你选择一个站点用于调试。当你按F5键(开始调试)时,Visual Studio 2010会进行wsp文件打包,然后将其部署到这里你填写的URL对应的站点上,同时附加上对应的w3wp进程,并打开一个浏览器显示该sharepoint站点。这样,你就立即拥有一个调试环境了。
如果你对这一过程印象不深刻,那么你可能没有做过多少WSS 3.0的开发——但请相信我,这个改进大大节省了我们的时间。尽管在本例中我们不会写任何服务器端代码,但仍旧可以体会到微软在SharePoint 2010上倾注了不少的心力。
当Visual Studio完成项目初始化后,选择项目->添加新项,然后选择应用程序页。
Visual Studio接下来会自动生成一个完全套用SharePoint母版页并包含内容控件的页面。
<%@ Import Namespace="Microsoft.SharePoint.ApplicationPages" %>
<%@ Register Tagprefix="SharePoint" Namespace="Microsoft.SharePoint.WebControls" Assembly="Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register Tagprefix="Utilities" Namespace="Microsoft.SharePoint.Utilities" Assembly="Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register Tagprefix="asp" Namespace="System.Web.UI" Assembly="System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" %>
<%@ Import Namespace="Microsoft.SharePoint" %>
<%@ Assembly Name="Microsoft.Web.CommandUI, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="UserStories.aspx.cs" Inherits="PreDemo.Layouts.PreDemo.UserStories" DynamicMasterPageFile="~masterurl/default.master" %>
<asp:Content ID="PageHead" ContentPlaceHolderID="PlaceHolderAdditionalPageHead" runat="server">
</asp:Content>
<asp:Content ID="Main" ContentPlaceHolderID="PlaceHolderMain" runat="server">
Hello World!
</asp:Content>
<asp:Content ID="PageTitle" ContentPlaceHolderID="PlaceHolderPageTitle" runat="server">
应用程序页
</asp:Content>
<asp:Content ID="PageTitleInTitleArea" ContentPlaceHolderID="PlaceHolderPageTitleInTitleArea" runat="server" >
我的应用程序页
</asp:Content>
(“Hello World!”是我写的)。现在如果你按F5键或者点击生成->部署解决方案,则Visual Studio会打包该应用程序页到一个wsp文件并将其部署到SharePoint。如果你导航到该应用程序页的话(例如本例中为http://contoso14/_layouts/PreDemo/UserStories.aspx)则会看到如下的显示:
很震撼吧!你真的应该如此。我们没有编写冗长的,重复的代码,或者容易出错的CAML,没有运行任何DOS(或PowerShell)脚本,没有手工部署到GAC,甚至没有注意到后台自动生成的Feature.xml文件,然而,我们已经部署完了。
安装ASP.Net AJAX模板
ASP.Net AJAX模板的安装比较棘手。为了得到它你需要先在CodePlex上下载最新推出的AJAX控件工具包源代码(本例中下载下来的是AjaxControlToolkit-9c860ac12ae9.zip)。并在Visual Studio(它的项目中包含了Visual Studio 2008和2010两个版本)中编译(编译时还需要安装Microsoft Ajax Minifier 2.0)然后将Javascript文件复制到SharePoint的layouts目录下。需要复制的目录有:
从 AjaxControlToolkit.Source\SampleWebSites\AjaxClientWebSite\Scripts; 复制到
C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\ \14\TEMPLATE\LAYOUTS\Scripts
还有从 AjaxControlToolkit.Source\Client\MicrosoftAjax\Templates; 复制到
C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\ \14\TEMPLATE\LAYOUTS\Scripts
或者采用更符合标准的做法,将其复制到Visual Studio项目的layouts目录中。
代码
Javascript文件都就位后,我们就可以编写最简单的模板页代码了:
<%@ Import Namespace="Microsoft.SharePoint.ApplicationPages" %>
<%@ Register Tagprefix="SharePoint" Namespace="Microsoft.SharePoint.WebControls" Assembly="Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register Tagprefix="Utilities" Namespace="Microsoft.SharePoint.Utilities" Assembly="Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register Tagprefix="asp" Namespace="System.Web.UI" Assembly="System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" %>
<%@ Import Namespace="Microsoft.SharePoint" %>
<%@ Assembly Name="Microsoft.Web.CommandUI, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="UserStories.aspx.cs" Inherits="PreDemo.Layouts.PreDemo.UserStories" DynamicMasterPageFile="~masterurl/default.master" %>
<asp:Content ID="PageHead" ContentPlaceHolderID="PlaceHolderAdditionalPageHead" runat="server">
<style type="text/css">
.sys-template
{
display:none;
}
</style>
<script src="/_layouts/Scripts/MicrosoftAjax/Start.js" type="text/javascript"></script>
<script src= "/_layouts/Scripts/MicrosoftAjax/MicrosoftAjax.js" type="text/javascript"></script>
<script type="text/javascript">
Sys.require([
Sys.components.dataView,
Sys.components.openDataServiceProxy,
Sys.scripts.jQuery
]);
Sys.onReady(function () {
var dataSource = Sys.create.openDataServiceProxy('/_vti_bin/ListData.svc');
Sys.query("#userStoriesList").dataView({
dataProvider: dataSource,
fetchOperation: "UserStories",
feachParameters: { orderby: '标题' },
autoFetch: "true"
});
});
</script>
</asp:Content>
<asp:Content ID="Main" ContentPlaceHolderID="PlaceHolderMain" runat="server">
Hello World!
<ul id="userStoriesList" class="sys-template">
<li>{{ 标题 }}</li>
</ul>
</asp:Content>
<asp:Content ID="PageTitle" ContentPlaceHolderID="PlaceHolderPageTitle" runat="server">
应用程序页
</asp:Content>
<asp:Content ID="PageTitleInTitleArea" ContentPlaceHolderID="PlaceHolderPageTitleInTitleArea" runat="server" >
我的应用程序页
</asp:Content>
其中的UserStories是我的一个任务列表。我事先填充了一些示例数据。
跳过JavaScript部分,我们先来看Main content一节中名为userStoriesList的<ul>无序列表。 其中的<li>元素包围着一个有趣的符号:{{ 标题 }}。这样就声明了一个绑定到SharePoint中ListItem的标题字段。我们也可以改成任何其他的列,如“优先级”,或者在其中添加JavaScript,如{{ String.format("{0:yyyy-M-dd}", 修改时间) }}。
注意PageHead的脚本引用。引用MicrosoftAjax.js使得我们可以使用ASP.Net AJAX的核心功能。Start.js使得我们可以使用Sys.require功能,以便导入其他具有依赖关系的Javascript。
至此,我们还没有导入足够的JavaScript以提供我们需要的模板功能。这部分工作由Sys.require来完成。声明了1.DataView对象,用于完成主要的模板工作;2.openDataServiceProxy,知道如何与SharePoint的ADO.Net Data Service进行通讯;3. jQuery。如果你用Firebug看的话就会发现Sys.require加载了哪些额外的.js文件,就像MicrosoftAjaxTemplates.js。而且是按照正确的顺序加载到页面上的。这种方式保证了页面不会放上很多不需要的Javascript。
真正的奇迹发生在Sys.onReady里,其功能是在DOM加载完成后执行一次。首先它实例化了一个openDataServiceProxy对象,负责与一个oData端点进行通讯,在我们的例子中是一个ADO.Net Data Services。
它做的第二件事情是实例化一个DataView对象,并将其关联到userStoriesList元素。fetchOperation参数告诉它从哪个列表获取ListItems(本例中为UserStories列表)。fetchParameters参数告诉它如何对数据进行排序,筛选或分页。
DataView实例化后(由于autoFetch被设为true) 会通过其dataProvider检索JSON数据,并为每个返回的行重复所关联的DOM元素的innerHTML,而且以实际的数据代替绑定语法。看起来似乎很简单,但在将来的博文中你会看到它很快会变得很复杂。
最后一个要注意的是sys-template类。这是一个在ASP.Net AJAX中预定义的CSS类,当完成渲染工作后,由ASP.Net AJAX将其设置为display:block。因此,我们需要为sys-template创建一个CSS类并设为display:none,这样在页面加载过程中最终用户就看不到你的模板代码了。
现在,如果你点击生成->部署解决方案,就会看到如下的显示:
总结
当然,可能如果用服务器端代码编写这个功能的话实现起来更容易。这个话题先搁在这儿,我们的解决方案还有很多潜力可挖。例如,从哪个层面上与jQuery结合,可以使界面更美观?如何将数据写回到上下文服务器端?如何实现主-子关系?我们将在随后的博文中一起来探讨这些问题。
参考资料
Client Side AJAX Applications in SharePoint 2010-Part3:ASP.Net AJAX Templating 101
ASP.NET AJAX: Client Postbacks cause a Sys.ArgumentTypeException