SharePoint 2010 客户端对象模型,第一部分
SharePoint 2010 客户端对象模型及其使用方法调查
SharePoint 2010 客户端对象模型
在 SharePoint 的早期版本中,当需要从 SharePoint 环境内部访问 ListItems 或其他对象时,唯一的选择是使用服务器端对象模型,可能是在 Web 部件或应用程序页面的代码隐藏中,或者是在 SharePoint 计算机上运行的服务中。在 SharePoint 环境外部,唯一的选择是使用 Web 服务,但存在固有的限制和低效率。
尽管 Web 服务仍然可用,并且在某些情况下很有用,但 SharePoint 2010 有一种更好的工作方式:客户端对象模型。客户端对象模型允许开发人员直接从 .NET 托管代码(例如 Windows WPF 应用程序)、Silverlight(浏览器内和浏览器外)或 JavaScript 中使用 SharePoint,而无需直接使用 Web 服务。本文将讨论客户端 OM 的内部工作原理以及如何有效地使用它,不仅可以处理列表和列表项,还可以处理通过 Web 服务无法获得的 SharePoint 对象。本文将侧重于细节,而 第二部分 将侧重于所有支持环境中的实际用法。
支持的环境
SharePoint 客户端对象模型支持三种环境:.NET 托管代码、JavaScript 和 SilverLight。
.NET 托管代码程序集
要在 .NET 托管代码中使用客户端对象模型,必须在 Visual Studio 项目中添加对两个程序集的引用。这两个程序集都可以在 [SharePoint Root]\ISAPI 文件夹中找到,该文件夹通常是 C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\ISAPI\。
Microsoft.SharePoint.Client.dll (282kb)
Microsoft.SharePoint.Client.Runtime.dll (146kb)
从指示的大小可以看出,这些程序集并不大,不会给应用程序带来太多负担。尽管它们的大小相对较小,但它们具有很大的潜力,正如您很快就会看到的。
Silverlight 程序集
与 .NET 托管代码一样,要在 Silverlight 应用程序中使用 SharePoint 客户端 OM,必须添加两个程序集引用。这些程序集查找起来有点棘手,位于 [SharePoint Root]\Templates\Layouts\ClientBin 文件夹中。
Microsoft.SharePoint.Client.Silverlight.dll (266kb)
Microsoft.SharePoint.Client.Silverlight.Runtime.dll (142kb)
考虑到 Layout 文件夹映射到每个 SharePoint Web 应用程序,以及 SharePoint 2010 中 Silverlight 的大量使用,这种位置是可以理解的。您会注意到这些程序集的大小略小于其 .NET 托管代码对应程序集。尽管如此,功能上没有区别。
JavaScript 代码
要在 JavaScript 中使用客户端 OM,必须在页面中包含 SP.js 文件。相应的 SP.Runtime.js 将由 SharePoint 自动添加。
FormDigest 和安全性
SharePoint 使用 FormDigest
控件根据用户、站点和时间段生成安全令牌,并使用它来验证发布数据和更新内容数据库的任何操作。在使用客户端 OM 的所有页面上都必须存在此控件。但是,由于它包含在 SharePoint 主页 v4.master 和 default.master 中,因此不应成为问题。在创建自己的主页时,只需牢记这一点。
客户端 OM 体系结构
在实际使用之前,我们将简要了解客户端对象模型的体系结构,以理解它是如何工作的。

