.NET 5 获得SharePoint Online的Client Context

当使用.NET core 或者.NET 5时,安装完SharePoint Online CSOM 包以后,需要获得Client Context。发现SharePointOnlineCredentials class不可用了。因为微软已经删除了这个class。https://docs.microsoft.com/en-us/answers/questions/58183/net-core-sharepointonlinecredentials-class.html

微软网站也给出了解决方案,就是重写AuthenticationManager。我参照上面的方法,重写了通过App Only获得Client Context的方法。废话不多说,上代码。

AuthenticationManager.cs

  1 using Microsoft.SharePoint.Client;
  2 using System;
  3 using System.Collections.Concurrent;
  4 using System.Net.Http;
  5 using System.Text;
  6 using System.Text.Json;
  7 using System.Threading;
  8 using System.Threading.Tasks;
  9 
 10 namespace DownloadSPLibAndUploadToAzureBlobWithNet5
 11 {
 12     public class AuthenticationManager : IDisposable
 13     {
 14         private static readonly HttpClient httpClient = new HttpClient();
 15         private const string tenantName = "";  //tenant name or id.
 16 
 17         // Token cache handling
 18         private static readonly SemaphoreSlim semaphoreSlimTokens = new SemaphoreSlim(1);
 19         private AutoResetEvent tokenResetEvent = null;
 20         private readonly ConcurrentDictionary<string, string> tokenCache = new ConcurrentDictionary<string, string>();
 21         private bool disposedValue;
 22 
 23 
 24         internal class TokenWaitInfo
 25         {
 26             public RegisteredWaitHandle Handle = null;
 27         }
 28 
 29         public ClientContext GetContext(Uri web, string clientId, string clientSecret)
 30         {
 31             ClientContext context = new ClientContext(web);
 32             context.ExecutingWebRequest += (sender, e) =>
 33             {
 34                 string accessToken = EnsureAccessTokenAsync(web, clientId, clientSecret).GetAwaiter().GetResult();
 35                 e.WebRequestExecutor.RequestHeaders["Authorization"] = "Bearer " + accessToken;
 36             };
 37 
 38             return context;
 39         }
 40 
 41         public async Task<string> EnsureAccessTokenAsync(Uri web, string clientId, string clientSecret)
 42         {
 43             string accessTokenFromCache = TokenFromCache(web, tokenCache);
 44             if (accessTokenFromCache == null)
 45             {
 46                 await semaphoreSlimTokens.WaitAsync().ConfigureAwait(false);
 47                 try
 48                 {
 49                     // No async methods are allowed in a lock section
 50                     string accessToken = await AcquireTokenAsync(web, clientId, clientSecret).ConfigureAwait(false);
 51                     AddTokenToCache(web, tokenCache, accessToken);
 52 
 53                     // Register a thread to invalidate the access token once's it's expired
 54                     tokenResetEvent = new AutoResetEvent(false);
 55                     TokenWaitInfo wi = new TokenWaitInfo();
 56                     wi.Handle = ThreadPool.RegisterWaitForSingleObject(
 57                         tokenResetEvent,
 58                         async (state, timedOut) =>
 59                         {
 60                             if (!timedOut)
 61                             {
 62                                 TokenWaitInfo wi = (TokenWaitInfo)state;
 63                                 if (wi.Handle != null)
 64                                 {
 65                                     wi.Handle.Unregister(null);
 66                                 }
 67                             }
 68                             else
 69                             {
 70                                 try
 71                                 {
 72                                     // Take a lock to ensure no other threads are updating the SharePoint Access token at this time
 73                                     await semaphoreSlimTokens.WaitAsync().ConfigureAwait(false);
 74                                     RemoveTokenFromCache(web, tokenCache);
 75                                     Console.WriteLine($"Cached token for resource {web.DnsSafeHost} and clientId {clientId} expired");
 76                                 }
 77                                 catch (Exception ex)
 78                                 {
 79                                     Console.WriteLine($"Something went wrong during cache token invalidation: {ex.Message}");
 80                                     RemoveTokenFromCache(web, tokenCache);
 81                                 }
 82                                 finally
 83                                 {
 84                                     semaphoreSlimTokens.Release();
 85                                 }
 86                             }
 87                         },
 88                         wi,
 89                         (uint)CalculateThreadSleep(accessToken).TotalMilliseconds,
 90                         true
 91                     );
 92 
 93                     return accessToken;
 94                 }
 95                 finally
 96                 {
 97                     semaphoreSlimTokens.Release();
 98                 }
 99             }
100             else
101                 return accessTokenFromCache;
102         }
103 
104         private async Task<string> AcquireTokenAsync(Uri web, string clientId, string clientSecret)
105         {
106             var body = "grant_type=client_credentials" +
107                 $"&resource=00000003-0000-0ff1-ce00-000000000000/{web.DnsSafeHost}@{tenantName}" +
108                 $"&client_id={clientId}@{tenantName}" +
109                 $"&client_secret={clientSecret}";
110 
111             using (var stringContent = new StringContent(body, Encoding.UTF8, "application/x-www-form-urlencoded"))
112             {
113                 var result = await httpClient.PostAsync($"https://accounts.accesscontrol.windows.net/{tenantName}/tokens/OAuth/2", stringContent)
114                     .ContinueWith((response) =>
115                     {
116                         return response.Result.Content.ReadAsStringAsync().Result;
117                     })
118                     .ConfigureAwait(false);
119 
120                 var tokenResult = JsonSerializer.Deserialize<JsonElement>(result);
121                 var token = tokenResult.GetProperty("access_token").GetString();
122                 return token;
123             }
124         }
125 
126         private static string TokenFromCache(Uri web, ConcurrentDictionary<string, string> tokenCache)
127         {
128             if (tokenCache.TryGetValue(web.DnsSafeHost, out string accessToken))
129                 return accessToken;
130 
131             return null;
132         }
133 
134         private static void AddTokenToCache(Uri web, ConcurrentDictionary<string, string> tokenCache, string newAccessToken)
135         {
136             if (tokenCache.TryGetValue(web.DnsSafeHost, out string currentAccessToken))
137                 tokenCache.TryUpdate(web.DnsSafeHost, newAccessToken, currentAccessToken);
138             else
139                 tokenCache.TryAdd(web.DnsSafeHost, newAccessToken);
140         }
141 
142         private static void RemoveTokenFromCache(Uri web, ConcurrentDictionary<string, string> tokenCache)
143         {
144             tokenCache.TryRemove(web.DnsSafeHost, out string currentAccessToken);
145         }
146 
147         private static TimeSpan CalculateThreadSleep(string accessToken)
148         {
149             var token = new System.IdentityModel.Tokens.Jwt.JwtSecurityToken(accessToken);
150             var lease = GetAccessTokenLease(token.ValidTo);
151             lease = TimeSpan.FromSeconds(lease.TotalSeconds - TimeSpan.FromMinutes(5).TotalSeconds > 0 ? lease.TotalSeconds - TimeSpan.FromMinutes(5).TotalSeconds : lease.TotalSeconds);
152             return lease;
153         }
154 
155         private static TimeSpan GetAccessTokenLease(DateTime expiresOn)
156         {
157             DateTime now = DateTime.UtcNow;
158             DateTime expires = expiresOn.Kind == DateTimeKind.Utc ? expiresOn : TimeZoneInfo.ConvertTimeToUtc(expiresOn);
159             TimeSpan lease = expires - now;
160             return lease;
161         }
162         protected virtual void Dispose(bool disposing)
163         {
164             if (!disposedValue)
165             {
166                 if (disposing)
167                 {
168                     if (tokenResetEvent != null)
169                     {
170                         tokenResetEvent.Set();
171                         tokenResetEvent.Dispose();
172                     }
173                 }
174                 disposedValue = true;
175             }
176         }
177 
178         public void Dispose()
179         {
180             // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
181             Dispose(disposing: true);
182             GC.SuppressFinalize(this);
183         }
184     }
185 }

