65.9K
CodeProject 正在变化。 阅读更多。
Home

SharePoint 2010 客户端对象模型,第一部分

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.53/5 (7投票s)

2011 年 10 月 13 日

CPOL

16分钟阅读

viewsIcon

133823

downloadIcon

3293

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 体系结构

在实际使用之前,我们将简要了解客户端对象模型的体系结构,以理解它是如何工作的。

Image1.png

如上图所示,所有版本的客户端 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,该 DictionaryProcess 方法中使用,并最终根据元素表示的操作类型在 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 对象包含当前上下文的 SiteWeb 的属性,以及一个已重写的 ExecuteQuery 方法,我稍后将介绍。ClientRunTimeContext 包含 LoadLoadQuery 等方法,我将稍后介绍。应注意的是,客户端 OM 的默认身份验证方法是 Windows 身份验证。如果正在使用的站点使用基于窗体的身份验证,则需要执行一些额外的步骤。我也会讲到。

一旦有了 ClientContext,就可以用它来获取对站点中的 SiteWeb 的引用。如上所述,SiteWebClientContext 对象的属性。与服务器端 OM 一样,您也可以使用 SiteRootWeb 属性或 OpenWeb 方法来获取 Web 对象。服务器端 OM 中有一个方法不可用,那就是 OpenWebById 方法,它接受要打开的 WebGuid 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 元素的集合。这些是通过使用 LoadLoadQuery 方法构造的。

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 时需要注意的一点是,无论您有多少个 LoadLoadQuery 语句,在调用 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 方法时,参数将传递给内部对象 DataRetrievalLoad 方法。它首先检查检索参数是否存在。如果不存在或为空,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();
}
Image2.png

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

Image3.png

但是,请注意,在此示例中,仍然会引发异常,因为加载的唯一对象是 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

&gl;Request AddExpandoFieldTypeSuffix="true" SchemaVersion="14.0.0.0" LibraryVersion="14.0.4762.1000" 
        ApplicationName=".NET Library" xmlns="http://schemas.microsoft.com/sharepoint/clientquery/2009">
  &gl;Actions>
    &gl;ObjectPath Id="2" ObjectPathId="1" />
    &gl;ObjectPath Id="4" ObjectPathId="3" />
    &gl;ObjectPath Id="6" ObjectPathId="5" />
    &gl;Query Id="7" ObjectPathId="5">
      &gl;Query SelectAllProperties="true">
        &gl;Properties />
      &gl;/Query>
      &gl;ChildItemQuery SelectAllProperties="true">
        &gl;Properties />
      &gl;/ChildItemQuery>
    &gl;/Query>
  &gl;/Actions>
  &gl;ObjectPaths>
    &gl;StaticProperty Id="1" TypeId="{3747adcd-a3c3-41b9-bfab-4a64dd2f1e0a}" Name="Current" />
    &gl;Property Id="3" ParentId="1" Name="Site" />
    &gl;Property Id="5" ParentId="3" Name="Features" />
  &gl;/ObjectPaths>
&gl;/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 ...
}
]
}

您可以看到 QueryObjectPaths 元素已添加了其他元素,并且返回的 JSON 对象不包含 Site 的属性,就像之前的结果一样。如果您同时需要属性和集合,则需要两个 Load 方法。

ctx.Load(ctx.Site);
ctx.Load(ctx.Site.Features);

但是,这有一个很大的缺点:请求和响应的大小会增加。

方法 请求大小(字节) 响应大小(字节)
属性 554 460
集合 719 2865
属性和集合 824 3201

Site 对象相对较小,属性和集合很少,但您可以看到这很容易失控。要控制这种膨胀,您需要使用 retrievals 参数来限制结果。

限制结果集

如上所示,Laod 方法的签名接受 Expressionparams 集合,这些 Expression 派生自 LambdaExpression

public sealed class Expression<TDelegate> : LambdaExpression

我将切换到 Web 对象以获得更多示例,并在下面展示一个简单的用法。

Web web = ctx.Site.RootWeb;
ctx.Load(web, w => w.Title);

第一个参数仍然是用于形成 ObjectPathClientObject 派生对象,但第二个参数是您在使用 Linq 时可能熟悉的普通 Lambda 表达式。将上面的与这个进行比较:

Web web = ctx.Site.RootWeb;
ctx.Load(web);

您可以看到,虽然请求大小略有增加,但结果却大大减少了。

方法 请求大小(字节) 响应大小(字节)
508 736
仅标题 698 297

因为retrievals是一个 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>,其中包含 WebList。可以像这样复制前面的示例:

Web web = ctx.Site.RootWeb;
var query = from l in web.Lists
                .Include(l => l.Title)
            select l;
var lists = ctx.LoadQuery(query);

处理 ListItems

尽管获取 WebList 的标题很有用,但在大多数情况下,客户端 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 语法来形成查询,包括 WhereRowLimit 元素。您还可以使用 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 方法之前,在任何 LoadLoadQuery 方法中使用的对象和属性创建的所有 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 日

© . All rights reserved.