将代码从 SharePoint 客户端对象模型 (CSOM) 升级到 PnP 库

在 SharePoint 经典解决方案中,很可能已使用适用于 .NET Framework 的客户端对象模型 (CSOM) 作为使用 SharePoint 数据的主要客户端库。 CSOM 已推出多年,其不同风格面向不同版本的 SharePoint 本地和 SharePoint Online。 自 2021 年以来,Microsoft发布了面向 .NET Standard 2.0 的新版 CSOM for SharePoint Online,因此可以在 .NET Framework 4.5+、.NET Core 2.0+和 .NET 5.0/6.0/7.0/8.0 中引用。

重要

本文指的是所谓的 PnP 组件、示例和/或工具,它们是由提供支持的活动社区支持的开源资产。 没有来自 Microsoft 的官方支持渠道的开放源代码工具支持的 SLA。 但是,这些组件或示例使用Microsoft Microsoft支持的现成 API 和功能。

如果愿意,可以观看以下视频,而不是阅读整个文章,你仍然可以将其视为更详细的参考。

使用 PnP 库简化 SharePoint 解决方案设计和实现

若要使用 CSOM,只需引用 Microsoft.SharePointOnline.CSOM NuGet 包,创建 ClientContext 对象,配置身份验证,然后开始使用 SharePoint Online 数据。 对于身份验证,在 CSOM 中,过去支持 SharePointOnlineCredentials 类型,以便通过 SharePoint Online 利用用户名和密码身份验证。 但是,由于Microsoft切换到新式身份验证时,用户名和密码身份验证不再受支持,因此应依赖于 OAuth 和新式身份验证。 在以下代码摘录中,可以看到通过 Microsoft 身份验证库 (MSAL) 使用普通 CSOM 与新式身份验证的示例。

using Microsoft.SharePoint.Client;
using Microsoft.Identity.Client;

var clientId = "<client-Id>";
var tenantId = "<tenant-Id>";
var authority = $"https://login.microsoftonline.com/{tenantId}/";
var redirectUri = "http://localhost";

var siteUrl = new Uri("https://contoso.sharepoint.com/sites/TargetSite");

var accessToken = await AcquireTokenAsync(siteUrl);

using (var context = new ClientContext(siteUrl))
{
    context.ExecutingWebRequest += async (sender, e) =>
    {
        // Insert the access token in the request
        e.WebRequestExecutor.RequestHeaders["Authorization"] = "Bearer " + accessToken;
    };

    // Read web properties
    var web = context.Web;
    context.Load(web, w => w.Id, w => w.Title);
    await context.ExecuteQueryAsync();

    Console.WriteLine($"{web.Id} - {web.Title}");

    // Retrieve a list by title together with selected properties
    var documents = web.Lists.GetByTitle("Documents");
    context.Load(documents, d => d.Id, d => d.Title);
    await context.ExecuteQueryAsync();

    Console.WriteLine($"{documents.Id} - {documents.Title}");

    // Retrieve the top 10 items from the list
    var query = CamlQuery.CreateAllItemsQuery(10);
    var items = documents.GetItems(query);
    context.Load(items);
    await context.ExecuteQueryAsync();

    // Browse through all the items
    foreach (var i in items)
    {
        Console.WriteLine($"{i.Id} - {i["Title"]}");
    }     
}

AcquireTokenAsync 方法依赖于 MSAL 来通过交互式身份验证检索访问令牌。 方法的实现在此上下文中并不基本,但可以在 与本文关联的 CSOM 的示例中找到它。

在后续部分中,你将了解如何使用一些新式 .NET 库(如 PnP 框架和 PnP Core SDK)提高代码的质量、可读性和可维护性。

PnP 框架库简介

