SharePointOnlineの特定フォルダにC#でファイルをアップロードする手順です。
ここでは、ドキュメント配下にTESTフォルダを作成し、そこへアップロードすることにします。
#環境
Visual Studio 2019
.NET Core 3.1
C#
SharePointOnline
#準備1
NuGetでMicrosoft.SharePointOnline.CSOMをインストール。
(当記事作成時点のバージョンは16.1.21909.12000)
![無題.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2240956/1564defd-8bd8-a4cf-8262-109063cdaf0e.png)
任意の名前を付けて登録。 必要に応じてリダイレクトURI の設定を。 ![無題.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2240956/b6d97530-0c86-a9ca-1abd-2a2fb370a699.png)
**アプリケーション (クライアント) IDをメモしておく** ![無題.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2240956/2ad86ae2-1b9f-9c23-4f9d-0cae73b844dc.png)
![無題.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2240956/847b439a-8366-235d-a785-d25caac1ae20.png)
![無題.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2240956/4f97d7f7-173b-392e-d814-5af5237b66d7.png)
管理者の同意を与える ![無題.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2240956/da398b1d-da0a-8299-cc46-ffe96ed4ce57.png)
#認証用クラスの作成
ここのを利用させてただきました。(https://docs.microsoft.com/en-us/sharepoint/dev/sp-add-ins/using-csom-for-dotnet-standard)
※パスワードの入力処理やnamespaceなどを一部変更してあります。
defaultAADAppIdを、準備2でメモした「アプリケーション (クライアント) ID」に置き換えてください。
AuthenticationManager.cs
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 HogehogeSystem
{
public class AuthenticationManager : IDisposable
{
private static readonly HttpClient httpClient = new HttpClient();
private const string tokenEndpoint = "https://login.microsoftonline.com/common/oauth2/token";
//メモしたアプリケーション (クライアント) IDを代入
private const string defaultAADAppId = "●●●●●●●●●●●●●●●●";
// 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, string password)
{
var context = new ClientContext(web);
var userPassword = new SecureString();
foreach (var p in password)
userPassword.AppendChar(p);
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 wi1 = (TokenWaitInfo)state;
if (wi1.Handle != null)
{
wi1.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<JsonElement>(result);
var token = tokenResult.GetProperty("access_token").GetString();
return token;
}
}
private static string TokenFromCache(Uri web, ConcurrentDictionary<string, string> tokenCache)
{
if (tokenCache.TryGetValue(web.DnsSafeHost, out string accessToken))
{
return accessToken;
}
return null;
}
private static void AddTokenToCache(Uri web, ConcurrentDictionary<string, string> tokenCache, string newAccessToken)
{
if (tokenCache.TryGetValue(web.DnsSafeHost, out string currentAccessToken))
{
tokenCache.TryUpdate(web.DnsSafeHost, newAccessToken, currentAccessToken);
}
else
{
tokenCache.TryAdd(web.DnsSafeHost, newAccessToken);
}
}
private static void RemoveTokenFromCache(Uri web, ConcurrentDictionary<string, string> tokenCache)
{
tokenCache.TryRemove(web.DnsSafeHost, out string currentAccessToken);
}
private static TimeSpan CalculateThreadSleep(string accessToken)
{
var token = new System.IdentityModel.Tokens.Jwt.JwtSecurityToken(accessToken);
var lease = GetAccessTokenLease(token.ValidTo);
lease = TimeSpan.FromSeconds(lease.TotalSeconds - TimeSpan.FromMinutes(5).TotalSeconds > 0 ? lease.TotalSeconds - TimeSpan.FromMinutes(5).TotalSeconds : lease.TotalSeconds);
return lease;
}
private static TimeSpan GetAccessTokenLease(DateTime expiresOn)
{
DateTime now = DateTime.UtcNow;
DateTime expires = expiresOn.Kind == DateTimeKind.Utc ? expiresOn : TimeZoneInfo.ConvertTimeToUtc(expiresOn);
TimeSpan lease = expires - now;
return lease;
}
protected virtual void Dispose(bool disposing)
{
if (!disposedValue)
{
if (disposing)
{
if (tokenResetEvent != null)
{
tokenResetEvent.Set();
tokenResetEvent.Dispose();
}
}
disposedValue = true;
}
}
public void Dispose()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}
}
#使い方
using System;
using Microsoft.SharePoint.Client;
using System.IO;
namespace HogehogeSystem
{
public class Sample()
{
//●●●●●:ドメイン名
private const string _serverUrl = @"https://●●●●●.sharepoint.com/";
//▲▲▲▲▲:サイト名
private const string _serverRelativeUrl = @"sites/▲▲▲▲▲";
//対象フォルダまでのパス
private const string _folderPath = @"Shared%20Documents/TEST";
//フォルダにアクセスできるユーザID
private const string _user = @"test-user@test.co.jp";
//フォルダにアクセスできるユーザのパスワード
private const string _password = @"test";
//アップロード対象のファイルパス
private const string _targetFilePath = @"C:\files\test.txt";
public void Upload()
{
var site = new Uri(_serverUrl + _serverRelativeUrl);
using (var authenticationManager = new AuthenticationManager())
{
using (var context = authenticationManager.GetContext(site, _user, _password))
{
var folder = context.Web.GetFolderByServerRelativeUrl(_folderPath);
var files = folder.Files;
context.Load(files);
context.ExecuteQueryAsync();
using (var fs = new FileStream(_targetFilePath, FileMode.Open, FileAccess.ReadWrite))
{
var fileParames = new FileCreationInformation();
fileParames.ContentStream = fs;
fileParames.Url = _folderPath + @"/test.txt";
files.Add(fileParames);
context.ExecuteQueryAsync();
}
}
}
}