Share via


使用 SharePoint Foundation 2010 托管客户端对象模型

**摘要:**学习如何使用 SharePoint Foundation 2010 托管客户端对象模型编写基于 .NET Framework 的应用程序,并从客户端访问 SharePoint 内容,而无需在运行 SharePoint Foundation 2010 的服务器上安装代码。

上次修改时间: 2015年3月9日

适用范围: Business Connectivity Services | Office 2010 | Open XML | SharePoint Designer 2010 | SharePoint Foundation 2010 | SharePoint Online | SharePoint Server 2010 | Visual Studio

本文内容
概述
使用 SharePoint Foundation 2010 托管客户端对象模型
客户端对象模型的工作原理
创建 Windows 控制台托管的客户端对象模型应用程序
SharePoint Foundation 2010 托管客户端对象模型概述
使用对象标识
修整结果集
创建和填充列表
使用 CAML 查询列表
使用 LINQ 筛选 Load 返回的子集合
使用 LoadQuery 方法
通过在 LoadQuery 中嵌套 Include 语句来提高性能
使用 LINQ 筛选 LoadQuery 返回的子集合
更新客户端对象
删除客户端对象
发现字段架构
访问大型列表
异步处理
其他资源

**供稿人:**Eric White,Microsoft Corporation

目录

  • 概述

  • 使用 SharePoint Foundation 2010 托管客户端对象模型

  • 客户端对象模型的工作原理

  • 创建 Windows 控制台托管的客户端对象模型应用程序

  • SharePoint Foundation 2010 托管客户端对象模型概述

  • 使用对象标识

  • 修整结果集

  • 创建和填充列表

  • 使用 CAML 查询列表

  • 使用 LINQ 筛选 Load 返回的子集合

  • 使用 LoadQuery 方法

  • 通过在 LoadQuery 中嵌套 Include 语句来提高性能

  • 使用 LINQ 筛选 LoadQuery 返回的子集合

  • 更新客户端对象

  • 删除客户端对象

  • 发现字段架构

  • 访问大型列表

  • 异步处理

  • 其他资源

概述

使用 SharePoint Foundation 2010 托管客户端对象模型,您可以设计客户端应用程序,以便无需在运行 Microsoft SharePoint Foundation 2010 的服务器上安装代码即可访问 SharePoint 内容。例如,您可以构建新类别的应用程序,其中包括编写基于 Microsoft .NET Framework 的应用程序、丰富的交互式 Web 部件、Microsoft Silverlight 应用程序以及在 SharePoint Web 部件中运行客户端的 ECMAScript(JavaScript、JScript) 应用程序。例如:

  • 一位工作组负责人创建了一个 SharePoint 网站,其中包含管理其工作组任务所需的许多列表。她希望以一种特殊方式更改这些列表,例如,基于 Open XML 电子表格更新任务分配和估计值,或者将项目从一个 SharePoint 列表移到另一个列表。她想编写一个小的自定义应用程序来帮助管理此任务。

  • 一家销售传统富客户端应用程序的软件公司希望将 SharePoint 文档库和列表集成到其应用程序中,并且他们希望这种集成是无缝的,甚至对用户是不可见的。

  • 一位 SharePoint 开发人员想要为 SharePoint 部署构建一个丰富 Web 部件,以便将列表内容放入其自定义 AJAX Web 代码中。他还想构建一个执行相同任务的更丰富的 Silverlight 应用程序。

这些人有哪些共同点?他们都可以使用 SharePoint Foundation 2010 托管客户端对象模型来实现他们的目标。SharePoint Foundation 2010 托管客户端对象模型允许您编写客户端代码以便在 SharePoint 网站中使用所有常见对象。通过该对象模型,您可以添加和删除列表,添加、更新和删除列表项,更改文档库中的文档,创建网站,管理项目权限,以及在页面中添加和删除 Web 部件。

以前,几乎没有什么选择。您可以使用 Web 服务与 SharePoint 列表和其他功能进行交互,但这并非易事。如果 Web 服务不提供必需的功能,您可以编写服务器端代码来提供新的 Web 服务(这是一项更为困难的任务)。有些 IT 部门不允许使用服务器端代码,或者只允许使用 IT 部门编写的代码,因此有时这并不可行。SharePoint Foundation 2010 托管客户端对象模型可实现新型应用程序,可以更方便地编写与 SharePoint 内容交互的客户端代码。

使用 SharePoint Foundation 2010 托管客户端对象模型

若要使用 SharePoint Foundation 2010 托管客户端对象模型(客户端对象模型),您需要编写基于 .NET Framework 的托管代码,这些代码使用与运行 SharePoint Foundation 的服务器上所用对象模型类似的 API。该客户端对象模型具有用于访问网站集信息、网站信息以及列表和列表项信息的类。

对于 Web 部件,您需要使用与 .NET Framework API 类似的 ECMAScript(JavaScript、JScript) 编程接口。对于 Silverlight,您需要使用通过客户端上的 .NET Framework 提供的一部分 API。尽管本文中的大部分信息都与 JavaScript 和 Silverlight API 有关,但本文着重介绍如何从基于 .NET Framework 的客户端应用程序使用 SharePoint Foundation 2010 托管客户端对象模型。

SharePoint Foundation 2010 托管客户端对象模型由两个程序集组成,这两个程序集共包含五个命名空间。如果您查看这些命名空间中提供的类,就会看到许多类。不必担心,其中的许多类都由对象模型内部使用。您只需要使用其中一部分,主要是能够与 SharePoint Foundation 服务器对象模型中的某些熟悉的类直接配对的类。

表 1. 客户端类和服务器端等效类

客户端

服务器

ClientContext

SPContext

Site

SPSite

Web

SPWeb

List

SPList

ListItem

SPListItem

Field

SPField