如上图所示,所有版本的客户端 OM 都通过名为 Client.svc
的 WCF Web 服务进行。此 Web 服务与所有其他 SharePoint Web 服务一样,位于 [SharePoint Root]\ISAPI 文件夹中。客户端 OM 负责将任何请求打包成 XML 并调用 Web 服务器,但是,调用发生的时间由开发人员控制,您将看到这一点。Client.svc
Web 服务的响应以 JSON 形式发送,客户端 OM 负责将其转换为调用发生的环境中使用的适当对象。
Client.svc
Web 服务有一个方法,ProcessQuery
,它接受一个 Stream
对象(如上所示的 XML 格式的流),并返回一个 Stream
(JSON 格式)。此方法的实现可以在程序集 Microsoft.SharePoint.Client.ServerRuntime.dll
和私有类 ClientRquestServiceImpl
中找到。深入研究此方法,您首先发现的是身份验证检查。
if (Utility.ShouldForceAuthentication(HttpContext.Current)) { WebOperationContext.Current.OutgoingResponse.StatusCode = HttpStatusCode.Unauthorized; WebOperationContext.Current.OutgoingResponse.SuppressEntityBody = true; return new MemoryStream(); } internal static bool ShouldForceAuthentication(HttpContext httpContext) { return (((httpContext != null) && (string.Compare(httpContext.Request.Headers["X-RequestForceAuthentication"], "true", StringComparison.OrdinalIgnoreCase) == 0)) && (((httpContext.User == null) || (httpContext.User.Identity == null)) || !httpContext.User.Identity.IsAuthenticated)); }
客户端 OM 默认使用 Windows 身份验证,但是,稍后我将展示如何利用声明式身份验证。如代码所示,此 Web 服务不执行身份验证,如果身份验证尚未完成,它仅返回未授权状态。身份验证由遗留 Web 服务 Sites.asmx
处理。当确认身份验证后,响应内容类型将设置为 JSON,如前所述……
WebOperationContext.Current.OutgoingResponse.ContentType = "application/json"; WebOperationContext.Current.OutgoingResponse.Headers.Add("X-Content-Type-Options", "nosniff");
……然后处理输入流中的 XML。
using (ClientMethodsProcessor processor = new ClientMethodsProcessor(inputStream, host)) { processor.Process(); stream = processor.CreateResultUTF8Stream(); }
到达此 Web 服务方法的 XML 是 ObjectPath
元素的集合,其中包含描述正在进行的请求的属性和子元素。ClientMethodsProcessor
构造函数方法创建这些元素的 Dictionary
,该 Dictionary
在 Process
方法中使用,并最终根据元素表示的操作类型在 ProcessOne
方法中使用。
private void ProcessOne(XmlElement xe) { this.ThrowIfTimeout(); if (xe.Name == "Query") { this.ProcessQuery(xe); } else if (xe.Name == "Method") { this.ProcessMethod(xe); } else if (xe.Name == "StaticMethod") { this.ProcessStaticMethod(xe); } else if (xe.Name == "SetProperty") { this.ProcessSetProperty(xe); } else if (xe.Name == "SetStaticProperty") { this.ProcessSetStaticProperty(xe); } else if (xe.Name == "ObjectPath") { this.ProcessInstantiateObjectPath(xe); } else if (xe.Name == "ObjectIdentityQuery") { this.ProcessObjectIdentityQuery(xe); } else if ((xe.Name == "ExceptionHandlingScope") || (xe.Name == "ExceptionHandlingScopeSimple")) { this.ProcessExceptionHandlingScope(xe); } else if (xe.Name == "ConditionalScope") { this.ProcessConditionalScope(xe); } }
您将在稍后的实际实现和用法中看到这一切如何结合在一起并起作用。
使用客户端对象模型
客户端 OM 与大多数 SharePoint 开发人员应该熟悉的服务器端对象模型非常相似。如下表所示,类名基本相同,除了 ClientContext
,只是没有 SP 前缀。存在一些差异,并且有一些类是客户端 OM 特有的,我将在过程中进行重点介绍。您将处理的所有对象都派生自 ClientObject
,它包含用于创建 ObjectPath
的属性,该 ObjectPath
用于形成查询,如上一节所述。
服务器端 OM | 客户端 OM |
SPContext | ClientContext |
SPSite | Site |
SPWeb | Web |
SPList | 列表 |
SPListItem | ListItem |
在托管代码中使用客户端 OM 的一个非常简单的示例如下:
using(ClientContext ctx = new ClientContext("http://mysite")) { ctx.Load(ctx.Site); ctx.ExecuteQuery(); }
或者使用 JavaScript:
var ctx = SP.ClientContext.get_current(); var site = ctx.get_site(); ctx.load(site); ctx.executeQueryAsync(Function.createDelegate(this, OnSuccess), Function.createDelegate(this, OnFail));
这两个示例几乎相同,主要区别在于 JavaScript 的异步方法。请求当然是在浏览器中带外进行的,因此需要异步处理。Silverlight 也是如此。在这两个示例中需要注意的一点是,实际调用 Web 服务直到调用 Execute
方法。正如我将在示例中展示的,这在构建复杂查询和优化传输数据量方面非常有用,而不是进行多次 Web 服务调用。
现在我将查看此过程的各个组件。我将为此示例使用托管代码控制台应用程序,但在本文的第二部分中,我将演示 JavaScript 和 Silverlight 的用法。
创建 ClientContext
与服务器端 OM 一样,在使用客户端 OM 时,第一步是创建一个操作上下文,在这种情况下是 ClientContext
。
using(ClientContext ctx = new ClientContext("http://mysite")) { }
构造函数的输入必须是完整 URL。ClientObject
派生自 ClientRunTimeContext
,它实现了 IDisposable
,因此在使用后应正确释放。这不像使用服务器端 OM 那样关键,但对于正确使用仍然很重要。ClientContext
对象包含当前上下文的 Site
和 Web
的属性,以及一个已重写的 ExecuteQuery
方法,我稍后将介绍。ClientRunTimeContext
包含 Load
和 LoadQuery
等方法,我将稍后介绍。应注意的是,客户端 OM 的默认身份验证方法是 Windows 身份验证。如果正在使用的站点使用基于窗体的身份验证,则需要执行一些额外的步骤。我也会讲到。
一旦有了 ClientContext
,就可以用它来获取对站点中的 Site
或 Web
的引用。如上所述,Site
和 Web
是 ClientContext
对象的属性。与服务器端 OM 一样,您也可以使用 Site
的 RootWeb
属性或 OpenWeb
方法来获取 Web
对象。服务器端 OM 中有一个方法不可用,那就是 OpenWebById
方法,它接受要打开的 Web
的 Guid
ID。
public Web OpenWebById(Guid gWebId) { return new Web(base.Context, new ObjectPathMethod(base.Context, base.Path, "OpenWebById", new object[] { gWebId })); }
实例化 ClientContext
后,有几个可用的属性,其中最重要的是 PendingRequest
。此属性包含对 ClientRequest
对象的引用,顾名思义,该对象将执行到 SharePoint 的实际查询并处理结果。它通过 RequestExecutor
属性公开的 WebRequestExecutor
对象来实现这一点。如上所述,所有请求都通过 Client.svc
Web 服务进行处理。在调试应用程序时,您可以在 WebRequest
属性中看到这一点。
ctx.PendingRequest.RequestExecutor.WebRequest.RequestUri // http://mysite/_vti_bin/client.svc/ProcessQuery
创建请求:Load
拥有 ClientContext
只是使用客户端 OM 的开始。如上所述,请求的创建和处理基于 ObjectPath
元素的集合。这些是通过使用 Load
或 LoadQuery
方法构造的。
public void Load<T>(T clientObject, params Expression<Func<T, object>>[] retrievals) where T: ClientObject; public IEnumerable<T> LoadQuery<T>(ClientObjectCollection<T> clientObjects) where T: ClientObject; public IEnumerable<T> LoadQuery<T>(IQueryable<T> clientObjects) where T: ClientObject; using(ClientContext ctx = new ClientContext(URL)) { ctx.Load(ctx.Site); ctx.ExecuteQuery(); }
这两个方法(LoadQuery 有两个重载)都以派生自 ClientObject
的对象作为参数。请记住,如上所述,此对象包含必要的信息,用于形成查询中将使用的对象的 ObjectPath
。在使用客户端 OM 时需要注意的一点是,无论您有多少个 Load
或 LoadQuery
语句,在调用 ExecuteQuery
之前都不会执行任何操作。这使您可以构建包含多个对象的复杂查询,但让客户端 OM 代码构建所需的 ObjectPath
,而不会产生多余的数据。查看 Load
方法,它有一个 ClientObject
和一个 params
,后者是可选的。在上面的示例中,Site
属性用于传递 Site
对象,如早期所注,该对象派生自 ClientObject
。检索参数用于指定应返回哪些属性或对象。我稍后会讲到,但首先我将看看 Load
方法的内部。
public void Load<T>(T clientObject, params Expression<Func<T, object>>[] retrievals) where T: ClientObject { if (clientObject == null) { throw new ArgumentNullException("clientObject"); } DataRetrieval.Load<T>(clientObject, retrievals); } internal static void Load<T>(T clientObject, params Expression<Func<T, object>>[] retrievals) where T: ClientObject { ClientObjectCollection objects = clientObject as ClientObjectCollection; if ((retrievals == null) || (retrievals.Length == 0)) { clientObject.Query.SelectAllProperties(); if (objects != null) { clientObject.Query.ChildItemQuery.SelectAllProperties(); } } else { ...removed for clarity } }
调用 Load
方法时,参数将传递给内部对象 DataRetrieval
的 Load
方法。它首先检查检索参数是否存在。如果不存在或为空,API 将假定检索所有属性。使用 Fiddler,您可以在调用 ExecuteQuery
后检查请求和结果,您应该会看到类似以下的内容。
POST http://mySite/_vti_bin/client.svc/ProcessQuery HTTP/1.1 X-RequestDigest: 0x8D593CC2539B9[truncated for space]0000 Content-Type: text/xml X-RequestForceAuthentication: true Host: mySite Content-Length: 554 Expect: 100-continue <Request AddExpandoFieldTypeSuffix="true" SchemaVersion="14.0.0.0" LibraryVersion="14.0.4762.1000" ApplicationName=".NET Library" xmlns="http://schemas.microsoft.com/sharepoint/clientquery/2009"> <Actions> <ObjectPath Id="2" ObjectPathId="1" /> <ObjectPath Id="4" ObjectPathId="3" /> <Query Id="5" ObjectPathId="3"> <Query SelectAllProperties="true"> <Properties /> </Query> </Query> </Actions> <ObjectPaths> <StaticProperty Id="1" TypeId="{3747adcd-a3c3-41b9-bfab-4a64dd2f1e0a}" Name="Current" /> <Property Id="3" ParentId="1" Name="Site" /> </ObjectPaths> </Request>
您可以看到 ObjectPath
元素已从 ClientObject
构造。注意 SelectAllProperties="true"
。结果如下:
HTTP/1.1 200 OK Cache-Control: private Content-Type: application/json Server: Microsoft-IIS/7.5 SPRequestGuid: 22e3f795-a5c9-492d-a551-3e950b8f1c10 Set-Cookie: WSS_KeepSessionAuthenticated={4e4cffb3-203e-4857-8f5a-90a4b18789a4}; path=/ X-SharePointHealthScore: 0 X-Content-Type-Options: nosniff X-AspNet-Version: 2.0.50727 X-Powered-By: ASP.NET MicrosoftSharePointTeamServices: 14.0.0.6029 Content-Length: 460 [ { "SchemaVersion":"14.0.0.0","LibraryVersion":"14.0.6106.5001","ErrorInfo":null },2,{ "IsNull":false },4,{ "IsNull":false },5,{ "_ObjectType_":"SP.Site", "Id":"\/Guid(2aa4b949-05b2-456e-8392-c694d41c95d0)\/", "ServerRelativeUrl":"\u002f", "Url":"http:\u002f\u002fmySite", "UIVersionConfigurationEnabled":false, "MaxItemsPerThrottledOperation":5000, "AllowDesigner":true, "AllowRevertFromTemplate":false, "AllowMasterPageEditing":false, "ShowUrlStructure":false } ]
由于 Site
对象没有太多属性,因此结果相对较小。您可以看到返回了一个 JSON 对象,其中填充了属性值。如果在调用 ExecuteQuery
之前尝试访问属性(如下所示),则会引发 PropertyOrFieldNotInitializedException
。
using(ClientContext ctx = new ClientContext(URL)) { ctx.Load(ctx.Site); // Will throw PropertyOrFieldNotInitializedException Console.WriteLine("Site.URL: {0}", ctx.Site.Url); ctx.ExecuteQuery(); }

尝试访问集合(例如 Site.Features
)时,将引发 CollectionNotInitializedException
。

但是,请注意,在此示例中,仍然会引发异常,因为加载的唯一对象是 Site
,它不包含其中的所有集合。要包含 Features 集合,Load
方法应如下所示:
ctx.Load(ctx.Site.Features);
但是,当尝试访问 Site
对象上的属性时,将引发 PropertyOrFieldNotInitializedException
,因为 Load
方法未包含它,只包含 Features 集合。作为对比,以上代码的请求和响应将如下所示:
POST http://mySite/_vti_bin/client.svc/ProcessQuery HTTP/1.1 X-RequestDigest: 0xF1C00A2A5C[truncated for space]0000 Content-Type: text/xml X-RequestForceAuthentication: true Host: mySite Content-Length: 714 Expect: 100-continue ≷Request AddExpandoFieldTypeSuffix="true" SchemaVersion="14.0.0.0" LibraryVersion="14.0.4762.1000" ApplicationName=".NET Library" xmlns="http://schemas.microsoft.com/sharepoint/clientquery/2009"> ≷Actions> ≷ObjectPath Id="2" ObjectPathId="1" /> ≷ObjectPath Id="4" ObjectPathId="3" /> ≷ObjectPath Id="6" ObjectPathId="5" /> ≷Query Id="7" ObjectPathId="5"> ≷Query SelectAllProperties="true"> ≷Properties /> ≷/Query> ≷ChildItemQuery SelectAllProperties="true"> ≷Properties /> ≷/ChildItemQuery> ≷/Query> ≷/Actions> ≷ObjectPaths> ≷StaticProperty Id="1" TypeId="{3747adcd-a3c3-41b9-bfab-4a64dd2f1e0a}" Name="Current" /> ≷Property Id="3" ParentId="1" Name="Site" /> ≷Property Id="5" ParentId="3" Name="Features" /> ≷/ObjectPaths> ≷/Request> HTTP/1.1 200 OK Cache-Control: private Content-Type: application/json Server: Microsoft-IIS/7.5 SPRequestGuid: ad372075-501e-4974-a466-2bd1004e8110 Set-Cookie: WSS_KeepSessionAuthenticated={4e4cffb3-203e-4857-8f5a-90a4b18789a4}; path=/ X-SharePointHealthScore: 0 X-Content-Type-Options: nosniff X-AspNet-Version: 2.0.50727 X-Powered-By: ASP.NET MicrosoftSharePointTeamServices: 14.0.0.6029 Content-Length: 2863 [ { "SchemaVersion":"14.0.0.0","LibraryVersion":"14.0.6106.5001","ErrorInfo":null },2,{ "IsNull":false },4,{ "IsNull":false },6,{ "IsNull":false },7,{ "_ObjectType_":"SP.FeatureCollection","_Child_Items_":[ { "_ObjectType_":"SP.Feature", "_ObjectIdentity_":"740c6a0b-85e2-48a0-a494-e0f1759d4aa7:site:2aa[truncated for space]5d0:feature:2acf[truncated for space]18b", "DefinitionId":"\/Guid(2acf27a5-f703-4277-9f5d-24d70110b18b)\/" },{ "_ObjectType_":"SP.Feature", "_ObjectIdentity_":"740c6a0b-85e2-48a0-a494-e0f1759d4aa7:site:2aa[truncated for space]5d0:feature:695b[truncated for space]162", "DefinitionId":"\/Guid(695b6570-a48b-4a8e-8ea5-26ea7fc1d162)\/" },{ "_ObjectType_":"SP.Feature", "_ObjectIdentity_":"740c6a0b-85e2-48a0-a494-e0f1759d4aa7:site:2aa[truncated for space]5d0:feature:c85[truncated for space]fb5", "DefinitionId":"\/Guid(c85e5759-f323-4efb-b548-443d2216efb5)\/" ... Removed for brevity ... } ] }
您可以看到 Query
和 ObjectPaths
元素已添加了其他元素,并且返回的 JSON 对象不包含 Site
的属性,就像之前的结果一样。如果您同时需要属性和集合,则需要两个 Load
方法。
ctx.Load(ctx.Site); ctx.Load(ctx.Site.Features);
但是,这有一个很大的缺点:请求和响应的大小会增加。
方法 | 请求大小(字节) | 响应大小(字节) |
属性 | 554 | 460 |
集合 | 719 | 2865 |
属性和集合 | 824 | 3201 |
Site
对象相对较小,属性和集合很少,但您可以看到这很容易失控。要控制这种膨胀,您需要使用 retrievals
参数来限制结果。
限制结果集
如上所示,Laod
方法的签名接受 Expression
的 params
集合,这些 Expression
派生自 LambdaExpression
。
public sealed class Expression<TDelegate> : LambdaExpression
我将切换到 Web
对象以获得更多示例,并在下面展示一个简单的用法。
Web web = ctx.Site.RootWeb; ctx.Load(web, w => w.Title);
第一个参数仍然是用于形成 ObjectPath
的 ClientObject
派生对象,但第二个参数是您在使用 Linq 时可能熟悉的普通 Lambda 表达式。将上面的与这个进行比较:
Web web = ctx.Site.RootWeb; ctx.Load(web);
您可以看到,虽然请求大小略有增加,但结果却大大减少了。
方法 | 请求大小(字节) | 响应大小(字节) |
满 | 508 | 736 |
仅标题 | 698 | 297 |
因为params
集合,您可以将表达式串联起来,例如:
ctx.Load(web, w => w.Title, w => w.Description); or ctx.Load(web, w => w.Title, w => w.Description, w => w.Lists);
对于第二个示例,您必须记住,只加载列表的属性,而不是集合,例如 Fields
。如果您希望将它们包含在结果中,您将使用 Include
语句。
Web web = ctx.Site.RootWeb; ctx.Load(web, w => w.Title, w => w.Lists .Include(l => l.Fields));
Include
方法也接受 Lambda 表达式,因此您可以包含属性和集合:
Web web = ctx.Site.RootWeb; ctx.Load(web, w => w.Title, w => w.Lists .Include(l => l.Title, l => l.Fields));
并且由于 Fields
也是一个集合,因此可以添加更深层的 Include
:
Web web = ctx.Site.RootWeb; ctx.Load(web, w => w.Title, w => w.Lists .Include(l => l.Title, l => l.Fields .Include(f => f.InternalName)));
与 Load
方法的所有用法一样,您必须谨慎。下表显示了结果的大小如何根据集合的深度而增长,以及 Lambda 表达式的明智使用如何可以显着减小它。您很少需要对象的全部属性或其集合,因此应该规划好所需内容,并根据这些需求形成 Load。
方法 | 请求大小(字节) | 响应大小(字节) |
一个 Include | 984 | 1,946,393 |
两个 Includes | 1057 | 1,946,671 |
三个 Includes | 1193 | 192,660 |
即使您使用两个 Load
方法,它仍然比一个大型、深度嵌套的 Include
更高效。
Web web = ctx.Site.RootWeb; ctx.Load(web, w => w.Title, w => w.Lists .Include(l => l.Title)); ctx.Load(web, w => w.Title, w => w.Lists .Include(l => l.Fields .Include(f => f.InternalName, f => f.Group)));
方法 | 请求大小(字节) | 响应大小(字节) |
两次加载 | 1201 | 211,489 |
创建请求:LoadQuery
上面显示的 Load
方法用于“填充”传递给它的对象,例如 Web
对象。这在加载对象或其集合的属性时是可以的,但对于访问 ListItems
来说则不然。使用 Load
只能获得 ItemCount
。要获取 ListItems
,您需要使用 LoadQuery
方法。
public IEnumerable<T> LoadQuery<T>(ClientObjectCollection<T> clientObjects) where T: ClientObject; public IEnumerable<T> LoadQuery<T>(IQueryable<T> clientObjects) where T: ClientObject;
从该方法的两个签名可以看出,它可以接受 ClientObjectCollection
(例如 List
)或 IQueryable
对象,这两者当然都必须派生自 ClientObject
,以便构造适当的 ObjectPath
。对于后者重载,可以使用方法语法或 Linq 语法传递 IQueryable
对象,我稍后将探讨这一点。
使用第一个重载,下面的行将产生相同的结果。区别在于 Load
将“填充”web.Lists
集合,而 LoadQuery
将返回一个 List
集合,而不使用 web.Lists
参数。
ctx.Load(web.Lists); var lists = ctx.LoadQuery(web.Lists);
这些方法之间的另一个区别是请求和返回的构造。Load
的请求如下所示。为简洁起见,我不显示结果,但它是 15,657 字节。
POST http://mySite/_vti_bin/client.svc/ProcessQuery HTTP/1.1 X-RequestDigest: 0x798DBB28C[truncated for space]0000 Content-Type: text/xml X-RequestForceAuthentication: true Host: mySite Content-Length: 796 Expect: 100-continue <Request AddExpandoFieldTypeSuffix="true" SchemaVersion="14.0.0.0" LibraryVersion="14.0.4762.1000" ApplicationName=".NET Library" xmlns="http://schemas.microsoft.com/sharepoint/clientquery/2009"> <Actions> <ObjectPath Id="2" ObjectPathId="1" /> <ObjectPath Id="4" ObjectPathId="3" /> <ObjectPath Id="6" ObjectPathId="5" /> <ObjectPath Id="8" ObjectPathId="7" /> <Query Id="9" ObjectPathId="7"> <Query SelectAllProperties="true"> <Properties /> </Query> <ChildItemQuery SelectAllProperties="true"> <Properties /> </ChildItemQuery> </Query> </Actions> <ObjectPaths> <StaticProperty Id="1" TypeId="{3747adcd-a3c3-41b9-bfab-4a64dd2f1e0a}" Name="Current" /> <Property Id="3" ParentId="1" Name="Site" /> <Property Id="5" ParentId="3" Name="RootWeb" /> <Property Id="7" ParentId="5" Name="Lists" /> </ObjectPaths> </Request>
LoadQuery
方法生成以下请求和 15,574 字节的结果:
POST http://mySite/_vti_bin/client.svc/ProcessQuery HTTP/1.1 X-RequestDigest: 0x798DBB28C8[truncated for space]0000 Content-Type: text/xml X-RequestForceAuthentication: true Host: mySite Content-Length: 646 Expect: 100-continue <Request AddExpandoFieldTypeSuffix="true" SchemaVersion="14.0.0.0" LibraryVersion="14.0.4762.1000" ApplicationName=".NET Library" xmlns="http://schemas.microsoft.com/sharepoint/clientquery/2009"> <Actions> <Query Id="21" ObjectPathId="7"> <Query SelectAllProperties="false"> <Properties /> </Query> <ChildItemQuery SelectAllProperties="true"> <Properties /> </ChildItemQuery> </Query> </Actions> <ObjectPaths> <Property Id="7" ParentId="5" Name="Lists" /> <Property Id="5" ParentId="3" Name="RootWeb" /> <Property Id="3" ParentId="1" Name="Site" /> <StaticProperty Id="1" TypeId="{3747adcd-a3c3-41b9-bfab-4a64dd2f1e0a}" Name="Current" /> </ObjectPaths> </Request>
尽管请求的形成方式不同,但结果(除了大小之外)是相同的:一个包含所有可用属性的 List
集合。此示例中的大小差异非常小,但对于更大、更复杂的查询可能会更显著。选择使用哪种方法时,请牢记这一点。
与 Load
方法一样,限制返回的结果集可能是有益的。这就是 LoadQuery
的第二个重载的使用之处。下面的示例使用方法语法形成一个 Lambda 表达式,以仅在结果中包含 Title
属性。同样,如下表所示,当应用筛选时,请求和响应的大小有显着差异。
Web web = ctx.Site.RootWeb; var lists = ctx.LoadQuery(web.Lists.Include(l => l.Title));
方法 | 请求大小(字节) | 响应大小(字节) |
完整列表 | 797 | 15,657 |
包含标题 | 705 | 2340 |
如上所述,查询语法也可以与 LoadQuery
方法一起使用。
Web web = ctx.Site.RootWeb; var query = from l in web.Lists select l; var lists = ctx.LoadQuery(query);
在这里,我从 Linq 表达式创建一个 IQueryable
对象,该表达式将用于返回一个 IEnumerable<List>
,其中包含 Web
的 List
。可以像这样复制前面的示例:
Web web = ctx.Site.RootWeb; var query = from l in web.Lists .Include(l => l.Title) select l; var lists = ctx.LoadQuery(query);
处理 ListItems
尽管获取 Web
中 List
的标题很有用,但在大多数情况下,客户端 OM 将用于处理 ListItems
:读取、插入、更新和删除。尽管 SharePoint 2010 提供了所有这些进步,但在此阶段,您仍然必须通过 CamlQuery
类使用 CAML。
CamlQuery query = new CamlQuery(); ListItemCollection items = list.GetItems(query); ctx.Load(items);
尽管没有提供 CAML,但这仍将返回所有 ListItem
,以及所有字段值。要仅获取特定字段,您将使用标准的 CAML 查询,例如:
CamlQuery query = new CamlQuery(); ListItemCollection items = list.GetItems(query); ctx.Load(items); query.ViewXml = @"<View Scope='RecursiveAll'> <ViewFields> <FieldRef Name='Title' /> <FieldRef Name='FirstName' /> </ViewFields> </View>";
如果您有一个有很多列的列表,编写 CAML 可能会非常繁琐且容易出错。幸运的是,CamlQuery
有一个静态方法可以帮助您,即 CreateAllItemsQuery
。此方法将返回以下 CAML:
<View Scope="RecursiveAll"> <Query> </Query> </View>
还有一个用于所有文件夹的静态方法 CreateAllFoldersQuery
。
<View Scope="RecursiveAll"> <Query> <Where> <Eq> <FieldRef Name="FSObjType" /> <Value Type="Integer">1</Value> </Eq> </Where> </Query> </View>
正如我上面展示的,您可以使用标准的 CAML 语法来形成查询,包括 Where
和 RowLimit
元素。您还可以使用 CreateAllItemsQuery
的重载:
public static CamlQuery CreateAllItemsQuery(int rowLimit, params string[] viewFields);
这将像这样使用:
CamlQuery query = CamlQuery.CreateAllItemsQuery(10, "Title", "FirstName");
ExecuteQuery
尽管在前面的示例中使用了 ExecuteQuery
,但为了完整起见,我将简要回顾一下它的工作原理。
public virtual void ExecuteQuery() { ScriptTypeMap.EnsureInited(); ClientRequest pendingRequest = this.PendingRequest; this.m_request = null; pendingRequest.ExecuteQuery(); }
EnsureInited
方法使用 lock
来确保对象的初始化只执行一次。之所以需要锁,是因为 Init
方法使用反射来检查自定义属性并创建实现 IScriptTypeFactory
接口的类的实例。初始化后,ClientRequest.ExecuteQuery
方法将调用 BuildQuery
方法。
private ChunkStringBuilder BuildQuery() { SerializationContext serializationContext = this.SerializationContext; ChunkStringBuilder builder = new ChunkStringBuilder(); XmlWriterSettings settings = new XmlWriterSettings { OmitXmlDeclaration = true, NewLineHandling = NewLineHandling.Entitize }; XmlWriter writer = XmlWriter.Create(builder.CreateTextWriter(CultureInfo.InvariantCulture), settings); writer.WriteStartElement("Request", "http://schemas.microsoft.com/sharepoint/clientquery/2009"); writer.WriteAttributeString("AddExpandoFieldTypeSuffix", "true"); writer.WriteAttributeString("SchemaVersion", ClientSchemaVersions.CurrentVersion.ToString()); writer.WriteAttributeString("LibraryVersion", "14.0.4762.1000"); if (!string.IsNullOrEmpty(this.m_context.ApplicationName)) { writer.WriteAttributeString("ApplicationName", this.m_context.ApplicationName); } writer.WriteStartElement("Actions"); ...removed for clarity... writer.WriteEndElement(); writer.WriteStartElement("ObjectPaths"); Dictionary<long, ObjectPath> dictionary = new Dictionary<long, ObjectPath>(); while (true) { List<long> list = new List<long>(); foreach (long num in serializationContext.Paths.Keys) { if (!dictionary.ContainsKey(num)) { list.Add(num); } } if (list.Count == 0) { break; } for (int i = 0; i < list.Count; i++) { ObjectPath path = this.m_context.ObjectPaths[list[i]]; path.WriteToXml(writer, serializationContext); dictionary[list[i]] = path; } } writer.WriteEndElement(); writer.WriteEndElement(); writer.Flush(); return builder; }
如您所见,这就是在调用 ExecuteQuery
方法之前,在任何 Load
或 LoadQuery
方法中使用的对象和属性创建的所有 ObjectPath
被构建成一个 XML 字符串,该字符串将与 LibraryVersion 和 ApplicationName 等属性一起发送到服务器。查询构建完成后,剩下的唯一要做的事情就是使用下面的 ExecuteQueryToServer
方法将其发送到服务器。
private void ExecuteQueryToServer(ChunkStringBuilder sb) { this.m_context.FireExecutingWebRequestEvent(new WebRequestEventArgs(this.RequestExecutor)); this.RequestExecutor.RequestContentType = "text/xml"; if (this.m_context.AuthenticationMode == ClientAuthenticationMode.Default) { this.RequestExecutor.RequestHeaders["X-RequestForceAuthentication"] = "true"; } Stream requestStream = this.RequestExecutor.GetRequestStream(); sb.WriteContentAsUTF8(requestStream); requestStream.Flush(); requestStream.Close(); this.RequestExecutor.Execute(); this.ProcessResponse(); }
此方法将发送查询,然后使用 ProcessResponse
方法将结果转换为 JSON 对象。
身份验证
如我之前提到的,当使用托管代码时,客户端 OM 默认使用 Windows 身份验证。对于 JavaScript 或 Silverlight,代码在应用程序的上下文中执行,因此它将使用现有的安全令牌。您可以通过使用 Fiddler 等工具监视生成的 Web 流量来看到这一点。在使用托管代码时,将调用 _vti_bin/sites.asmx
请求 FormsDigest
信息,其中包含安全令牌。
POST http://mySite/_vti_bin/sites.asmx HTTP/1.1 Content-Type: text/xml SOAPAction: http://schemas.microsoft.com/sharepoint/soap/GetUpdatedFormDigestInformation X-RequestForceAuthentication: true Authorization: NTLM TlRMTVNTUAABAAAAt7II4gkACQAsAAAABAAEACgAAAAGAbEdAAAAD01BUktOSVNDSEFMS0U= Host: mySite Content-Length: 0
当使用基于声明的身份验证(例如窗体身份验证)的站点时,在调用 ExecuteQuery
之前需要一个额外的步骤来设置要使用的类型和凭据。您的代码中的其他所有内容都保持不变。
ctx.AuthenticationMode = ClientAuthenticationMode.FormsAuthentication; ctx.FormsAuthenticationLoginInfo = new FormsAuthenticationLoginInfo("loginName", "password");
FormsAuthenticationLoginInfo
类由 ClientContext.FormsAuthenticationLogin
方法使用,该方法首先调用 EnsureLogin
方法来检查身份验证 Cookie。
internal CookieCollection EnsureLogin(Uri contextUri) { if ((this.m_authCookies == null) || !this.m_cookieValid) { this.m_authCookies = new Dictionary<Uri, CookieInfo>(); this.m_cookieValid = true; } contextUri = new Uri(contextUri, "/"); CookieInfo info = null; if (!this.m_authCookies.TryGetValue(contextUri, out info) || (info.Expires <= DateTime.UtcNow)) { info = this.Login(contextUri); this.m_authCookies[contextUri] = info; } return info.Cookies; }
如果 Cookie 不存在或已过期,则调用 Login
,这将调用身份验证服务来完成登录。
Uri authenticationServiceUrl; if (this.AuthenticationServiceUrl == null) { authenticationServiceUrl = new Uri(contextUri, "/_vti_bin/authentication.asmx"); } else { authenticationServiceUrl = this.AuthenticationServiceUrl; } Authentication authentication = new Authentication(authenticationServiceUrl) { CookieContainer = this.CookieContainer }; LoginResult result = authentication.Login(this.LoginName, this.Password);
下一步
希望本文能帮助您了解 SharePoint 客户端对象模型是什么以及它是如何工作的。在第二部分中,我将使用 .NET 托管代码、JavaScript 和 Silverlight 介绍实际操作,例如更新和添加 ListItems、创建 Lists 等。
历史
首次发布:2011 年 10 月 13 日