6
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

SharePointOnlineへのファイルアップロード

Posted at

SharePointOnlineの特定フォルダにC#でファイルをアップロードする手順です。
ここでは、ドキュメント配下にTESTフォルダを作成し、そこへアップロードすることにします。

無題.png

#環境
Visual Studio 2019
.NET Core 3.1
C#
SharePointOnline

#準備1
NuGetでMicrosoft.SharePointOnline.CSOMをインストール。
(当記事作成時点のバージョンは16.1.21909.12000)
無題.png

#準備2
Azure ADでアプリの登録をする。
無題.png


![無題.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)

#準備3
登録したアプリにAPIアクセス許可を付与する。
無題.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)

#準備4
パブリック クライアント フローを許可する
無題.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();
                    }
                }
            }
        }

6
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?