请注意,SharePoint Foundation 2010 托管客户端对象模型对网站集和网站使用与服务器对象模型相同的传统命名模式。Site 类代表网站集,Web 类代表网站。在使用这些类时,我更愿意指定这些变量的名称,使用变量名称来指示是网站集还是网站,但您必须使用 SiteWeb 类来声明这些变量。

以下示例演示了我如何命名这些变量。

ClientContext clientContext = new ClientContext(siteUrl);
Site siteCollection = clientContext.Site;
Web site = clientContext.Web;

客户端对象模型的工作原理

使用 SharePoint 内容的应用程序通过多种方式与 API 交互:调用方法并获取返回值,传递协作应用程序标记语言 (CAML) 查询并获取结果,以及设置或获取属性。使用 API 执行特定任务后,SharePoint Foundation 2010 托管客户端对象模型会将使用的这些 API 捆绑到 XML 并发送给运行 SharePoint Foundation 的服务器。服务器在收到该请求后,会调用服务器上适当的对象模型,收集响应,将响应组成 JavaScript 对象标注 (JSON),然后将 JSON 发送回 SharePoint Foundation 2010 托管客户端对象模型。客户端对象模型随后分析 JSON 并将结果作为 .NET Framework 对象(或 JavaScript 的 JavaScript 对象)提供给应用程序。下图演示了这些交互。

图 1. SharePoint Foundation 2010 托管客户端对象模型

0ebaeb17-ceb2-43a7-9ebe-22adc04b6137

必须注意,您需要控制 SharePoint Foundation 2010 托管客户端对象模型何时开始向服务器发送 XML 以及何时从服务器接收 JSON。

是否将对服务器的多次方法调用捆绑为单次调用由网络速度、网络延迟和所需的性能特性决定。如果 SharePoint Foundation 2010 托管客户端对象模型在每次调用方法时都与服务器交互,系统性能将会降低,网络流量会增大,从而导致系统不稳定。

正如我提到的那样,您可以明确控制 SharePoint Foundation 2010 托管客户端对象模型何时捆绑方法调用以及向服务器发送请求。在此过程中,在启动与服务器的交互之前,您必须明确指定希望从服务器检索哪些内容。这是 SharePoint Foundation 2010 托管客户端对象模型与 SharePoint Foundation 2010 对象模型的最大区别。但是在了解该模型之后,这就不再是什么难题了。开始了解这一区别的最简单方法是查看一个简单的应用程序。

创建 Windows 控制台托管的客户端对象模型应用程序

备注

我将示例代码用于 Windows 控制台应用程序,但您可以对其他应用程序类型使用同样的方法。

若要构建应用程序,您必须添加对以下两个程序集的引用:Microsoft.SharePoint.Client.dll 和 Microsoft.SharePoint.Client.Runtime.dll。安装 SharePoint Foundation 时将会在服务器上安装这两个程序集。这两个程序集位于以下目录中:

%ProgramFiles%\Common Files\Microsoft Shared\web server extensions\14\ISAPI

重要说明重要说明

对于 SharePoint Foundation Beta 和 Microsoft SharePoint Server 2010 Beta,您必须复制这两个程序集并将其放到开发客户端计算机上的方便位置。在设置使用 SharePoint Foundation 2010 托管客户端对象模型的项目时,必须通过浏览找到这两个程序集才能添加对它们的引用。

构建应用程序

  1. 启动 Microsoft Visual Studio 2010。

  2. 在"文件"菜单上,指向"新建",然后单击"项目"。

  3. 在"新建项目"对话框的"最近打开的模板"窗格中,展开"Visual C#",然后单击"Windows"。

  4. 在"最近打开的模板"窗格的右侧,单击"控制台应用程序"。

  5. 默认情况下,Visual Studio 会创建一个面向 .NET Framework 4 的项目,但您必须面向 .NET Framework 3.5。从"打开"对话框上部的列表中,选择".NET Framework 3.5"。

  6. 在"名称"框中,键入要用于项目的名称,例如 FirstClientApiApplication。

  7. 在"位置"框中,键入希望放置项目的位置。

    图 2. 在"新建项目"对话框中创建解决方案

    6fff7a0d-bf31-4042-acb2-72a16fce6e19

  8. 单击"确定"以创建解决方案。

添加对 Microsoft.SharePoint.Client 程序集和 Microsoft.SharePoint.Client.Runtime 程序集的引用

  1. 您在客户端对象模型应用程序中使用的类位于 Microsoft.SharePoint.Client.dll 和 Microsoft.SharePoint.Client.Runtime.dll中。在前面我已经提到,在添加引用之前,您必须将这些程序集从运行 SharePoint Foundation 的服务器复制到客户端开发计算机。

  2. 在"项目"菜单上,单击"添加引用"以打开"添加引用"对话框。

  3. 选择"浏览"选项卡,导航到放置 Microsoft.SharePoint.Client.dll 和 Microsoft.SharePoint.Client.Runtime.dll的位置。选择这两个 DLL,然后单击"确定",如图 3 所示。

    图 3. 添加对程序集的引用

    820cc11d-ae55-4acb-9cf5-8272117ce0df

向解决方案中添加示例代码

  1. 在 Visual Studio 中,将 Program.cs 源文件的内容替换为以下代码。

    using System;
    using Microsoft.SharePoint.Client;
    
    class DisplayWebTitle
    {
        static void Main()
        {
            ClientContext clientContext = new ClientContext("http://intranet.contoso.com");
            Web site = clientContext.Web;
            clientContext.Load(site);
            clientContext.ExecuteQuery();
            Console.WriteLine("Title: {0}", site.Title);
        }
    }
    
  2. ClientContext(String) 构造函数中的 URL 替换为 SharePoint 网站的 URL。生成并运行该解决方案。该示例将输出网站的标题。

