Introduction
This article will cover, How to consume Sharepoint online CSOM (REST API) operation using .NET Core 3.1 framework into a console application.
The topic needs to cover here:
- Create Azure AD Instance with Delegate Permission.
- Create Console Application and Add Microsoft.SharePointOnline.CSOM Nuget Package.
- Add Authentication Manager Class to generate the access token.
- Get Access Token & access SharePoint Online List data
Step 1 - Create Azure AD Instance with Delegate Permission
Note
Users should have access to Azure Active Directory.
- Login to https://portal.azure.com
- Search and Select Azure Active Directory
Once the user selects Azure AD, the App registration page will appear.
- Select App registrations
- Select +New registration
Once the user selects new registration, add below details
- Name "spocsom-api", user can add as per project namespace or naming convention guidelines.
- Select "Account in this organization (single-tenant)
- Click on the Register button
Grant access to SharePoint Online.
- Select API Permission
- Click to Add Permission
- Search and Add SharePoint from Request API Permission
Delegate Permission, It required where user application needs to access data as the signed-in user.
- Select Add a Permission
- Selection Delegate Permission
- Search and Select Sites
- Choose "All Site.Read" -> This is not site specific but quite secure , It will just come into use to generate token, not going to grant access to the site to access the content.
- Click to "Add Permission"
Grant Admin Consent
Grant permission by admin or user as part of the consent process.
- Select "Grant admin consent for default directory and click ok to proceed.
- All added api will show granted admin consent on behalf of the user. so it will not prompt for consent.
Allow Public Client Flows
The app is going to collect username and password into plain text, so it should allow to yes.
- Select authentication and scroll down till the end
- Under advance settings -> select Allow Public client flows -> Select Yes -> Click on Save to Proceed.
Step 2 - Create Console Application and Add Microsoft.SharePointOnline.CSOM Nuget Package
- Login to Visual Studio 2019 and Create New Project
- Select ConsoleApp (.Net Core) and Ok to proceed.
Install NuGet Package
Select solution and right-click on dependencies and NuGet Packages
Install the below packages:
- Microsoft.SharepointOnline.CSOM
- Newtonsoft.Json
- System.IdentityModel.Token.Jwt
Step 3 - Add Authentication Manager Class to generate the access token
This class helps us to generate access tokens based on application URI, UserName, Password & Azurre AD App Client ID.
Create Auth Manager Class and Copy paste the below codebase:
- public class AuthManager : IDisposable
- {
- private static readonly HttpClient httpClient = new HttpClient();
- private const string tokenEndpoint = "https://login.microsoftonline.com/common/oauth2/token";
- -Generated in above Steps
- private const string defaultAADAppId = "Azure Active Director Client ID";
-
-
- 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
- {
-
- 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);
-
-
- 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
- {
-
- 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;
- }
- }
-
- public 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()
- {
-
- Dispose(disposing: true);
- GC.SuppressFinalize(this);
- }
-
- }
Step 4 - Get Access Token & access SharePoint Online List data
Get List Data function get data from SharePoint online List and Print out all result based on return data.
- static void Main(string[] args)
- {
- List<Data> getdata = GetListData();
- foreach (Data data in getdata)
- {
- Console.WriteLine("Employee Name " + data.Title);
- }
- Console.ReadLine();
- }
The codebase will invoke the SharePoint online api with help of an access token
- public static List<Data> GetListData()
- {
- const string DataColumn = "ID,Title";
- const string DataAPIAllData = "{0}/_api/lists/getbytitle('{1}')/items?$top=10&$select=" + DataColumn + "&$orderby=Modified desc";
-
- try
- {
- var results = new List<Data>();
-
- string sharepointSiteUrl = Convert.ToString("https://mittal1201.sharepoint.com/sites/CommSiteHub");
- if (!string.IsNullOrEmpty(sharepointSiteUrl))
- {
- string listname = "Employee";
- string api = string.Format(DataAPIAllData, sharepointSiteUrl, listname);
- if (!string.IsNullOrEmpty(listname))
- {
-
- string response = TokenHelper.GetAPIResponse(api);
- if (!String.IsNullOrEmpty(response))
- {
- JObject jobj = Utility.Deserialize(response);
- JArray jarr = (JArray)jobj["d"]["results"];
-
-
- foreach (JObject j in jarr)
- {
- Data data = new Data();
- data.Title = Convert.ToString(j["Title"]);
-
- results.Add(data);
- }
- }
- return results;
- }
- else
- {
- throw new Exception("Custom Message");
- }
- }
- else
- {
- throw new Exception("Custom Message");
- }
- }
- catch (Exception ex)
- {
- throw new Exception("Custom Message");
- }
- }
- }
Created a data class with filed title here
- public class Data
- {
- public string Title { get; set; }
- }
Create TokenHelper class to get access token based on user credentials and URI. This function helps to get access from AuthManager Class with the provided information.
- public static string GetAPIResponse(string url)
- {
- string response = String.Empty;
- try
- {
-
- string accessToken = GetSharePointAccessToken();
-
- System.Net.HttpWebRequest endpointRequest = (System.Net.HttpWebRequest)System.Net.HttpWebRequest.Create(url);
- endpointRequest.Method = "GET";
- endpointRequest.Accept = "application/json;odata=verbose";
- endpointRequest.Headers.Add("Authorization", "Bearer " + accessToken);
- System.Net.WebResponse webResponse = endpointRequest.GetResponse();
- Stream webStream = webResponse.GetResponseStream();
- StreamReader responseReader = new StreamReader(webStream);
- response = responseReader.ReadToEnd();
- return response;
- }
- catch (Exception ex)
- {
- throw;
- }
-
- }
-
- public static string GetSharePointAccessToken()
- {
-
- Uri site = new Uri("https://mittal1201.sharepoint.com/sites/CommSiteHub");
- string user = "user email address";
- string pwd = "user password";
- string result;
- using (var authenticationManager = new AuthManager())
- {
- string accessTokenSP = authenticationManager.AcquireTokenAsync(site, user, pwd).Result;
- result = accessTokenSP;
- }
- return result;
- }
Output Window
Execute this solution or press F5.
SharePoint List Screen Shot where data is going to read by a console app
Console Output
Finally, we got output using .NetCore 3.1 console app instead of .NetStandard framework.
I hope you enjoyed and learned something new in this article.