program.cs

 1 using Microsoft.SharePoint.Client;
 2 using System;
 3 using System.Threading.Tasks;
 4 
 5 namespace DownloadSPLibAndUploadToAzureBlobWithNet5
 6 {
 7     class Program
 8     {
 9         static async Task Main(string[] args)
10         {
11             Uri site = new Uri("https://******.sharepoint.com/sites/10000034");
12             string clientId = "";
13             string clientSecret = "";
14 
15             // Note: The PnP Sites Core AuthenticationManager class also supports this
16             using (var authenticationManager = new AuthenticationManager())
17             {
18                 using (var context = authenticationManager.GetContext(site, clientId, clientSecret))
19                 {
20                     context.Load(context.Web, p => p.Title);
21                     await context.ExecuteQueryAsync();
22                     Console.WriteLine($"Title: {context.Web.Title}");
23 
24                     var list = context.Web.Lists.GetByTitle("Links");
25                     ListItemCreationInformation itemCreateInfo = new ListItemCreationInformation();
26                     ListItem newItem = list.AddItem(itemCreateInfo);
27                     newItem["Title"] = "My New Item!";
28                     newItem.Update();
29                     await context.ExecuteQueryAsync();
30                     Console.WriteLine(newItem.Id);
31                 }
32             }
33 
34         }
35     }
36 }

通过用户名和密码获得client context可以直接参考微软给的code. https://docs.microsoft.com/en-us/sharepoint/dev/sp-add-ins/using-csom-for-dotnet-standard

另外还有一种更简便的方发,就是使用pnp.framework包。

用VS生成新项目以后,安装pnp.framework包,然后用下面的code就可以得到client context了。

 1 using Microsoft.SharePoint.Client;
 2 using System;
 3 using System.Threading.Tasks;
 4 
 5 namespace DownloadSPLibAndUploadToAzureBlobWithNet5
 6 {
 7     class Program
 8     {
 9         static async Task Main(string[] args)
10         {
11             Uri site = new Uri("https://********.sharepoint.com/sites/10000034");
12             string clientId = "";
13             string clientSecret = "";
14 
15             // Note: The PnP Sites Core AuthenticationManager class also supports this
16             using (var authenticationManager = new AuthenticationManager())
17             {
18                 using (var context = authenticationManager.GetContext(site, clientId, clientSecret))
19                 {
20                     context.Load(context.Web, p => p.Title);
21                     await context.ExecuteQueryAsync();
22                     Console.WriteLine($"Title: {context.Web.Title}");
23 
24                     var list = context.Web.Lists.GetByTitle("Links");
25                     ListItemCreationInformation itemCreateInfo = new ListItemCreationInformation();
26                     ListItem newItem = list.AddItem(itemCreateInfo);
27                     newItem["Title"] = "My New Item!";
28                     newItem.Update();
29                     await context.ExecuteQueryAsync();
30                     Console.WriteLine(newItem.Id);
31                 }
32             }
33 
34         }
35     }
36 }

 github 链接: https://github.com/HaiyeWang0717/DownloadSPLibAndUploadToAzureBlobWithNet5

 

posted @ 2021-04-27 03:16  老王717  阅读(354)  评论(0)    收藏  举报