PnP 框架库 (于 2021 年 1 月发布为 1.0) 是 CSOM 和名为 PnP Sites Core 的旧库的演变, (现已停用并存档) 。 PnP 框架库面向 .NET Standard 2.0 和 .NET 5.0/6.0/7.0,因此你可以在 Windows、Linux、MacOS) 的任何 (平台上以及任何基于云的服务(如 Azure 应用服务、Azure Functions、容器等)上使用它。

根据设计,此库仅支持 SharePoint Online (SPO) 。 如果要使用 .NET 为 SharePoint Online 开发新式解决方案,并且希望具有类似于 CSOM 的编码风格,请务必安装 PnP.Framework NuGet 包并使用它。

从功能的角度来看,PnP 框架是基于 CSOM 构建的一组扩展,它是跨平台的,它使用新的客户端对象模型 (CSOM) SPO for .NET Standard。 它主要面向 OAuth 2.0 的新式身份验证,但 ACS 中仍支持“现有”应用的仅限应用模式。

默认情况下,PnP 框架库使用名为 PnP 命令行管理程序的多租户 Azure Active Directory 应用程序,应使用以下 PnP PowerShell 语法在租户中注册该应用程序。

Register-PnPManagementShellAccess

这将启动一个登录流,要求你同意该应用程序。 请注意,每个租户只需要 -一次。 需要具有适当的访问权限才能在 Azure AD 中同意应用程序。

但是,在本文中,你将依赖具有自定义 AAD 应用程序的新式身份验证,必须使用该应用程序注册并配置为桌面身份验证 http://localhost ,作为受支持的重定向 URI。

重要

若要详细了解如何使用新式身份验证和 AAD 而不是 ACS,请阅读将 SharePoint 应用程序从 Azure 访问控制服务升级到 Azure Active Directory 一文。

现在,让我们创建一个 .NET 控制台应用程序,以使用 PnP 框架库使用一些 SharePoint Online 数据。 首先,需要添加对 PnP.Framework nuget 包的引用。 在 Visual Studio 中,只需使用 UI 添加包引用。 在 Visual Studio Code 中,需要执行以下命令。

dotnet add package PnP.Framework

完成此操作后,项目不仅将引用 PnP 框架库,还会引用 CSOM 库,该库由 PnP Framework 内部使用。 在以下代码示例中,你可以看到如何使用 PnP 框架连接到 SPO 并使用某些数据,使用 AuthenticationManager 类的实例和 OAuth 2.0 的新式身份验证。

using Microsoft.SharePoint.Client;
using PnP.Framework;

var clientId = "<client-Id>";
var tenantId = "<tenant-Id>";
var redirectUrl = "<redirect-Url>";
var siteUrl = "<target-Site-Url>";

// Create an instance of the AuthenticationManager type
var authManager = AuthenticationManager.CreateWithInteractiveLogin(clientId, redirectUrl, tenantId);

// Get a reference to the ClientContext of CSOM
using (var context = await authManager.GetContextAsync(siteUrl))
{
    // Read web properties
    var web = context.Web;
    context.Load(web, w => w.Id, w => w.Title);
    await context.ExecuteQueryRetryAsync();

    Console.WriteLine($"{web.Id} - {web.Title}");

    // Retrieve a list by title together with selected properties
    var documents = web.GetListByTitle("Documents", l => l.Id, l => l.Title);

    Console.WriteLine($"{documents.Id} - {documents.Title}");

    // Retrieve the top 10 items from the list
    var query = CamlQuery.CreateAllItemsQuery(10);
    var items = documents.GetItems(query);
    context.Load(items);
    await context.ExecuteQueryRetryAsync();

    // Browse through all the items
    foreach (var i in items)
    {
        Console.WriteLine($"{i.Id} - {i["Title"]}");
    }     
}

上述代码依赖于交互式和基于 Web 的登录名来访问目标 SharePoint Online 网站。 身份验证由 PnP 框架的 AuthenticationManager 类管理,这为你提供了一种简单、简单的技术,可使用以下任何选项对 SharePoint Online 进行身份验证:

  • 交互式用户身份验证
  • 用户名和密码 (,其中密码必须存储在 SecureString)
  • 在 Azure Active Directory 中注册的应用程序的设备代码
  • 授权代码流
  • 仅限应用的身份验证的客户端 ID 和客户端密码
  • 仅限应用的身份验证的 X.509 证书
  • 代表流
  • 用于仅限应用的身份验证的 Azure ACS

代码示例重试标题为“文档”的库的 ID标题 ,并检索库中的前 10 个项目,以显示这些项目的 ID标题 。 语法非常接近你习惯在 CSOM 中使用的语法。 事实上,你从 CSOM 获取一个类型为 Microsoft.SharePoint.Client.ClientContext上下文对象,并使用它。 但是,例如,使用 ExecuteQueryRetryAsync 方法(它实际上是 PnP Framework 提供的 .NET 扩展方法),或者使用 GetListByTitle 方法(这是 PnP Framework 提供的另一个扩展方法)。 此外,在 PnP 框架中,有许多异步方法可用于编写新式异步代码。

在 PnP 框架库中,有数千种适用于 CSOM 类型的扩展方法,可提高代码质量、可读性和效率。 Furhtermore,在 PnP 框架中,还有 PnP 预配引擎的实现,该引擎是许多企业使用的引擎,Microsoft本身来自动预配网站和项目。

最后但并非最不重要的一点,PnP 框架位于 PnP PowerShell 模块提供的大多数 cmdlet 的后端中,并为 PnP 现代化框架的 PnP 页面转换 引擎提供基本基础结构。

一般来说,PnP 框架是每个 SharePoint 开发人员应该用来在 Microsoft .NET 中创建 SPO 新式解决方案的“事实”库。

PnP 核心 SDK 库简介

PnP 核心 SDK 是 Microsoft 365 PnP 社区为新式云开发引入的全新库。 它于 2021 年 2 月初上线,面向 .NET Standard 2.0 和 .NET 5.0/6.0/7.0。 因此,可以在 Windows、Linux、MacOS) 和任何基于云的服务 (的任何平台上使用它,并且以名为 PnP.Core 的 NuGet 包的形式提供。

PnP Core SDK 库在设计时考虑了新式开发。 事实上,它本机支持依赖注入 (DI) 、面向服务的体系结构、流畅的语法(包括语言集成查询 (LINQ) 支持)和异步开发模型。 它还本机支持 REST 级别的请求批处理,并在请求限制时支持自动重试逻辑。

你可能想知道 PnP 框架和 PnP Core SDK 之间的区别是什么。 实际上,PnP Core SDK 由 PnP 框架使用,并且它以独立于 SharePoint 的 CSOM 的思维模式实现。 事实上,SPO 中引入的新功能不一定通过 CSOM 提供。 这就是引入 PnP Core SDK 的原因,它属于 PnP 框架和 PnP PowerShell 的涵盖范围,适用于某些功能。

PnP Core SDK 通常称为 PnP Graph First 库,因为此库的目标是通过 Microsoft Graph 帮助使用 SPO 和 Microsoft Teams 的新式 .NET 开发人员 (Teams) 。 事实上,Microsoft Graph 是用于使用任何Microsoft 365 工作负载(包括 SPO 和 Teams)的标准 API。 但是,在撰写本文时,有些功能尚不可用或尚未通过 Microsoft Graph 完全可用,例如对新式页面的完全支持,或者需要改进才能与 SPO 的 REST API 提供的功能完全相媲美。

使用 PnP Core SDK,可以在后端 API 之上创建一个抽象层,以便可以专注于业务目标:在 SPO 和 Teams 的基础上创建新式解决方案。 PnP Core SDK 面向 Microsoft Graph(如果你正在寻找的功能可通过 Graph 提供),或者在需要时回退到 SPO 的 REST API。

Microsoft Graph 正在迅速发展和发展,使用此技术时,开发人员无需持续更新其代码。 他们只需依赖 PnP Core SDK,这将为他们做出正确的选择。 如果 Microsoft Graph 中目前缺少某个功能,则添加该功能时,开发人员只需更新 PnP Core SDK NuGet 包,而无需更改其代码的任何行。

PnP Core SDK 仅面向 (SPO 和 Teams) 的云工作负载,从身份验证的角度来看,它依赖于基于 OAuth 2.0 的开放模型。 基本上,只要提供有效的访问令牌,就可以使用任何你喜欢的技术实现身份验证层。 如果要使用 Microsoft 身份验证库 (MSAL) ,可以使用名为 PnP.Core.Auth 的专用包进行插件,并使用 它通过 MSAL 对目标云服务的访问进行身份验证。

若要使用新的 PnP Core SDK,只需创建一个 .NET 项目,添加一个对名为 PnP.Core.Auth 的 NuGet 包的引用,其中包含主 PnP.Core 包的依赖项,即可开始使用它。

为了构建示例,让我们创建一个 .NET 控制台应用程序,该应用程序通过 Microsoft.Extensions.Hosting NuGet 包依赖于依赖关系注入,以使用 PnP 核心 SDK 库使用某些 SharePoint Online 数据。 若要添加对 PnP.Core.Auth 的引用,在 Visual Studio 中,只需使用 UI 添加包引用即可。 在 Visual Studio Code 中,需要执行以下命令。

dotnet add package PnP.Core.Auth
dotnet add package Microsoft.Extensions.Hosting

现在,你已准备好定义服务主机并生成服务链,包括 PnP Core SDK 提供的服务。

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using PnP.Core.Auth.Services.Builder.Configuration;
using PnP.Core.Services.Builder.Configuration;
using PnP.Core.Services;
using PnP.Core.QueryModel;

var host = Host.CreateDefaultBuilder()
// Configure services with Dependency Injection
.ConfigureServices((hostingContext, services) =>
{
    // Add the PnP Core SDK library services
    services.AddPnPCore();
    // Add the PnP Core SDK library services configuration from the appsettings.json file
    services.Configure<PnPCoreOptions>(hostingContext
        .Configuration.GetSection("PnPCore"));
    // Add the PnP Core SDK Authentication Providers
    services.AddPnPCoreAuthentication();
    // Add the PnP Core SDK Authentication Providers 
    // configuration from the appsettings.json file
    services.Configure<PnPCoreAuthenticationOptions>(hostingContext
        .Configuration.GetSection("PnPCore"));
})
// Let the builder know we're running in a console
.UseConsoleLifetime()
// Add services to the container
.Build();

// Start console host
await host.StartAsync();

// Optionally create a DI scope
using (var scope = host.Services.CreateScope())
{
    // Obtain a PnP Context factory
    var pnpContextFactory = scope.ServiceProvider
        .GetRequiredService<IPnPContextFactory>();
    // Use the PnP Context factory to get a PnPContext for the given configuration
    using (var context = await pnpContextFactory.CreateAsync("SiteToWorkWith"))
    {
        // Retrieving web with lists and masterpageurl loaded ==> SharePoint REST query
        var web = await context.Web.GetAsync(p => p.Title, p => p.Lists,
        p => p.MasterUrl);

        // Output some information about the web
        Console.WriteLine($"{web.Id} - {web.Title}");

        // Browse through the requested lists
        foreach (var l in web.Lists.AsRequested())
        {
            Console.WriteLine($"{l.Id} - {l.Title}");
        }
    }
}

// Cleanup console host
host.Dispose();

在代码摘录中,可以注意到,PnP Core SDK 提供了自己的上下文对象(称为 PnPContext),你可以使用该对象来访问 SPO 或 Teams,该模型使用专用的域模型类型,该模型与 CSOM 或 Microsoft Graph 完全独立,并提供将 PnP Core SDK 与后端 REST 服务分离所需的抽象层。 还可以注意到 fluent 语法,你可以在其中访问 PnPContext 实例的 Web 属性并调用其上的方法,而无需先加载 Web 对象实例。

为了能够执行刚才看到的示例,需要提供配置文件 (appsettings.json) 如以下代码摘录中所示。

{
    "PnPCore": {
        "Credentials": {
        "DefaultConfiguration": "interactive",
        "Configurations": {
            "interactive": {
                "Interactive": {
                    "RedirectUri": "http://localhost"
                }
            }
        }
        },
        "Sites": {
            "SiteToWorkWith": {
                "SiteUrl": "https://consoto.sharepoint.com/sites/targetSite"
            }
        }
    }
}

如果需要,还可以仅使用代码提供配置设置,而无需依赖.json配置文件。

重要

有关如何使用新 PnP Core SDK 的更多详细信息,请参阅文档 PnP Core SDK 的官方文档。

无论使用哪种技术创建 PnPContext 类型的已配置实例,一旦拥有该实例,即可轻松访问与上下文相关的 SPO 站点。 如果网站是新式团队网站,还可以访问连接到工作组网站的 Microsoft Teams 中的团队,如以下代码摘录所示。

// Get a reference to the team connected to the current site, including the FunSettings
var team = await context.Team.GetAsync(t => t.FunSettings);

// Show one of the settings in the FunSettings property
Console.WriteLine($"Are Giphy allowed? {team.FunSettings.AllowGiphy}");

PnP Core SDK 的另一个有趣功能是支持 LINQ (语言集成查询) 查询,以便可以使用 LINQ 查询 PnP Core SDK 的对象模型。 例如,在以下代码摘录中,可以看到如何查询按 TemplateType 筛选的当前 Web 对象的列表,以便我们只能检索文档库。

// Define a LINQ query to retrieve only the document libraries of the current web
var lists = (from l in context.Web.Lists
            where l.TemplateType == PnP.Core.Model.SharePoint.ListTemplateType.DocumentLibrary
            select l);

Console.WriteLine("=> Here are all the document libraries:");

// Browse the lists resulting from the LINQ query
foreach (var l in lists)
{
    Console.WriteLine($"{l.Id} - {l.Title} - {l.TemplateType}");
}

混合使用 PnP 框架和 PnP Core SDK

阅读本文后,你可能想知道应该使用什么以及何时使用。

如果你是一名 SharePoint 开发人员,习惯于使用 CSOM,或者如果你有一个基于 PnP Sites Core 和 CSOM 构建的现有项目,则应依赖 PnP 框架,并开始利用新 PnP 框架库提供的一组丰富的扩展和实用工具构建新式解决方案。

如果你是 .NET 开发人员,愿意为 SPO 创建新的新式解决方案,请开始使用 PnP Core SDK,并享受使用具有高级抽象层的 Microsoft Graph 和 SPO REST API。

如果需要,甚至可以使用这两个库,具体取决于需要执行的操作。 例如,如果需要使用 PnP 预配引擎自动预配网站,则必须依赖 PnP 框架。 如果需要使用 LINQ 查询 SharePoint 对象,则必须依赖于 PnP Core SDK。 可以使用 和 从一个上下文切换到另一个上下文。 例如,在以下代码摘录中,可以看到如何从 PnP 框架切换到 PnP 核心 SDK,共享相同的上下文。

// Create an instance of the AuthenticationManager type
var authManager = AuthenticationManager.CreateWithInteractiveLogin(clientId, redirectUrl, tenantId);

// Get a reference to the ClientContext of CSOM
using (var csomContext = await authManager.GetContextAsync(siteUrl))
{
    // Use CSOM to load the web title
    csomContext.Load(csomContext.Web, p => p.Title);
    csomContext.ExecuteQueryRetry();
    Console.WriteLine($"Title from PnP Framework: {csomContext.Web.Title}");

    using (PnPContext pnpCoreContext = PnPCoreSdk.Instance.GetPnPContext(csomContext))
    {
        // Use PnP Core SDK (Microsoft Graph / SPO Rest) to load the web title
        var web = await pnpCoreContext.Web.GetAsync(p => p.Title);

        Console.WriteLine($"Title from PnP Core SDK: {web.Title}");
    }
}

不过,在以下代码摘录中,可以看到如何从 PnP 核心 SDK 切换到 PnP 框架,并共享相同的上下文。

using (var scope = host.Services.CreateScope())
{
    // Obtain a PnP Context factory
    var pnpContextFactory = scope.ServiceProvider
        .GetRequiredService<IPnPContextFactory>();
    // Use the PnP Context factory to get a PnPContext for the given configuration
    using (var pnpCoreContext = await pnpContextFactory.CreateAsync("SiteToWorkWith"))
    {
        // Use PnP Core SDK (Microsoft Graph / SPO Rest) to load the web title
        var web = await pnpCoreContext.Web.GetAsync(p => p.Title);
        Console.WriteLine($"Title from PnP Core SDK: {web.Title}");

        using (ClientContext csomContext = PnPCoreSdk.Instance.GetClientContext(pnpCoreContext))
        {
            // Use CSOM to load the web title
            csomContext.Load(csomContext.Web, p => p.Title);
            csomContext.ExecuteQueryRetry();

            Console.WriteLine($"Title from PnP Framework: {csomContext.Web.Title}");
        }   
    }
}

因此,你可以自由移动这两个库,并享受新的编码体验。

有关本主题的其他信息,请参阅以下文档: