可以使用 SharePoint 客户端对象模型 (CSOM) 在 SharePoint 中检索、更新和管理数据。SharePoint 按以下几种形式提供此 CSOM:
在本文中,我们将重点介绍 .NET Framework 版本和 .NET Standard 可再发行版本之间的区别。 这两个版本在许多方面完全相同,并且如果你一直在使用 .NET Framework 版本编写代码,那么在使用 .NET Standard 版本时,该代码和你学到的一切在很大程度上仍将是相关的。
下表概括了两个版本之间的区别,并提供了有关如何处理这些区别的指南。
备注
.NET 标准版本的 CSOM 程序集包含在自版本 16.1.20211.12000 以来名为 Microsoft.SharePointOnline.CSOM 的现有 NuGet 包中。 以下示例需要此版本或更高版本才能在 .Net core/标准目标项目中工作。
使用通过
在本章中,我们将使用 OAuth 资源所有者密码凭据流生成一个 OAuth 访问令牌,然后,CSOM 将其用于模仿
以下步骤将帮助你在 Azure Active Directory 中创建和配置应用程序:
使用 CSOM for .NET Standard 时,开发人员有责任获取 SharePoint Online 的访问令牌,并确保将其插入到对 SharePoint Online 的每次调用中。实现此操作的常见代码模式如下所示:
通过
备注
PnP 网站核心库具有类似的 AuthenticationManager 类,支持更多基于 Azure AD 的身份验证流。
备注
使用在 Azure AD 中注册的应用程序的应用程序 ID 更新
备注
如果将 CSOM for .NET Standard 与 Azure Functions v3 一起使用,则可能会遇到与 System.IdentityModel.Tokens.Jwt 相关的运行时错误。 可通过遵循此解决方法解决此问题。
.NET Framework 版本和 .NET Standard 版本之间的主要区别
CSOM 功能
.NET Framework 版本
.NET Standard 版本
准则
.NET 可支持性
.NET Framework 4.5+
.NET Framework 4.6.1+、.NET Core 2.0+、Mono 5.4+ (.NET docs)
建议针对所有 SharePoint Online CSOM 开发使用 CSOM for .NET Standard 版本
跨平台
否
是(可用于任何支持 .NET Standard 的平台)
对于跨平台,必须使用 CSOM for .NET Standard
本地 SharePoint 支持
是
否
CSOM .NET Framework 版本仍然完全受支持并且持续更新,因此请使用它们进行本地 SharePoint 开发
支持旧式身份验证流程(使用
SharePointOnlineCredentials
类的所谓基于 cookie 的身份验证)
是
否
请参阅 对 CSOM for .NET Standard 使用新式身份验证 一章。 建议使用 Azure AD 应用程序配置 SharePoint Online 的身份验证
SaveBinaryDirect
/ OpenBinaryDirect
API(基于 webdav)
是
否
在 CSOM 中使用常规文件 API,因为不建议使用 BinaryDirect API,即使在使用 .NET Framework 版本时也不例外
Microsoft.SharePoint.Client.Utilities.HttpUtility
类
是
否
切换到 .NET 中的类似类,如
System.Web.HttpUtility
Microsoft.SharePoint.Client.EventReceivers
命名空间
是
否
切换到新式事件概念,如 Web 挂钩。
对 CSOM for .NET Standard 使用新式身份验证
SharePointOnlineCredentials
类实施的基于用户/密码的身份验证是使用 CSOM for .NET Framework 的开发人员的常用方法。 在 CSOM for .NET Standard 中,这种方法不再可行,由使用 CSOM for .NET Standard 的开发人员来获取 OAuth 访问令牌并在调用 SharePoint Online 时使用该令牌。 建议通过设置 Azure AD 应用程序来为 SharePoint Online 获取访问令牌。 对于 CSOM for .NET Standard,唯一重要的是获取有效的访问令牌,可以通过使用资源所有者密码凭据流、使用设备登录信息、使用基于证书的身份验证等等来实现。SharePointOnlineCredentials
类的行为,以对 SharePoint Online 进行身份验证请求。
在 Azure AD 中配置应用程序
从Azure AD 获取访问令牌,并在基于 CSOM for .NET Standard 的应用程序中使用该令牌
public ClientContext GetContext(Uri web, string userPrincipalName, SecureString userPassword)
{
context.ExecutingWebRequest += (sender, e) =>
{
// Get an access token using your preferred approach
string accessToken = MyCodeToGetAnAccessToken(new Uri($"{web.Scheme}://{web.DnsSafeHost}"), userPrincipalName, new System.Net.NetworkCredential(string.Empty, userPassword).Password);
// Insert the access token in the request
e.WebRequestExecutor.RequestHeaders["Authorization"] = "Bearer " + accessToken;
};
}
GetContext
方法获取的 ClientContext
可以像其他任何 ClientContext
一样使用,并且可以与所有现有代码一起使用。 下面的代码段显示帮助程序类和使用该帮主程序类的控制台应用,重用这些类将能够轻松实施 SharePointOnlineCredentials
类的等效操作。
控制台应用示例
p.Title);
await context.ExecuteQueryAsync();
Console.WriteLine($"Title: {context.Web.Title}");
}
}
" style="box-sizing: inherit; font-family: SFMono-Regular, Consolas, "Liberation Mono", Menlo, Courier, monospace; font-size: 1em; direction: ltr; outline-color: inherit; line-height: 1.3571; border: 0px; display: block; padding: 0px; position: relative;">public static async Task Main(string[] args)
{
Uri site = new Uri("https://contoso.sharepoint.com/sites/siteA");
string user = "joe.doe@contoso.onmicrosoft.com";
SecureString password = GetSecureString($"Password for {user}");
// Note: The PnP Sites Core AuthenticationManager class also supports this
using (var authenticationManager = new AuthenticationManager())
using (var context = authenticationManager.GetContext(site, user, password))
{
context.Load(context.Web, p => p.Title);
await context.ExecuteQueryAsync();
Console.WriteLine($"Title: {context.Web.Title}");
}
}
AuthenticationManager 示例类
defaultAADAppId
using Microsoft.SharePoint.Client;
using System;
using System.Collections.Concurrent;
using System.Net.Http;
using System.Security;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
namespace CSOMDemo
{
public class AuthenticationManager: IDisposable
{
private static readonly HttpClient httpClient = new HttpClient();
private const string tokenEndpoint = "https://login.microsoftonline.com/common/oauth2/token";
private const string defaultAADAppId = "986002f6-c3f6-43ab-913e-78cca185c392";
// Token cache handling
private static readonly SemaphoreSlim semaphoreSlimTokens = new SemaphoreSlim(1);
private AutoResetEvent tokenResetEvent = null;
private readonly ConcurrentDictionary<string, string> tokenCache = new ConcurrentDictionary<string, string>();
private bool disposedValue;
internal class TokenWaitInfo
{
public RegisteredWaitHandle Handle = null;
}
public ClientContext GetContext(Uri web, string userPrincipalName, SecureString userPassword)
{
var context = new ClientContext(web);
context.ExecutingWebRequest += (sender, e) =>
{
string accessToken = EnsureAccessTokenAsync(new Uri($"{web.Scheme}://{web.DnsSafeHost}"), userPrincipalName, new System.Net.NetworkCredential(string.Empty, userPassword).Password).GetAwaiter().GetResult();
e.WebRequestExecutor.RequestHeaders["Authorization"] = "Bearer " + accessToken;
};
return context;
}
public async Task<string> EnsureAccessTokenAsync(Uri resourceUri, string userPrincipalName, string userPassword)
{
string accessTokenFromCache = TokenFromCache(resourceUri, tokenCache);
if (accessTokenFromCache == null)
{
await semaphoreSlimTokens.WaitAsync().ConfigureAwait(false);
try
{
// No async methods are allowed in a lock section
string accessToken = await AcquireTokenAsync(resourceUri, userPrincipalName, userPassword).ConfigureAwait(false);
Console.WriteLine($"Successfully requested new access token resource {resourceUri.DnsSafeHost} for user {userPrincipalName}");
AddTokenToCache(resourceUri, tokenCache, accessToken);
// Register a thread to invalidate the access token once's it's expired
tokenResetEvent = new AutoResetEvent(false);
TokenWaitInfo wi = new TokenWaitInfo();
wi.Handle = ThreadPool.RegisterWaitForSingleObject(
tokenResetEvent,
async (state, timedOut) =>
{
if (!timedOut)
{
TokenWaitInfo internalWaitToken = (TokenWaitInfo)state;
if (internalWaitToken.Handle != null)
{
internalWaitToken.Handle.Unregister(null);
}
}
else
{
try
{
// Take a lock to ensure no other threads are updating the SharePoint Access token at this time
await semaphoreSlimTokens.WaitAsync().ConfigureAwait(false);
RemoveTokenFromCache(resourceUri, tokenCache);
Console.WriteLine($"Cached token for resource {resourceUri.DnsSafeHost} and user {userPrincipalName} expired");
}
catch (Exception ex)
{
Console.WriteLine($"Something went wrong during cache token invalidation: {ex.Message}");
RemoveTokenFromCache(resourceUri, tokenCache);
}
finally
{
semaphoreSlimTokens.Release();
}
}
},
wi,
(uint)CalculateThreadSleep(accessToken).TotalMilliseconds,
true
);
return accessToken;
}
finally
{
semaphoreSlimTokens.Release();
}
}
else
{
Console.WriteLine($"Returning token from cache for resource {resourceUri.DnsSafeHost} and user {userPrincipalName}");
return accessTokenFromCache;
}
}
private async Task<string> AcquireTokenAsync(Uri resourceUri, string username, string password)
{
string resource = $"{resourceUri.Scheme}://{resourceUri.DnsSafeHost}";
var clientId = defaultAADAppId;
var body = $"resource={resource}&client_id={clientId}&grant_type=password&username={HttpUtility.UrlEncode(username)}&password={HttpUtility.UrlEncode(password)}";
using (var stringContent = new StringContent(body, Encoding.UTF8, "application/x-www-form-urlencoded"))
{
var result = await httpClient.PostAsync(tokenEndpoint, stringContent).ContinueWith((response) =>
{
return response.Result.Content.ReadAsStringAsync().Result;
}).ConfigureAwait(false);
var tokenResult = JsonSerializer.Deserialize