与使用 SharePoint Foundation 服务器对象模型一样,您可以为想要访问的 SharePoint 网站创建上下文。然后,可以从该上下文中检索对该网站的引用。

调用 ExecuteQuery() 方法将会导致 SharePoint Foundation 2010 托管客户端对象模型向服务器发送请求。在应用程序调用 ExecuteQuery() 方法之前,不会产生网络流量。

该示例中非常重要的一点是,调用 Load() 方法实际上并不会加载任何内容,而是通知客户端对象模型,在应用程序调用 ExecuteQuery() 方法时,您需要加载 siteCollection 对象的属性值。

以下是与服务器的所有交互均需遵循的模型:

  1. 您将需要执行的操作告知 SharePoint Foundation 2010 托管客户端对象模型。这包括访问对象(例如,List 类、ListItem 类和 Web 类的对象)的属性值,要运行的 CAML 查询,以及要插入、更新或删除的对象(如 ListItem 对象)。

  2. 然后,您调用 ExecuteQuery() 方法。在调用 ExecuteQuery() 方法之前,不会发生任何网络流量。在调用该方法时,应用程序将只注册其请求。

从该示例中可以看出,最简单的方法是,先设置一个查询,然后执行该查询。这会导致客户端对象模型向服务器发送流量并接收来自服务器的响应。下一节将详细介绍该模型,并演示为何要这样设计,最后演示如何使用该模型构建应用程序。

SharePoint Foundation 2010 托管客户端对象模型概述

您必须检查客户端对象模型的以下几个特定方面:

  • 客户端对象模型用于最大限度减少网络流量的方法

  • 查询构造

  • 改进服务器性能的方法

  • 如何创建、更新和删除客户端对象

  • 如何处理大型列表

在深入探讨其中任一主题之前,我们先回顾一下有关对象标识的问题。

使用对象标识

对象标识背后的主要概念在于,客户端对象在调用 ExecuteQuery() 方法前后会引用 SharePoint Foundation 服务器对象模型中相应的对象。它们会通过多次调用 ExecuteQuery() 方法来继续引用该同一对象。

这意味着,在设置查询时,您可以使用客户端对象模型返回的对象来进一步设置查询,然后再调用 ExecuteQuery() 方法。这样,您就可以在开始向服务器发送流量以及从服务器接收流量之前,编写更复杂的查询。您可以在单个查询中执行更多有意义的操作,并消除网络流量。

以下示例获取 Announcements 列表对象,然后使用最简单的 CAML 查询检索该列表中的所有项。

using System;
using Microsoft.SharePoint.Client;

class Program
{
    static void Main()
    {
        ClientContext clientContext = new ClientContext("http://intranet.contoso.com");
        List list = clientContext.Web.Lists.GetByTitle("Announcements");
        CamlQuery camlQuery = new CamlQuery();
        camlQuery.ViewXml = "<View/>";
        ListItemCollection listItems = list.GetItems(camlQuery);
        clientContext.Load(list);clientContext.Load(listItems);
        clientContext.ExecuteQuery();
        foreach (ListItem listItem in listItems)
            Console.WriteLine("Id: {0} Title: {1}", listItem.Id, oListItem["Title"]);
    }
}

注意该示例中的顺序:

  1. 首先,该代码使用 GetByTitle() 方法获取 List 对象。记住,该 List 对象不包含任何数据;在应用程序调用 ExecuteQuery() 方法之前,该对象的任何属性中均不会有数据。

  2. 然后,该代码对 list 对象调用 GetItems() 方法,即使该 list 对象尚未填充数据。

  3. 最后,它对 list 对象和 listItems 对象调用 Load() 方法,然后再调用 ExecuteQuery() 方法。

这里面的关键点在于,客户端对象模型会记往 list 对象就是应用程序使用 GetByTitle() 方法初始化的对象,并且客户端对象模型应该在从 SharePoint 数据库检索 list 对象后,对该同一 list 对象执行 CAML 查询。从 ClientObject 类派生的任何类均具有这些语义。

另外,如上所述,在调用 ExecuteQuery() 方法后,您可以继续使用客户端对象设置其他查询。在以下示例中,代码将加载 list 对象并调用 ExecuteQuery() 方法。然后,它使用该 list 客户端对象调用 List.GetItems 方法,之后再次调用 ExecuteQuery() 方法。list 对象通过调用 ExecuteQuery() 方法来保留其标识。

using System;
using Microsoft.SharePoint.Client;

class Program
{
    static void Main()
    {
        ClientContext clientContext =
            new ClientContext("http://intranet.contoso.com");
        List list = clientContext.Web.Lists
            .GetByTitle("Announcements");
        clientContext.Load(list);
        clientContext.ExecuteQuery();
        Console.WriteLine("List Title: {0}", list.Title);
        CamlQuery camlQuery = new CamlQuery();
        camlQuery.ViewXml = "<View/>";
        ListItemCollection listItems = list.GetItems(camlQuery);
        clientContext.Load(listItems);
        clientContext.ExecuteQuery();
        foreach (ListItem listItem in listItems)
            Console.WriteLine("Id: {0} Title: {1}",
                oListItem.Id, listItem["Title"]);
    }
}

有些属性和方法会返回不是派生自 ClientObject 类的对象或值类型。仅当方法和属性返回客户端对象或客户端对象集合时,才能从使用客户端对象标识访问这些方法和属性中获益。例如,有些类(如 FieldUrlValue 类和 FieldLookupValue 类)是从 ClientValueObject 类派生的,只有在调用 ExecuteQuery() 方法之后,您才能使用返回这些类型的属性。有些属性会返回字符串或整数之类的 .NET Framework 类型,在调用 ExecuteQuery() 方法之前,您也不能使用返回这些类型的属性或方法。因为在 ExecuteQuery() 调用中填充任意属性的值之前您不能使用这些值,所以也无法查找列表中的项,并使用该项的某个字段值选择其他查询中的项。如果尝试在 ExecuteQuery() 方法填充某个属性之前使用该属性,客户端对象模型将引发 PropertyOrFieldNotInitializedException 异常。

警告注释警告

客户端对象标识只对单个 ClientContext 对象有效。如果您将另一个 ClientContext 对象初始化到同一 SharePoint 网站,则无法将来自一个客户端上下文的客户端对象用于另一个客户端上下文。

本文中后面的几个示例都将使用对象标识行为。

备注

该示例不执行任何错误处理。如果 Announcements 列表不存在,客户端对象模型将在调用 ExecuteQuery() 方法时引发异常。如果您编写的代码在所请求的对象不存在时可能会失败,则应准备好捕获异常。

修整结果集

SharePoint Foundation 通常部署在具有数千名用户的组织中。在构建通过网络访问 SharePoint Foundation 的应用程序时,应使其占用最少的网络流量。客户端对象模型可通过多种方法帮助您实现这一目标。最简单的方法是使用 lambda 表达式来明确指定客户端对象模型应返回到应用程序的属性。

以下示例演示了如何指定在客户端对象模型加载网站对象时,它只能加载 Title 属性和 Description 属性。这可以减小从服务器发送回客户端的 JSON 响应的大小。

using System;
using Microsoft.SharePoint.Client;

class Program
{
    static void Main()
    {
        ClientContext clientContext =
            new ClientContext("http://intranet.contoso.com");
        Web site = clientContext.Web;
        clientContext.Load(site,
            s => s.Title,
            s => s.Description);
        clientContext.ExecuteQuery();
        Console.WriteLine("Title: {0} Description: {1}",
            site.Title, site.Description);
    }
}

默认情况下,如果在对 Load() 方法的调用中不包含这些 lambda 表达式,它将加载更多的属性(但并非全部)。前两个示例在未指定要加载的属性的情况下调用了 Load() 方法,因此服务器返回的 JSON 数据包稍大于实际所需大小。尽管在这些小型示例中,这不会产生太大区别,但是在加载数千个列表项时,仔细指定所需属性将可以减少网络流量。

使用 lambda 表达式,您可以指定 Load() 方法的属性列表。减少网络流量并不是您从客户端对象模型使用 lambda 表达式所获得的唯一好处。下文中将介绍如何使用 lambda 表达式筛选结果集。

接下来,我将演示一个创建列表然后向其中添加内容的示例。该示例将提供本文其余部分将使用的示例内容。

创建和填充列表

以下示例将创建一个列表,然后向其中添加字段和项。

using System;
using Microsoft.SharePoint.Client;

class Program
{
    static void Main()
    {
        ClientContext clientContext =
            new ClientContext("http://intranet.contoso.com");
        Web site = clientContext.Web;

        // Create a list.
        ListCreationInformation listCreationInfo =
            new ListCreationInformation();
        listCreationInfo.Title = "Client API Test List";
        listCreationInfo.TemplateType = (int)ListTemplateType.GenericList;
        List list = site.Lists.Add(listCreationInfo);

        // Add fields to the list.
        Field field1 = list.Fields.AddFieldAsXml(
            @"<Field Type='Choice'
                     DisplayName='Category'
                     Format='Dropdown'>
                <Default>Specification</Default>
                <CHOICES>
                  <CHOICE>Specification</CHOICE>
                  <CHOICE>Development</CHOICE>
                  <CHOICE>Test</CHOICE>
                  <CHOICE>Documentation</CHOICE>
                </CHOICES>
              </Field>",
            true, AddFieldOptions.DefaultValue);
        Field field2 = list.Fields.AddFieldAsXml(
            @"<Field Type='Number'
                     DisplayName='Estimate'/>",
            true, AddFieldOptions.DefaultValue);

        // Add some data.
        ListItemCreationInformation itemCreateInfo =
            new ListItemCreationInformation();
        ListItem listItem = list.AddItem(itemCreateInfo);
        listItem["Title"] = "Write specs for user interface.";
        listItem["Category"] = "Specification";
        listItem["Estimate"] = "20";
        listItem.Update();

        listItem = list.AddItem(itemCreateInfo);
        listItem["Title"] = "Develop proof-of-concept.";
        listItem["Category"] = "Development";
        listItem["Estimate"] = "42";
        listItem.Update();
        
        listItem = list.AddItem(itemCreateInfo);
        listItem["Title"] = "Write test plan for user interface.";
        listItem["Category"] = "Test";
        listItem["Estimate"] = "16";
        listItem.Update();

        listItem = list.AddItem(itemCreateInfo);
        listItem["Title"] = "Validate SharePoint interaction.";
        listItem["Category"] = "Test";
        listItem["Estimate"] = "18";
        listItem.Update();

        listItem = list.AddItem(itemCreateInfo);
        listItem["Title"] = "Develop user interface.";
        listItem["Category"] = "Development";
        listItem["Estimate"] = "18";
        listItem.Update();

        clientContext.ExecuteQuery();
    }
}

在很多情况下,如果您可以创建客户端对象,则应用程序可以调用 Add 方法(该方法将用于指定创建信息的对象作为参数)。该示例演示了如何使用 ListCreationInformation 类创建 List 对象,以及如何使用 ListItemCreationInformation 类创建 ListItem 对象。您经常会在实例化创建信息类之后设置其属性。您会看到,该代码设置了 ListItemCreationInformation 对象的 Title 属性和 TemplateType 属性。请注意,若要创建列表,需要调用 Add() 方法,但是若要创建 ListItem 对象,需要调用 AddItem() 方法。Add() 方法会在集合中创建一个列表,而 AddItem() 方法会创建单个列表项。

