当使用.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