在列表中创建字段同样不会使用 Add 方法,这是因为在您创建字段时,您并不是在真正地创建 Field 类的实例,而是创建从 Field 类派生的类的实例。有许多可用于这些派生类的选项,而使用 Add 方法会大大增加 FieldCreationInformation 类的设计的复杂性。因此,客户端对象模型不包括这样的类。而创建字段最简单的方法是指定一些用于定义字段的 XML,然后将这些 XML 传递给 AddFieldAsXml() 方法。有一种 Add() 方法可用来创建字段,但该方法不采用 FieldCreationInformation 对象而是采用另一个 Field 对象作为参数,以将该参数用作要创建的字段的原型。在有些情况下,这非常有用。

提示提示

本文的发现字段架构一节演示了一种简单方法,可用于发现必须为要创建的字段指定的 XML。

当然,请注意,在应用程序调用 ExecuteQuery() 方法之前,并不会有任何对象实际添加到 SharePoint 数据库中。

该示例中还有一个值得关注的地方。请注意,在调用 AddItem() 方法后,该示例会设置三个索引属性。代码会设置之前添加到列表中的字段的值。设置这些属性后,应用程序必须调用 Update() 方法,通知客户端对象模型这些对象已被修改。如果不这样做,客户端对象模型将无法正常工作。您会看到 Update() 方法,在后面演示如何修改现有客户端对象的示例中,将会用到该方法。

现在您已经有了一些数据,我们来探讨一些查询和更改这些数据的有趣方法。

使用 CAML 查询列表

以下示例演示了如何使用 CAML 查询在上例中创建的列表。该示例将输出我们的测试列表中的 Development 项。

using System;
using Microsoft.SharePoint.Client;

class Program
{
    static void Main(string[] args)
    {
        ClientContext clientContext = new ClientContext("http://intranet.contoso.com");
        List list = clientContext.Web.Lists.GetByTitle("Client API Test List");
        CamlQuery camlQuery = new CamlQuery();
        camlQuery.ViewXml =
            @"<View>
                <Query>
                  <Where>
                    <Eq>
                      <FieldRef Name='Category'/>
                      <Value Type='Text'>Development</Value>
                    </Eq>
                  </Where>
                </Query>
                <RowLimit>100</RowLimit>
              </View>";
        ListItemCollection listItems = list.GetItems(camlQuery);
        clientContext.Load(
             listItems,
             items => items
                 .Include(
                     item => item["Title"],
                     item => item["Category"],
                     item => item["Estimate"]));
        clientContext.ExecuteQuery();
        foreach (ListItem listItem in listItems)
        {
            Console.WriteLine("Title: {0}", listItem["Title"]);
            Console.WriteLine("Category: {0}", listItem["Category"]);
            Console.WriteLine("Estimate: {0}", listItem["Estimate"]);
            Console.WriteLine();
        }
    }
}

该示例将生成以下输出。

Title: Develop proof-of-concept.
Category: Development
Estimate: 42

Title: Develop user interface.
Category: Development
Estimate: 18

您可能已注意到,在该示例中指定的 lambda 表达式与修整结果集一节所提供的示例中的 lambda 表达式之间的区别。必须使用 Include() 扩展方法来指定要为所加载的集合中的每个项加载的属性。Lambda 表达式的 items 参数属于 ListItemCollection 类型,该类型当然不包含允许我们指定为集合中的项加载哪些属性的索引属性。应调用 Include() 扩展方法,它允许我们指定要加载该子集合的哪些参数。Include() 扩展方法中 lambda 表达式的参数的类型与集合项的类型相同。因此,您可以指定要为集合中的每个项加载的属性。

再次说明,完全没有必要了解 lambda 表达式的这种用法的确切语义,只需记住以下两点编码习惯即可:

如果要求客户端对象模型加载客户端对象(而不是客户端对象集合)的某些属性,则应在您直接添加到 Load() 方法的 lambda 表达式中指定这些属性。

clientContext.Load(site,
    s => s.Title,
    s => s.Description);

如果要求客户端对象模型加载客户端对象集合中每个项目的特定属性,应使用 Include() 扩展方法,并将指定所需属性的 lambda 表达式传递给 Include() 方法。

clientContext.Load(
    listItems,
    items => items.Include(
        item => item["Title"],
        item => item["Category"],
        item => item["Estimate"]));

使用 LINQ 筛选 Load 返回的子集合

由于 Include() 扩展方法会返回 IQueryable<T>,因此您可以从 Include() 方法链接到 IQueryable<T>.Where 扩展方法。这可以提供一种简单的方法来筛选结果集。只有在查询除 ListItem 对象集合以外的客户端对象集合时,才应使用此功能。这是因为尽管您可以使用该方法来筛选 ListItem 对象集合,但使用 CAML 可以提高性能。这一点很重要,因此必须重申:

警告注释警告

在查询 ListItem 对象时,永远不要使用 IQueryable<T>.Where 扩展方法。原因是,客户端对象模型会首先评估 CAML 查询的结果,检索这些结果,然后使用 LINQ 筛选生成的集合。如果您使用 LINQ 而不是 CAML 来筛选大型列表,客户端对象模型将会尝试在使用 LINQ 进行筛选之前检索列表中的所有项,并发出需要占用太多系统资源的查询,或者查询失败。除非您知道客户端对象模型的内部工作原理,否则无法了解其中的原因。在查询列表项时,必须 使用 CAML。

以下示例将查询处于非隐藏状态的所有列表的客户端对象模型。请注意,您必须包含一个用于 System.Linq 命名空间的 using 指令。

using System;
using System.Linq;
using Microsoft.SharePoint.Client;
 
class Program
{
    static void Main(string[] args)
    {
        ClientContext clientContext =
            new ClientContext("http://intranet.contoso.com");
        ListCollection listCollection = clientContext.Web.Lists;
        clientContext.Load(
            listCollection,
            lists => lists
                .Include(
                    list => list.Title,
                    list => list.Hidden)
                . Where(list => ! list.Hidden)
             );
        clientContext.ExecuteQuery();
        foreach (var list in listCollection)
            Console.WriteLine(list.Title);
    }
}

在我的服务器上,该示例将生成以下输出。

Announcements
Calendar
Client API Test List
Content and Structure Reports
Customized Reports
Eric's ToDo List
Eric's Wiki
Form Templates
Links
Reusable Content
Shared Documents
Site Assets
Site Collection Documents
Site Collection Images
Site Pages
Style Library
Tasks
Team Discussion
Workflow Tasks

使用 LoadQuery 方法

LoadQuery() 方法在功能上与 Load() 方法类似,只是在某些特定情况下,客户端对象模型可以更高效地处理查询和使用内存。它还允许更灵活的编程风格。

LoadQuery() 方法的语义与 Load() 方法不同。Load() 方法使用来自服务器的数据填充客户端对象(或客户端对象集合),而 LoadQuery() 方法会填充并返回一个新集合。这意味着您可以多次查询同一对象集合,并为每次查询保留单独的结果集。例如,您可以查询项目列表中分配给某人的所有项,并单独查询估计小时数大于特定阈值的所有项,然后同时访问这两个结果集。您还可以让这些集合超出范围,使其符合垃圾回收的条件。仅当客户端上下文变量本身超出范围时,使用 Load() 方法加载的集合才符合垃圾回收条件。除了这些区别外,LoadQuery() 方法提供的功能与 Load() 方法相同。

以下示例使用 LoadQuery() 方法检索网站中所有列表的列表。

using System;
using System.Collections.Generic;
using Microsoft.SharePoint.Client;

class Program
{
    static void Main(string[] args)
    {
        ClientContext clientContext =
            new ClientContext("http://intranet.contoso.com");
        Web site = clientContext.Web;
        ListCollection lists = site.Lists;
        IEnumerable<List> newListCollection = clientContext.LoadQuery(
            lists.Include(
                list => list.Title,
                list => list.Id,
                list => list.Hidden));
        clientContext.ExecuteQuery();
        foreach (List list in newListCollection)
            Console.WriteLine("Title: {0} Id: {1}",
                list.Title.PadRight(40), list.Id.ToString("D"));
    }
}

请注意,LoadQuery() 方法会返回一个新的列表集合,您可以循环访问该集合。新列表集合的类型为 IEnumerable<List> 而不是 ListCollection

必须注意 LoadQuery() 方法语义的一个方面。在前面的示例中,在 ExecuteQuery() 方法返回后,原始列表变量并没有填充其属性值。如果您希望填充该列表,则必须明确调用 Load() 方法,并指定要加载的属性。

通过在 LoadQuery 中嵌套 Include 语句来提高性能

在调用 LoadQuery() 方法时,您可以指定要加载的多级属性。这使客户端对象模型能够减少必须调用运行 SharePoint Foundation 的服务器以检索所需数据的次数,从而优化其对运行 SharePoint Foundation 的服务器的访问。以下查询将从网站检索所有列表以及从每个列表检索所有字段。然后将这些列表和字段输出到控制台,以指示每个列表或字段是否处于隐藏状态。

using System;
using System.Collections.Generic;
using Microsoft.SharePoint.Client;

class Program
{
    static void Main()
    {
        ClientContext clientContext =
            new ClientContext("http://intranet.contoso.com");
        IEnumerable<List> lists = clientContext.LoadQuery(
            clientContext.Web.Lists.Include(
                list => list.Title,
                list => list.Hidden,
                list => list.Fields.Include(
                    field => field.Title,
                    field => field.Hidden)));
        clientContext.ExecuteQuery();
        foreach (List list in lists)
        {
            Console.WriteLine("{0}List: {1}",
                list.Hidden ? "Hidden " : "", list.Title);
            foreach (Field field in list.Fields)
                Console.WriteLine("  {0}Field: {1}",
                    field.Hidden ? "Hidden " : "",
                    field.Title);
        }
    }
}

该方法使客户端对象模型的服务器部件比在以下情况下更加高效:应用程序首先加载一系列列表,然后加载每个列表的字段。

使用 LINQ 筛选 LoadQuery 返回的子集合

LoadQuery() 方法会将 IQueryable<T> 类型的对象作为其参数,这使您能够编写 LINQ 查询来取代 CAML 以筛选结果。该示例返回未处于隐藏状态的所有文档库的集合。

using System;
using System.Linq;
using System.Collections.Generic;
using Microsoft.SharePoint.Client;
 
class Program
{
    static void Main(string[] args)
    {
        ClientContext clientContext =
            new ClientContext("http://intranet.contoso.com");
        ListCollection listCollection = clientContext.Web.Lists;
        IEnumerable<List> hiddenLists = clientContext.LoadQuery(
            listCollection
                . Where(list => !list.Hidden &&
                      list.BaseType == BaseType.DocumentLibrary));
        clientContext.ExecuteQuery();
        foreach (var list in hiddenLists)
            Console.WriteLine(list.Title);
    }
}

更新客户端对象

使用客户端对象模型更新客户端对象非常简单。只需检索对象、更改属性,对要更改的每个对象调用 Update 方法,然后调用 ExecuteQuery() 方法即可。以下示例将修改 客户端 API 测试列表中的项,将所有开发项的估计值提高 50%(一种常见操作)。

using System;
using Microsoft.SharePoint.Client;

class Program
{
    static void Main(string[] args)
    {
        ClientContext clientContext = new ClientContext("http://intranet.contoso.com");
        List list = clientContext.Web.Lists.GetByTitle("Client API Test List");
        CamlQuery camlQuery = new CamlQuery();
        camlQuery.ViewXml =
            @"<View>
                <Query>
                  <Where>
                    <Eq>
                      <FieldRef Name='Category'/>
                      <Value Type='Text'>Development</Value>
                    </Eq>
                  </Where>
                </Query>
                <RowLimit>100</RowLimit>
              </View>";
        ListItemCollection listItems = list.GetItems(camlQuery);
        clientContext.Load(
             listItems,
             items => items.Include(
                 item => item["Category"],
                 item => item["Estimate"]));
        clientContext.ExecuteQuery();
        foreach (ListItem listItem in listItems)
        {
            listItem["Estimate"] = (double)listItem["Estimate"] * 1.5;
            listItem.Update();
        }
        clientContext.ExecuteQuery();
    }
}

删除客户端对象

删除客户端对象也非常简单。但是,从客户端对象集合中删除客户端对象时有一个非常重要的注意事项。您不能通过循环访问集合来删除对象。删除第一个对象后,客户端对象集合的迭代器随即失效。迭代器可能会引发异常,或者安静地完成,而不访问集合中的所有项目。您必须使用 ToList 方法将集合具体化到 List<T> 中,然后循环访问该列表以删除客户端对象。

以下示例将从 客户端 API 测试列表中删除测试项。它演示了在循环访问集合之前如何使用 ToList 方法具体化该集合:

using System;
using System.Linq;
using System.Collections.Generic;
using Microsoft.SharePoint.Client;

class Program
{
    static void Main(string[] args)
    {
        ClientContext clientContext = new ClientContext("http://intranet.contoso.com");
        List list = clientContext.Web.Lists.GetByTitle("Client API Test List");
        CamlQuery camlQuery = new CamlQuery();
        camlQuery.ViewXml =
            @"<View>
                <Query>
                  <Where>
                    <Eq>
                      <FieldRef Name='Category'/>
                      <Value Type='Text'>Test</Value>
                    </Eq>
                  </Where>
                </Query>
                <RowLimit>100</RowLimit>
              </View>";
        ListItemCollection listItems = list.GetItems(camlQuery);
        clientContext.Load(
             listItems,
             items => items.Include(
                 item => item["Title"]));
        clientContext.ExecuteQuery();
        foreach (ListItem listItem in listItems.ToList())
            listItem.DeleteObject();
        clientContext.ExecuteQuery();
    }
}

以下代码示例演示了错误的方法。

clientContext.Load(
    listItems,
    items => items.Include(
        item => item["Title"]));
clientContext.ExecuteQuery();

// The ToList() method call is removed in the following line.
foreach (ListItem listItem in listItems)  
    listItem.DeleteObject();

clientContext.ExecuteQuery();

最后,为了清理客户端 API 测试列表,在下面提供了一个示例,以删除列表和列表项。

using System;
using Microsoft.SharePoint.Client;

class DisplayWebTitle
{
    static void Main()
    {
        ClientContext clientContext =
            new ClientContext("http://intranet.contoso.com");
        clientContext.Web.Lists.GetByTitle("Client API Test List")
            .DeleteObject();
        clientContext.ExecuteQuery();
    }
}

发现字段架构

如上所述,本节演示了一种发现 XML 架构的简单方法,XML 架构可用于创建列表中所需的字段。首先,在 SharePoint 网站上,创建一个包含按所需方式配置的列的列表。然后,可以使用以下示例输出创建这些字段的 XML。

以下示例将输出我添加到 客户端 API 测试列表中的字段的字段架构。

using System;
using System.Linq;
using System.Xml.Linq;
using Microsoft.SharePoint.Client;

class Program
{
    static void Main(string[] args)
    {
        ClientContext clientContext =
            new ClientContext("http://intranet.contoso.com");
        List list = clientContext.Web.Lists
            .GetByTitle("Client API Test List");
        clientContext.Load(list);
        FieldCollection fields = list.Fields;
        clientContext.Load(fields);
        clientContext.ExecuteQuery();
        foreach (var f in fields)
        {
            XElement e = XElement.Parse(f.SchemaXml);
            string name = (string)e.Attribute("Name");
            if (name == "Category" || name == "Estimate")
            {
                e.Attributes("ID").Remove();
                e.Attributes("SourceID").Remove();
                e.Attributes("ColName").Remove();
                e.Attributes("RowOrdinal").Remove();
                e.Attributes("StaticName").Remove();
                Console.WriteLine(e);
                Console.WriteLine("===============");
            }
        }
    }
}

当您在使用创建和填充列表一节中的示例程序创建列表后运行该代码时,它将生成以下输出。

<Field Type="Choice" DisplayName="Category" Format="Dropdown" Name="Category">
  <Default>Specification</Default>
  <CHOICES>
    <CHOICE>Specification</CHOICE>
    <CHOICE>Development</CHOICE>
    <CHOICE>Test</CHOICE>
    <CHOICE>Documentation</CHOICE>
  </CHOICES>
</Field>
===============
<Field Type="Number" DisplayName="Estimate" Name="Estimate" />
===============

该示例将删除不是创建字段所需的属性。

访问大型列表

SharePoint 开发指南指示,您不应尝试在单个查询中检索 2000 个以上的项目。如果您的应用程序中可能会出现这种情况,可以考虑在 CAML 查询中使用 RowLimit 元素,以限制客户端对象模型可以为应用程序检索的数据量。有时您必须访问可能包含 2000 个以上项目的列表中的所有项目。如果必须如此,则最好进行分页,一次查看 2000 个项目。本节介绍了使用 ListItemCollectionPosition 属性进行分页的方法。

using System;
using System.Linq;
using Microsoft.SharePoint.Client;

class Program
{
    static void Main()
    {
        ClientContext clientContext =
            new ClientContext("http://intranet.contoso.com");

        List list = clientContext.Web.Lists
            .GetByTitle("Client API Test List");

        // First, add 20 items to Client API Test List so that there are
        // enough records to show paging.
        ListItemCreationInformation itemCreateInfo =
            new ListItemCreationInformation();
        for (int i = 0; i < 20; i++)
        {
            ListItem listItem = list.AddItem(itemCreateInfo);
            listItem["Title"] = String.Format("New Item #{0}", i);
            listItem["Category"] = "Development";
            listItem["Estimate"] = i;
            listItem.Update();
        }
        clientContext.ExecuteQuery();

        // This example shows paging through the list ten items at a time.
        // In a real-world scenario, you would want to limit a page to
        // 2000 items.
        ListItemCollectionPosition itemPosition = null;
        while (true)
        {
            CamlQuery camlQuery = new CamlQuery();
            camlQuery.ListItemCollectionPosition = itemPosition;
            camlQuery.ViewXml =
                @"<View>
                    <ViewFields>
                      <FieldRef Name='Title'/>
                      <FieldRef Name='Category'/>
                      <FieldRef Name='Estimate'/>
                    </ViewFields>
                    <RowLimit>10</RowLimit>
                  </View>";
            ListItemCollection listItems = list.GetItems(camlQuery);
            clientContext.Load(listItems);
            clientContext.ExecuteQuery();
            itemPosition = listItems.ListItemCollectionPosition;
            foreach (ListItem listItem in listItems)
                Console.WriteLine("  Item Title: {0}", listItem["Title"]);
            if (itemPosition == null)
                break;
            Console.WriteLine(itemPosition.PagingInfo);
            Console.WriteLine();
        }
    }
}

该示例将生成以下输出:

  Item Title: Write specs for user interface.
  Item Title: Develop proof-of-concept.
  Item Title: Write test plan for user interface.
  Item Title: Validate SharePoint interaction.
  Item Title: Develop user interface.
  Item Title: New Item #0
  Item Title: New Item #1
  Item Title: New Item #2
  Item Title: New Item #3
  Item Title: New Item #4
Paged=TRUE&p_ID=10

  Item Title: New Item #5
  Item Title: New Item #6
  Item Title: New Item #7
  Item Title: New Item #8
  Item Title: New Item #9
  Item Title: New Item #10
  Item Title: New Item #11
  Item Title: New Item #12
  Item Title: New Item #13
  Item Title: New Item #14
Paged=TRUE&p_ID=20

  Item Title: New Item #15
  Item Title: New Item #16
  Item Title: New Item #17
  Item Title: New Item #18
  Item Title: New Item #19

异步处理

如果您要构建必须附加到可能不可用的 SharePoint 网站的应用程序,或者您必须定期调用可能要花很长时间的查询,则应考虑使用异步处理。这样,当查询在单独的线程中执行时,您的应用程序能够继续响应用户。在主线程中,您可以设置一个计时器。如果查询所花时间长于所需阈值,该计时器可通知您,以便您可以告诉用户查询状态,并在查询最终完成时显示结果。

客户端对象模型的 JavaScript 版本和 Silverlight 版本(在修改用户界面时)都使用异步处理。SharePoint 2010 SDK 中的主题Data Retrieval Overview包含有关如何通过 JavaScript 和 Silverlight 使用异步处理的示例。

在构建基于 .NET Framework 的传统应用程序(如 Windows 窗体或 WPF 应用程序)时,可能需要使用异步处理。以下示例使用 BeginInvoke 方法异步执行查询。请注意,该代码会将一个语句式 lambda 表达式传递给 BeginInvoke 方法,这样便可方便地创建此模式,因为语句式 lambda 表达式可以引用该表达式所在方法中的自动变量。您可以看到语句式 lambda 表达式可以访问 clientContext 变量和 newListCollection 变量。Microsoft Visual C# 闭包使该语言能够按您预期的方式工作。

using System;
using System.Collections.Generic;
using Microsoft.SharePoint.Client;

class Program
{
    static void Main(string[] args)
    {
        AsynchronousAccess asynchronousAccess = new AsynchronousAccess();
        asynchronousAccess.Run();
        Console.WriteLine("Before exiting Main");
        Console.WriteLine();
        Console.WriteLine("In a real application, the application can");
        Console.WriteLine("continue to be responsive to the user.");
        Console.WriteLine();
        Console.ReadKey();
    }
}

class AsynchronousAccess
{
    delegate void AsynchronousDelegate();

    public void Run()
    {
        Console.WriteLine("About to start a query that will take a long time.");
        Console.WriteLine();
        ClientContext clientContext =
            new ClientContext("http://intranet.contoso.com");
        ListCollection lists = clientContext.Web.Lists;
        IEnumerable<List> newListCollection = clientContext.LoadQuery(
            lists.Include(
                list => list.Title));
        AsynchronousDelegate executeQueryAsynchronously =
            new AsynchronousDelegate(clientContext.ExecuteQuery);
        executeQueryAsynchronously.BeginInvoke(
            arg =>
            {
                clientContext.ExecuteQuery();
                Console.WriteLine("Long running query completed.");
                foreach (List list in newListCollection)
                    Console.WriteLine("Title: {0}", list.Title);
            }, null);
    }
}

该示例将生成以下输出。

About to start a query that will take a long time.

Before exiting Main

In a real application, the application can
continue to be responsive to the user.

Long running query completed.
Title: Announcements
Title: Cache Profiles
Title: Calendar
Title: Client API Test List
Title: Content and Structure Reports
Title: Content type publishing error log
Title: Converted Forms
Title: Customized Reports
Title: Eric's ToDo List
Title: Eric's Wiki
Title: Form Templates
Title: Links

其他资源

有关详细信息,请参阅以下资源: