diff --git a/.editorconfig b/.editorconfig
index b1b0306..9994d68 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -519,4 +519,19 @@ dotnet_diagnostic.S6605.severity = none # Collection-specific "Exist
##########################################
# Custom - Code Analyzers Rules
##########################################
-[*.{cs,csx,cake}]
\ No newline at end of file
+[*.{cs,csx,cake}]
+
+MA0051.maximum_lines_per_method = 100
+
+dotnet_diagnostic.SA1010.severity = none #
+dotnet_diagnostic.SA1402.severity = none #
+dotnet_diagnostic.SA1615.severity = none #
+
+dotnet_diagnostic.CA1002.severity = none #
+dotnet_diagnostic.CA1031.severity = none #
+dotnet_diagnostic.CA2007.severity = none # Consider calling ConfigureAwait on the awaited task
+
+dotnet_diagnostic.MA0004.severity = none # Use Task.ConfigureAwait(false) as the current SynchronizationContext is not needed
+dotnet_diagnostic.MA0016.severity = none # Prefer returning collection abstraction instead of implementation
+
+dotnet_diagnostic.S3267.severity = none #
\ No newline at end of file
diff --git a/Atc.Microsoft.Graph.Client.sln.DotSettings b/Atc.Microsoft.Graph.Client.sln.DotSettings
new file mode 100644
index 0000000..9e78504
--- /dev/null
+++ b/Atc.Microsoft.Graph.Client.sln.DotSettings
@@ -0,0 +1,2 @@
+
+ True
\ No newline at end of file
diff --git a/src/.editorconfig b/src/.editorconfig
index 970bed4..03eafd5 100644
--- a/src/.editorconfig
+++ b/src/.editorconfig
@@ -44,4 +44,6 @@
##########################################
# Custom - Code Analyzers Rules
-##########################################
\ No newline at end of file
+##########################################
+
+dotnet_diagnostic.IDE0039.severity = none #
\ No newline at end of file
diff --git a/src/Atc.Microsoft.Graph.Client/Atc.Microsoft.Graph.Client.csproj b/src/Atc.Microsoft.Graph.Client/Atc.Microsoft.Graph.Client.csproj
index 483e0bd..77db47a 100644
--- a/src/Atc.Microsoft.Graph.Client/Atc.Microsoft.Graph.Client.csproj
+++ b/src/Atc.Microsoft.Graph.Client/Atc.Microsoft.Graph.Client.csproj
@@ -12,4 +12,12 @@
+
+
+
+
+
+
+
+
diff --git a/src/Atc.Microsoft.Graph.Client/Extensions/ServiceCollectionExtensions.cs b/src/Atc.Microsoft.Graph.Client/Extensions/ServiceCollectionExtensions.cs
new file mode 100644
index 0000000..01531d6
--- /dev/null
+++ b/src/Atc.Microsoft.Graph.Client/Extensions/ServiceCollectionExtensions.cs
@@ -0,0 +1,108 @@
+// ReSharper disable ConvertToLocalFunction
+namespace Atc.Microsoft.Graph.Client.Extensions;
+
+public static class ServiceCollectionExtensions
+{
+ private static readonly string[] DefaultScopes = { "https://graph.microsoft.com/.default" };
+
+ ///
+ /// Adds the to the service collection.
+ ///
+ /// The instance to augment.
+ /// to use for the service. If null, one must be available in the service provider when this service is resolved.
+ /// The same instance as .
+ public static IServiceCollection AddMicrosoftGraphServices(
+ this IServiceCollection services,
+ GraphServiceClient? graphServiceClient = null)
+ {
+ Func factory = (serviceProvider)
+ => graphServiceClient ?? serviceProvider.GetRequiredService();
+
+ services.AddSingleton(factory);
+
+ RegisterGraphServices(services);
+
+ return services;
+ }
+
+ ///
+ /// Adds the to the service collection using the provided and optional scopes.
+ ///
+ /// The instance to augment.
+ /// The to use for authentication.
+ /// Optional array of scopes for the .
+ /// The same instance as .
+ public static IServiceCollection AddMicrosoftGraphServices(
+ this IServiceCollection services,
+ TokenCredential tokenCredential,
+ string[]? scopes = null)
+ {
+ services.AddSingleton(_ => new GraphServiceClient(tokenCredential, scopes ?? DefaultScopes));
+
+ RegisterGraphServices(services);
+
+ return services;
+ }
+
+ ///
+ /// Adds the to the service collection using the provided and optional scopes.
+ ///
+ /// The instance to augment.
+ /// The containing configuration for the service.
+ /// Optional array of scopes for the .
+ /// The same instance as .
+ /// Thrown if the are invalid.
+ public static IServiceCollection AddMicrosoftGraphServices(
+ this IServiceCollection services,
+ GraphServiceOptions graphServiceOptions,
+ string[]? scopes = null)
+ {
+ ArgumentNullException.ThrowIfNull(graphServiceOptions);
+
+ if (!graphServiceOptions.IsValid())
+ {
+ throw new InvalidOperationException($"Required service '{nameof(GraphServiceOptions)}' is not registered");
+ }
+
+ services.AddSingleton(_ =>
+ {
+ var options = new TokenCredentialOptions
+ {
+ AuthorityHost = AzureAuthorityHosts.AzurePublicCloud,
+ };
+
+ var clientSecretCredential = new ClientSecretCredential(
+ graphServiceOptions.TenantId,
+ graphServiceOptions.ClientId,
+ graphServiceOptions.ClientSecret,
+ options);
+
+ return new GraphServiceClient(clientSecretCredential, scopes ?? DefaultScopes);
+ });
+
+ RegisterGraphServices(services);
+
+ return services;
+ }
+
+ private static void RegisterGraphServices(
+ IServiceCollection services)
+ {
+ services.AddGraphService();
+ services.AddGraphService();
+ services.AddGraphService();
+ services.AddGraphService();
+ services.AddGraphService();
+ }
+
+ private static void AddGraphService(
+ this IServiceCollection services)
+ where TService : class
+ where TImplementation : GraphServiceClientWrapper, TService
+ {
+ services.AddSingleton(s => (TImplementation)Activator.CreateInstance(
+ typeof(TImplementation),
+ s.GetRequiredService(),
+ s.GetRequiredService())!);
+ }
+}
\ No newline at end of file
diff --git a/src/Atc.Microsoft.Graph.Client/Factories/RequestConfigurationFactory.cs b/src/Atc.Microsoft.Graph.Client/Factories/RequestConfigurationFactory.cs
new file mode 100644
index 0000000..a1db8d7
--- /dev/null
+++ b/src/Atc.Microsoft.Graph.Client/Factories/RequestConfigurationFactory.cs
@@ -0,0 +1,278 @@
+namespace Atc.Microsoft.Graph.Client.Factories;
+
+public static class RequestConfigurationFactory
+{
+ public static Action> CreateForAttachments(
+ List? expandQueryParameters,
+ string? filterQueryParameter,
+ List? selectQueryParameters) =>
+ rc =>
+ {
+ if (expandQueryParameters is not null &&
+ expandQueryParameters.Count != 0)
+ {
+ rc.QueryParameters.Expand = [.. expandQueryParameters];
+ }
+
+ if (!string.IsNullOrEmpty(filterQueryParameter))
+ {
+ rc.QueryParameters.Filter = filterQueryParameter;
+ }
+
+ if (selectQueryParameters is not null &&
+ selectQueryParameters.Count != 0)
+ {
+ rc.QueryParameters.Select = [.. selectQueryParameters];
+ }
+ };
+
+ public static Action> CreateForDrives(
+ List? expandQueryParameters,
+ string? filterQueryParameter,
+ List? selectQueryParameters) =>
+ rc =>
+ {
+ if (expandQueryParameters is not null &&
+ expandQueryParameters.Count != 0)
+ {
+ rc.QueryParameters.Expand = [.. expandQueryParameters];
+ }
+
+ if (!string.IsNullOrEmpty(filterQueryParameter))
+ {
+ rc.QueryParameters.Filter = filterQueryParameter;
+ }
+
+ if (selectQueryParameters is not null &&
+ selectQueryParameters.Count != 0)
+ {
+ rc.QueryParameters.Select = [.. selectQueryParameters];
+ }
+ };
+
+ public static Action> CreateForItems(
+ List? expandQueryParameters,
+ string? filterQueryParameter,
+ List? selectQueryParameters) =>
+ rc =>
+ {
+ if (expandQueryParameters is not null &&
+ expandQueryParameters.Count != 0)
+ {
+ rc.QueryParameters.Expand = [.. expandQueryParameters];
+ }
+
+ if (!string.IsNullOrEmpty(filterQueryParameter))
+ {
+ rc.QueryParameters.Filter = filterQueryParameter;
+ }
+
+ if (selectQueryParameters is not null &&
+ selectQueryParameters.Count != 0)
+ {
+ rc.QueryParameters.Select = [.. selectQueryParameters];
+ }
+ };
+
+ public static Action> CreateForItemsWithDelta(
+ string? filterQueryParameter,
+ List? selectQueryParameters) =>
+ rc =>
+ {
+ if (!string.IsNullOrEmpty(filterQueryParameter))
+ {
+ rc.QueryParameters.Filter = filterQueryParameter;
+ }
+
+ if (selectQueryParameters is not null &&
+ selectQueryParameters.Count != 0)
+ {
+ rc.QueryParameters.Select = [.. selectQueryParameters];
+ }
+ };
+
+ public static Action> CreateForChildFolders(
+ List? expandQueryParameters,
+ string? filterQueryParameter,
+ List? selectQueryParameters) =>
+ rc =>
+ {
+ if (expandQueryParameters is not null &&
+ expandQueryParameters.Count != 0)
+ {
+ rc.QueryParameters.Expand = [.. expandQueryParameters];
+ }
+
+ if (!string.IsNullOrEmpty(filterQueryParameter))
+ {
+ rc.QueryParameters.Filter = filterQueryParameter;
+ }
+
+ if (selectQueryParameters is not null &&
+ selectQueryParameters.Count != 0)
+ {
+ rc.QueryParameters.Select = [.. selectQueryParameters];
+ }
+ };
+
+ public static Action> CreateForMailFolders(
+ List? expandQueryParameters,
+ string? filterQueryParameter,
+ List? selectQueryParameters) =>
+ rc =>
+ {
+ if (expandQueryParameters is not null &&
+ expandQueryParameters.Count != 0)
+ {
+ rc.QueryParameters.Expand = [.. expandQueryParameters];
+ }
+
+ if (!string.IsNullOrEmpty(filterQueryParameter))
+ {
+ rc.QueryParameters.Filter = filterQueryParameter;
+ }
+
+ if (selectQueryParameters is not null &&
+ selectQueryParameters.Count != 0)
+ {
+ rc.QueryParameters.Select = [.. selectQueryParameters];
+ }
+ };
+
+ public static Action> CreateForMessagesMailFolder(
+ List? expandQueryParameters,
+ string? filterQueryParameter,
+ List? selectQueryParameters) =>
+ rc =>
+ {
+ if (expandQueryParameters is not null &&
+ expandQueryParameters.Count != 0)
+ {
+ rc.QueryParameters.Expand = [.. expandQueryParameters];
+ }
+
+ if (!string.IsNullOrEmpty(filterQueryParameter))
+ {
+ rc.QueryParameters.Filter = filterQueryParameter;
+ }
+
+ if (selectQueryParameters is not null &&
+ selectQueryParameters.Count != 0)
+ {
+ rc.QueryParameters.Select = [.. selectQueryParameters];
+ }
+ };
+
+ public static Action> CreateForMessagesUserId(
+ List? expandQueryParameters,
+ string? filterQueryParameter,
+ List? selectQueryParameters) =>
+ rc =>
+ {
+ if (expandQueryParameters is not null &&
+ expandQueryParameters.Count != 0)
+ {
+ rc.QueryParameters.Expand = [.. expandQueryParameters];
+ }
+
+ if (!string.IsNullOrEmpty(filterQueryParameter))
+ {
+ rc.QueryParameters.Filter = filterQueryParameter;
+ }
+
+ if (selectQueryParameters is not null &&
+ selectQueryParameters.Count != 0)
+ {
+ rc.QueryParameters.Select = [.. selectQueryParameters];
+ }
+ };
+
+ public static Action> CreateForMessagesDelta(
+ string? filterQueryParameter,
+ List? selectQueryParameters) =>
+ rc =>
+ {
+ if (!string.IsNullOrEmpty(filterQueryParameter))
+ {
+ rc.QueryParameters.Filter = filterQueryParameter;
+ }
+
+ if (selectQueryParameters is not null &&
+ selectQueryParameters.Count != 0)
+ {
+ rc.QueryParameters.Select = [.. selectQueryParameters];
+ }
+ };
+
+ public static Action> CreateForSites(
+ List? expandQueryParameters,
+ string? filterQueryParameter,
+ List? selectQueryParameters) =>
+ rc =>
+ {
+ if (expandQueryParameters is not null &&
+ expandQueryParameters.Count != 0)
+ {
+ rc.QueryParameters.Expand = [.. expandQueryParameters];
+ }
+
+ if (!string.IsNullOrEmpty(filterQueryParameter))
+ {
+ rc.QueryParameters.Filter = filterQueryParameter;
+ }
+
+ if (selectQueryParameters is not null &&
+ selectQueryParameters.Count != 0)
+ {
+ rc.QueryParameters.Select = [.. selectQueryParameters];
+ }
+ };
+
+ public static Action> CreateForTeams(
+ List? expandQueryParameters,
+ string? filterQueryParameter,
+ List? selectQueryParameters) =>
+ rc =>
+ {
+ if (expandQueryParameters is not null &&
+ expandQueryParameters.Count != 0)
+ {
+ rc.QueryParameters.Expand = [.. expandQueryParameters];
+ }
+
+ if (!string.IsNullOrEmpty(filterQueryParameter))
+ {
+ rc.QueryParameters.Filter = filterQueryParameter;
+ }
+
+ if (selectQueryParameters is not null &&
+ selectQueryParameters.Count != 0)
+ {
+ rc.QueryParameters.Select = [.. selectQueryParameters];
+ }
+ };
+
+ public static Action> CreateForUsers(
+ List? expandQueryParameters,
+ string? filterQueryParameter,
+ List? selectQueryParameters) =>
+ rc =>
+ {
+ if (expandQueryParameters is not null &&
+ expandQueryParameters.Count != 0)
+ {
+ rc.QueryParameters.Expand = [.. expandQueryParameters];
+ }
+
+ if (!string.IsNullOrEmpty(filterQueryParameter))
+ {
+ rc.QueryParameters.Filter = filterQueryParameter;
+ }
+
+ if (selectQueryParameters is not null &&
+ selectQueryParameters.Count != 0)
+ {
+ rc.QueryParameters.Select = [.. selectQueryParameters];
+ }
+ };
+}
\ No newline at end of file
diff --git a/src/Atc.Microsoft.Graph.Client/GlobalUsings.cs b/src/Atc.Microsoft.Graph.Client/GlobalUsings.cs
new file mode 100644
index 0000000..9b6cd30
--- /dev/null
+++ b/src/Atc.Microsoft.Graph.Client/GlobalUsings.cs
@@ -0,0 +1,29 @@
+global using System.Diagnostics.CodeAnalysis;
+global using System.Net;
+global using System.Runtime.CompilerServices;
+global using Atc.Microsoft.Graph.Client.Factories;
+global using Atc.Microsoft.Graph.Client.Options;
+global using Atc.Microsoft.Graph.Client.Services;
+global using Atc.Microsoft.Graph.Client.Services.OneDrive;
+global using Atc.Microsoft.Graph.Client.Services.Outlook;
+global using Atc.Microsoft.Graph.Client.Services.Sharepoint;
+global using Atc.Microsoft.Graph.Client.Services.Teams;
+global using Atc.Microsoft.Graph.Client.Services.Users;
+global using Azure.Core;
+global using Azure.Identity;
+global using Microsoft.Extensions.DependencyInjection;
+global using Microsoft.Extensions.Logging;
+global using Microsoft.Graph;
+global using Microsoft.Graph.Drives.Item.List.Items;
+global using Microsoft.Graph.Models;
+global using Microsoft.Graph.Models.ODataErrors;
+global using Microsoft.Graph.Sites;
+global using Microsoft.Graph.Sites.Item.Drives;
+global using Microsoft.Graph.Teams;
+global using Microsoft.Graph.Users;
+global using Microsoft.Graph.Users.Item.MailFolders;
+global using Microsoft.Graph.Users.Item.MailFolders.Item.ChildFolders;
+global using Microsoft.Graph.Users.Item.Messages.Item.Attachments;
+global using Microsoft.Kiota.Abstractions;
+global using Polly;
+global using Polly.Retry;
\ No newline at end of file
diff --git a/src/Atc.Microsoft.Graph.Client/LoggingEventIdConstants.cs b/src/Atc.Microsoft.Graph.Client/LoggingEventIdConstants.cs
new file mode 100644
index 0000000..2d291b1
--- /dev/null
+++ b/src/Atc.Microsoft.Graph.Client/LoggingEventIdConstants.cs
@@ -0,0 +1,24 @@
+namespace Atc.Microsoft.Graph.Client;
+
+internal static class LoggingEventIdConstants
+{
+ internal static class GraphServiceClientWrapper
+ {
+ public const int GetFailure = 10_000;
+
+ public const int SubscriptionSetupFailed = 10_100;
+ public const int SubscriptionRenewalFailed = 10_101;
+ public const int SubscriptionDeletionFailed = 10_102;
+
+ public const int DownloadFileFailed = 10_200;
+ public const int DownloadFileRetrying = 10_201;
+ public const int DownloadFileEmpty = 10_202;
+
+ public const int DeltaLinkNotFoundForDrive = 10_300;
+
+ public const int DriveNotFoundForTeam = 10_400;
+
+ public const int PageIteratorCount = 10_500;
+ public const int PageIteratorTotalCount = 10_501;
+ }
+}
\ No newline at end of file
diff --git a/src/Atc.Microsoft.Graph.Client/MicrosoftGraphConstants.cs b/src/Atc.Microsoft.Graph.Client/MicrosoftGraphConstants.cs
new file mode 100644
index 0000000..fa3f9d5
--- /dev/null
+++ b/src/Atc.Microsoft.Graph.Client/MicrosoftGraphConstants.cs
@@ -0,0 +1,6 @@
+namespace Atc.Microsoft.Graph.Client;
+
+public static class MicrosoftGraphConstants
+{
+ public const int RetryWaitDelayInMs = 500;
+}
\ No newline at end of file
diff --git a/src/Atc.Microsoft.Graph.Client/Options/GraphServiceOptions.cs b/src/Atc.Microsoft.Graph.Client/Options/GraphServiceOptions.cs
new file mode 100644
index 0000000..8b346b6
--- /dev/null
+++ b/src/Atc.Microsoft.Graph.Client/Options/GraphServiceOptions.cs
@@ -0,0 +1,17 @@
+namespace Atc.Microsoft.Graph.Client.Options;
+
+public sealed class GraphServiceOptions
+{
+ public string TenantId { get; set; } = string.Empty;
+
+ public string ClientId { get; set; } = string.Empty;
+
+ public string ClientSecret { get; set; } = string.Empty;
+
+ public bool IsValid() => !string.IsNullOrEmpty(TenantId) &&
+ !string.IsNullOrEmpty(ClientId) &&
+ !string.IsNullOrEmpty(ClientSecret);
+
+ public override string ToString()
+ => $"{nameof(TenantId)}: {TenantId}, {nameof(ClientId)}: {ClientId}, {nameof(ClientSecret)}: {ClientSecret}";
+}
\ No newline at end of file
diff --git a/src/Atc.Microsoft.Graph.Client/Services/GraphServiceClientWrapper.cs b/src/Atc.Microsoft.Graph.Client/Services/GraphServiceClientWrapper.cs
new file mode 100644
index 0000000..2dbead4
--- /dev/null
+++ b/src/Atc.Microsoft.Graph.Client/Services/GraphServiceClientWrapper.cs
@@ -0,0 +1,45 @@
+namespace Atc.Microsoft.Graph.Client.Services;
+
+public abstract partial class GraphServiceClientWrapper
+{
+ protected GraphServiceClient Client { get; }
+
+ protected ResiliencePipeline DownloadResiliencePipeline { get; }
+
+ protected GraphServiceClientWrapper(
+ ILoggerFactory loggerFactory,
+ GraphServiceClient client)
+ {
+ this.logger = loggerFactory.CreateLogger();
+ this.Client = client;
+
+ var retryStrategyOptions = new RetryStrategyOptions
+ {
+ ShouldHandle = new PredicateBuilder()
+ .Handle()
+ .Handle(),
+ BackoffType = DelayBackoffType.Exponential,
+ UseJitter = true,
+ MaxRetryAttempts = 3,
+ Delay = TimeSpan.FromSeconds(3),
+ OnRetry = args =>
+ {
+ var errorMessage = args.Outcome.Result switch
+ {
+ ODataError oDataError => oDataError.Error?.Message,
+ Exception ex => ex.GetLastInnerMessage(),
+ _ => null,
+ };
+
+ errorMessage ??= args.Outcome.Exception?.GetLastInnerMessage() ?? "Unknown Exception";
+
+ LogDownloadFileRetrying(errorMessage);
+ return ValueTask.CompletedTask;
+ },
+ };
+
+ DownloadResiliencePipeline = new ResiliencePipelineBuilder()
+ .AddRetry(retryStrategyOptions)
+ .Build();
+ }
+}
\ No newline at end of file
diff --git a/src/Atc.Microsoft.Graph.Client/Services/GraphServiceClientWrapperLoggerMessages.cs b/src/Atc.Microsoft.Graph.Client/Services/GraphServiceClientWrapperLoggerMessages.cs
new file mode 100644
index 0000000..1678be2
--- /dev/null
+++ b/src/Atc.Microsoft.Graph.Client/Services/GraphServiceClientWrapperLoggerMessages.cs
@@ -0,0 +1,118 @@
+namespace Atc.Microsoft.Graph.Client.Services;
+
+///
+/// GraphServiceClientWrapper LoggerMessages.
+///
+[SuppressMessage("Design", "MA0048:File name must match type name", Justification = "OK - By Design")]
+public abstract partial class GraphServiceClientWrapper
+{
+ private readonly ILogger logger;
+
+ [LoggerMessage(
+ EventId = LoggingEventIdConstants.GraphServiceClientWrapper.GetFailure,
+ Level = LogLevel.Error,
+ Message = "{callerMethodName}({callerLineNumber}) - Failed to retrieve data: '{errorMessage}'.")]
+ protected partial void LogGetFailure(
+ string? errorMessage,
+ [CallerMemberName] string callerMethodName = "",
+ [CallerLineNumber] int callerLineNumber = 0);
+
+ [LoggerMessage(
+ EventId = LoggingEventIdConstants.GraphServiceClientWrapper.SubscriptionSetupFailed,
+ Level = LogLevel.Error,
+ Message = "{callerMethodName}({callerLineNumber}) - Failed to setup subscription for the resource '{resource}': '{errorMessage}'.")]
+ protected partial void LogSubscriptionSetupFailed(
+ string? resource,
+ string? errorMessage,
+ [CallerMemberName] string callerMethodName = "",
+ [CallerLineNumber] int callerLineNumber = 0);
+
+ [LoggerMessage(
+ EventId = LoggingEventIdConstants.GraphServiceClientWrapper.SubscriptionRenewalFailed,
+ Level = LogLevel.Error,
+ Message = "{callerMethodName}({callerLineNumber}) - Failed to renew subscription with id '{subscriptionId}' with expirationDate '{expirationDate}': '{errorMessage}'.")]
+ protected partial void LogSubscriptionRenewalFailed(
+ Guid subscriptionId,
+ DateTimeOffset expirationDate,
+ string? errorMessage,
+ [CallerMemberName] string callerMethodName = "",
+ [CallerLineNumber] int callerLineNumber = 0);
+
+ [LoggerMessage(
+ EventId = LoggingEventIdConstants.GraphServiceClientWrapper.SubscriptionDeletionFailed,
+ Level = LogLevel.Error,
+ Message = "{callerMethodName}({callerLineNumber}) - Failed to delete subscription with id '{subscriptionId}': '{errorMessage}'.")]
+ protected partial void LogSubscriptionDeletionFailed(
+ Guid subscriptionId,
+ string? errorMessage,
+ [CallerMemberName] string callerMethodName = "",
+ [CallerLineNumber] int callerLineNumber = 0);
+
+ [LoggerMessage(
+ EventId = LoggingEventIdConstants.GraphServiceClientWrapper.DownloadFileFailed,
+ Level = LogLevel.Error,
+ Message = "{callerMethodName}({callerLineNumber}) - Failed to download file with id: '{fileId}': '{errorMessage}'.")]
+ protected partial void LogDownloadFileFailed(
+ string fileId,
+ string? errorMessage,
+ [CallerMemberName] string callerMethodName = "",
+ [CallerLineNumber] int callerLineNumber = 0);
+
+ [LoggerMessage(
+ EventId = LoggingEventIdConstants.GraphServiceClientWrapper.DownloadFileRetrying,
+ Level = LogLevel.Warning,
+ Message = "{callerMethodName}({callerLineNumber}) - Retrying download of file: '{errorMessage}'.")]
+ protected partial void LogDownloadFileRetrying(
+ string? errorMessage,
+ [CallerMemberName] string callerMethodName = "",
+ [CallerLineNumber] int callerLineNumber = 0);
+
+ [LoggerMessage(
+ EventId = LoggingEventIdConstants.GraphServiceClientWrapper.DownloadFileEmpty,
+ Level = LogLevel.Warning,
+ Message = "{callerMethodName}({callerLineNumber}) - File to download is empty - id: '{fileId}'.")]
+ protected partial void LogDownloadFileEmpty(
+ string fileId,
+ [CallerMemberName] string callerMethodName = "",
+ [CallerLineNumber] int callerLineNumber = 0);
+
+ [LoggerMessage(
+ EventId = LoggingEventIdConstants.GraphServiceClientWrapper.DeltaLinkNotFoundForDrive,
+ Level = LogLevel.Warning,
+ Message = "{callerMethodName}({callerLineNumber}) - Could not find Delta Link for drive with id: '{driveId}': '{errorMessage}'.")]
+ protected partial void LogDeltaLinkNotFoundForDrive(
+ string driveId,
+ string? errorMessage,
+ [CallerMemberName] string callerMethodName = "",
+ [CallerLineNumber] int callerLineNumber = 0);
+
+ [LoggerMessage(
+ EventId = LoggingEventIdConstants.GraphServiceClientWrapper.DriveNotFoundForTeam,
+ Level = LogLevel.Warning,
+ Message = "{callerMethodName}({callerLineNumber}) - Could not find drive for team with id: '{teamId}': '{errorMessage}'.")]
+ protected partial void LogDriveNotFoundForTeam(
+ string teamId,
+ string? errorMessage,
+ [CallerMemberName] string callerMethodName = "",
+ [CallerLineNumber] int callerLineNumber = 0);
+
+ [LoggerMessage(
+ EventId = LoggingEventIdConstants.GraphServiceClientWrapper.PageIteratorCount,
+ Level = LogLevel.Debug,
+ Message = "{callerMethodName}({callerLineNumber}) - {area} Iterator processed {count} items.")]
+ protected partial void LogPageIteratorCount(
+ string area,
+ int count,
+ [CallerMemberName] string callerMethodName = "",
+ [CallerLineNumber] int callerLineNumber = 0);
+
+ [LoggerMessage(
+ EventId = LoggingEventIdConstants.GraphServiceClientWrapper.PageIteratorTotalCount,
+ Level = LogLevel.Debug,
+ Message = "{callerMethodName}({callerLineNumber}) - {area} Iterator processed a total of {count} items.")]
+ protected partial void LogPageIteratorTotalCount(
+ string area,
+ int count,
+ [CallerMemberName] string callerMethodName = "",
+ [CallerLineNumber] int callerLineNumber = 0);
+}
\ No newline at end of file
diff --git a/src/Atc.Microsoft.Graph.Client/Services/OneDrive/IOneDriveGraphService.cs b/src/Atc.Microsoft.Graph.Client/Services/OneDrive/IOneDriveGraphService.cs
new file mode 100644
index 0000000..1a5e0ec
--- /dev/null
+++ b/src/Atc.Microsoft.Graph.Client/Services/OneDrive/IOneDriveGraphService.cs
@@ -0,0 +1,38 @@
+namespace Atc.Microsoft.Graph.Client.Services.OneDrive;
+
+public interface IOneDriveGraphService
+{
+ Task<(HttpStatusCode StatusCode, IList Data)> GetDrivesBySiteId(
+ Guid siteId,
+ List? expandQueryParameters,
+ string? filterQueryParameter,
+ List? selectQueryParameters,
+ CancellationToken cancellationToken = default);
+
+ Task GetDriveByTeamId(
+ string teamId,
+ CancellationToken cancellationToken = default);
+
+ Task GetDeltaTokenForDriveItemsByDriveId(
+ string driveId,
+ CancellationToken cancellationToken = default);
+
+ Task<(HttpStatusCode StatusCode, IList Data)> GetDriveItemsByDriveId(
+ string driveId,
+ List? expandQueryParameters,
+ string? filterQueryParameter,
+ List? selectQueryParameters,
+ CancellationToken cancellationToken = default);
+
+ Task<(HttpStatusCode StatusCode, IList Data)> GetDriveItemsByDriveIdAndDeltaToken(
+ string driveId,
+ string deltaToken,
+ string? filterQueryParameter,
+ List? selectQueryParameters,
+ CancellationToken cancellationToken = default);
+
+ Task DownloadFile(
+ string driveId,
+ string fileId,
+ CancellationToken cancellationToken = default);
+}
\ No newline at end of file
diff --git a/src/Atc.Microsoft.Graph.Client/Services/OneDrive/OneDriveGraphService.cs b/src/Atc.Microsoft.Graph.Client/Services/OneDrive/OneDriveGraphService.cs
new file mode 100644
index 0000000..dc25d7b
--- /dev/null
+++ b/src/Atc.Microsoft.Graph.Client/Services/OneDrive/OneDriveGraphService.cs
@@ -0,0 +1,363 @@
+namespace Atc.Microsoft.Graph.Client.Services.OneDrive;
+
+public sealed class OneDriveGraphService : GraphServiceClientWrapper, IOneDriveGraphService
+{
+ public OneDriveGraphService(
+ ILoggerFactory loggerFactory,
+ GraphServiceClient client)
+ : base(loggerFactory, client)
+ {
+ }
+
+ public async Task<(HttpStatusCode StatusCode, IList Data)> GetDrivesBySiteId(
+ Guid siteId,
+ List? expandQueryParameters,
+ string? filterQueryParameter,
+ List? selectQueryParameters,
+ CancellationToken cancellationToken = default)
+ {
+ List pagedItems = [];
+ var count = 0;
+
+ try
+ {
+ var requestInformation = Client
+ .Sites[siteId.ToString()]
+ .Drives
+ .ToGetRequestInformation(
+ RequestConfigurationFactory.CreateForDrives(
+ expandQueryParameters,
+ filterQueryParameter,
+ selectQueryParameters));
+
+ var response = await Client.RequestAdapter.SendAsync(
+ requestInformation,
+ (_) => new DriveCollectionResponse(),
+ cancellationToken: cancellationToken);
+
+ if (response is null)
+ {
+ return (HttpStatusCode.InternalServerError, pagedItems);
+ }
+
+ var pageIterator = PageIterator.CreatePageIterator(
+ Client,
+ response,
+ item =>
+ {
+ pagedItems.Add(item);
+
+ count++;
+ if (count % 1000 == 0)
+ {
+ LogPageIteratorCount(nameof(Drive), count);
+ }
+
+ return true;
+ });
+
+ try
+ {
+ await pageIterator.IterateAsync(cancellationToken);
+ }
+ catch (ODataError odataError) when (odataError.ResponseStatusCode == (int)HttpStatusCode.TooManyRequests)
+ {
+ await Task.Delay(MicrosoftGraphConstants.RetryWaitDelayInMs, cancellationToken);
+
+ await pageIterator.IterateAsync(cancellationToken);
+ }
+ catch (ODataError odataError) when (odataError.ResponseStatusCode == (int)HttpStatusCode.Gone)
+ {
+ return (HttpStatusCode.Gone, pagedItems);
+ }
+
+ LogPageIteratorTotalCount(nameof(Drive), count);
+
+ return (HttpStatusCode.OK, pagedItems);
+ }
+ catch (ODataError odataError)
+ {
+ LogGetFailure(odataError.Error?.Message);
+ return (HttpStatusCode.InternalServerError, pagedItems);
+ }
+ catch (Exception ex)
+ {
+ LogGetFailure(ex.GetLastInnerMessage());
+ return (HttpStatusCode.InternalServerError, pagedItems);
+ }
+ }
+
+ public async Task GetDriveByTeamId(
+ string teamId,
+ CancellationToken cancellationToken = default)
+ {
+ try
+ {
+ var drive = await Client
+ .Groups[teamId]
+ .Drive
+ .GetAsync(cancellationToken: cancellationToken);
+
+ if (drive is not null)
+ {
+ return drive;
+ }
+
+ LogDriveNotFoundForTeam(teamId, errorMessage: null);
+ return null;
+ }
+ catch (ODataError odataError)
+ {
+ LogDriveNotFoundForTeam(teamId, odataError.Error?.Message);
+ return null;
+ }
+ catch (Exception ex)
+ {
+ LogGetFailure(ex.GetLastInnerMessage());
+ return null;
+ }
+ }
+
+ public async Task GetDeltaTokenForDriveItemsByDriveId(
+ string driveId,
+ CancellationToken cancellationToken = default)
+ {
+ try
+ {
+ var deltaWithTokenResponse = await Client
+ .Drives[driveId]
+ .Items["root"]
+ .DeltaWithToken("latest")
+ .GetAsDeltaWithTokenGetResponseAsync(cancellationToken: cancellationToken);
+
+ if (deltaWithTokenResponse?.OdataDeltaLink is null)
+ {
+ LogDeltaLinkNotFoundForDrive(driveId, errorMessage: null);
+ return null;
+ }
+
+ var sa = deltaWithTokenResponse.OdataDeltaLink.Split("token='", StringSplitOptions.RemoveEmptyEntries);
+ if (sa.Length == 2)
+ {
+ return sa[1].Replace("')", string.Empty, StringComparison.OrdinalIgnoreCase);
+ }
+
+ LogDeltaLinkNotFoundForDrive(driveId, errorMessage: null);
+ return null;
+ }
+ catch (ODataError odataError)
+ {
+ LogDeltaLinkNotFoundForDrive(driveId, odataError.Error?.Message);
+ return null;
+ }
+ catch (Exception ex)
+ {
+ LogGetFailure(ex.GetLastInnerMessage());
+ return null;
+ }
+ }
+
+ public async Task<(HttpStatusCode StatusCode, IList Data)> GetDriveItemsByDriveId(
+ string driveId,
+ List? expandQueryParameters,
+ string? filterQueryParameter,
+ List? selectQueryParameters,
+ CancellationToken cancellationToken = default)
+ {
+ var requestInformation = Client
+ .Drives[driveId]
+ .List
+ .Items
+ .ToGetRequestInformation(
+ RequestConfigurationFactory.CreateForItems(
+ expandQueryParameters,
+ filterQueryParameter,
+ selectQueryParameters));
+
+ var (httpStatusCode, data) = await GetAllListItemsByDriveId(requestInformation, cancellationToken);
+
+ var driveItems = data
+ .Where(x => x.DriveItem is not null)
+ .Select(x => x.DriveItem!)
+ .ToList();
+
+ return (httpStatusCode, driveItems);
+ }
+
+ public async Task<(HttpStatusCode StatusCode, IList Data)> GetDriveItemsByDriveIdAndDeltaToken(
+ string driveId,
+ string deltaToken,
+ string? filterQueryParameter,
+ List? selectQueryParameters,
+ CancellationToken cancellationToken = default)
+ {
+ List pagedItems = [];
+ var count = 0;
+
+ try
+ {
+ var requestInformation = Client
+ .Drives[driveId]
+ .Items["root"]
+ .DeltaWithToken(deltaToken)
+ .ToGetRequestInformation(
+ RequestConfigurationFactory.CreateForItemsWithDelta(
+ filterQueryParameter,
+ selectQueryParameters));
+
+ var response = await Client.RequestAdapter.SendAsync(
+ requestInformation,
+ (_) => new DriveItemCollectionResponse(),
+ cancellationToken: cancellationToken);
+
+ if (response is null)
+ {
+ return (HttpStatusCode.InternalServerError, pagedItems);
+ }
+
+ var pageIterator = PageIterator.CreatePageIterator(
+ Client,
+ response,
+ item =>
+ {
+ pagedItems.Add(item);
+
+ count++;
+ if (count % 1000 == 0)
+ {
+ LogPageIteratorCount(nameof(DriveItem), count);
+ }
+
+ return true;
+ });
+
+ try
+ {
+ await pageIterator.IterateAsync(cancellationToken);
+ }
+ catch (ODataError odataError) when (odataError.ResponseStatusCode == (int)HttpStatusCode.TooManyRequests)
+ {
+ await Task.Delay(MicrosoftGraphConstants.RetryWaitDelayInMs, cancellationToken);
+
+ await pageIterator.IterateAsync(cancellationToken);
+ }
+ catch (ODataError odataError) when (odataError.ResponseStatusCode == (int)HttpStatusCode.Gone)
+ {
+ return (HttpStatusCode.Gone, pagedItems);
+ }
+
+ LogPageIteratorTotalCount(nameof(DriveItem), count);
+
+ return (HttpStatusCode.OK, pagedItems);
+ }
+ catch (ODataError odataError)
+ {
+ LogGetFailure(odataError.Error?.Message);
+ return (HttpStatusCode.InternalServerError, pagedItems);
+ }
+ catch (Exception ex)
+ {
+ LogGetFailure(ex.GetLastInnerMessage());
+ return (HttpStatusCode.InternalServerError, pagedItems);
+ }
+ }
+
+ public async Task DownloadFile(
+ string driveId,
+ string fileId,
+ CancellationToken cancellationToken = default)
+ {
+ try
+ {
+ return await DownloadResiliencePipeline.ExecuteAsync(
+ async context =>
+ {
+ var stream = await Client
+ .Drives[driveId]
+ .Items[fileId]
+ .Content
+ .GetAsync(cancellationToken: context);
+
+ if (stream is null)
+ {
+ LogDownloadFileEmpty(fileId);
+ }
+
+ return stream;
+ },
+ cancellationToken);
+ }
+ catch (Exception ex)
+ {
+ LogDownloadFileFailed(fileId, ex.GetLastInnerMessage());
+ return null;
+ }
+ }
+
+ private async Task<(HttpStatusCode StatusCode, IList Data)> GetAllListItemsByDriveId(
+ RequestInformation requestInformation,
+ CancellationToken cancellationToken)
+ {
+ List pagedItems = [];
+ var count = 0;
+
+ try
+ {
+ var response = await Client.RequestAdapter.SendAsync(
+ requestInformation,
+ (_) => new ListItemCollectionResponse(),
+ cancellationToken: cancellationToken);
+
+ if (response is null)
+ {
+ return (HttpStatusCode.InternalServerError, pagedItems);
+ }
+
+ var pageIterator = PageIterator.CreatePageIterator(
+ Client,
+ response,
+ item =>
+ {
+ pagedItems.Add(item);
+
+ count++;
+ if (count % 1000 == 0)
+ {
+ LogPageIteratorCount(nameof(ListItem), count);
+ }
+
+ return true;
+ });
+
+ try
+ {
+ await pageIterator.IterateAsync(cancellationToken);
+ }
+ catch (ODataError odataError) when (odataError.ResponseStatusCode == (int)HttpStatusCode.TooManyRequests)
+ {
+ await Task.Delay(MicrosoftGraphConstants.RetryWaitDelayInMs, cancellationToken);
+
+ await pageIterator.IterateAsync(cancellationToken);
+ }
+ catch (ODataError odataError) when (odataError.ResponseStatusCode == (int)HttpStatusCode.Gone)
+ {
+ return (HttpStatusCode.Gone, pagedItems);
+ }
+
+ LogPageIteratorTotalCount(nameof(ListItem), count);
+
+ return (HttpStatusCode.OK, pagedItems);
+ }
+ catch (ODataError odataError)
+ {
+ LogGetFailure(odataError.Error?.Message);
+ return (HttpStatusCode.InternalServerError, pagedItems);
+ }
+ catch (Exception ex)
+ {
+ var errorMessage = ex.GetLastInnerMessage();
+ LogGetFailure(errorMessage);
+ return (HttpStatusCode.InternalServerError, pagedItems);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Atc.Microsoft.Graph.Client/Services/Outlook/IOutlookGraphService.cs b/src/Atc.Microsoft.Graph.Client/Services/Outlook/IOutlookGraphService.cs
new file mode 100644
index 0000000..25eee9b
--- /dev/null
+++ b/src/Atc.Microsoft.Graph.Client/Services/Outlook/IOutlookGraphService.cs
@@ -0,0 +1,43 @@
+namespace Atc.Microsoft.Graph.Client.Services.Outlook;
+
+public interface IOutlookGraphService
+{
+ Task<(HttpStatusCode StatusCode, IList Data)> GetRootMailFoldersByUserId(
+ string userId,
+ List? expandQueryParameters,
+ string? filterQueryParameter,
+ List? selectQueryParameters,
+ CancellationToken cancellationToken = default);
+
+ Task<(HttpStatusCode StatusCode, IList Data)> GetMailFoldersByUserIdAndFolderId(
+ string userId,
+ string folderId,
+ List? expandQueryParameters,
+ string? filterQueryParameter,
+ List? selectQueryParameters,
+ CancellationToken cancellationToken = default);
+
+ Task<(HttpStatusCode StatusCode, IList Data)> GetMessagesByUserId(
+ string userId,
+ List? expandQueryParameters,
+ string? filterQueryParameter,
+ List? selectQueryParameters,
+ CancellationToken cancellationToken = default);
+
+ Task<(HttpStatusCode StatusCode, IList Data, string? DeltaToken)> GetMessagesByUserIdAndFolderId(
+ string userId,
+ string folderId,
+ List? expandQueryParameters,
+ string? filterQueryParameter,
+ List? selectQueryParameters,
+ string? deltaToken = null,
+ CancellationToken cancellationToken = default);
+
+ Task<(HttpStatusCode StatusCode, IList Data)> GetFileAttachmentsByUserIdAndMessageId(
+ string userId,
+ string messageId,
+ List? expandQueryParameters,
+ string? filterQueryParameter,
+ List? selectQueryParameters,
+ CancellationToken cancellationToken = default);
+}
\ No newline at end of file
diff --git a/src/Atc.Microsoft.Graph.Client/Services/Outlook/OutlookGraphService.cs b/src/Atc.Microsoft.Graph.Client/Services/Outlook/OutlookGraphService.cs
new file mode 100644
index 0000000..8b41ae2
--- /dev/null
+++ b/src/Atc.Microsoft.Graph.Client/Services/Outlook/OutlookGraphService.cs
@@ -0,0 +1,539 @@
+namespace Atc.Microsoft.Graph.Client.Services.Outlook;
+
+public sealed class OutlookGraphService : GraphServiceClientWrapper, IOutlookGraphService
+{
+ public OutlookGraphService(
+ ILoggerFactory loggerFactory,
+ GraphServiceClient client)
+ : base(loggerFactory, client)
+ {
+ }
+
+ public async Task<(HttpStatusCode StatusCode, IList Data)> GetRootMailFoldersByUserId(
+ string userId,
+ List? expandQueryParameters,
+ string? filterQueryParameter,
+ List? selectQueryParameters,
+ CancellationToken cancellationToken = default)
+ {
+ List pagedItems = [];
+ var count = 0;
+
+ try
+ {
+ var requestInformation = Client
+ .Users[userId]
+ .MailFolders
+ .ToGetRequestInformation(
+ RequestConfigurationFactory.CreateForMailFolders(
+ expandQueryParameters,
+ filterQueryParameter,
+ selectQueryParameters));
+
+ var response = await Client.RequestAdapter.SendAsync(
+ requestInformation,
+ (_) => new MailFolderCollectionResponse(),
+ cancellationToken: cancellationToken);
+
+ if (response is null)
+ {
+ return (HttpStatusCode.InternalServerError, pagedItems);
+ }
+
+ var pageIterator = PageIterator.CreatePageIterator(
+ Client,
+ response,
+ item =>
+ {
+ pagedItems.Add(item);
+
+ count++;
+ if (count % 1000 == 0)
+ {
+ LogPageIteratorCount(nameof(MailFolder), count);
+ }
+
+ return true;
+ });
+
+ try
+ {
+ await pageIterator.IterateAsync(cancellationToken);
+ }
+ catch (ODataError odataError) when (odataError.ResponseStatusCode == (int)HttpStatusCode.TooManyRequests)
+ {
+ await Task.Delay(MicrosoftGraphConstants.RetryWaitDelayInMs, cancellationToken);
+
+ await pageIterator.IterateAsync(cancellationToken);
+ }
+ catch (ODataError odataError) when (odataError.ResponseStatusCode == (int)HttpStatusCode.Gone)
+ {
+ return (HttpStatusCode.Gone, pagedItems);
+ }
+
+ LogPageIteratorTotalCount(nameof(MailFolder), count);
+
+ return (HttpStatusCode.OK, pagedItems);
+ }
+ catch (ODataError odataError)
+ {
+ LogGetFailure(odataError.Error?.Message);
+ return (HttpStatusCode.InternalServerError, pagedItems);
+ }
+ catch (Exception ex)
+ {
+ LogGetFailure(ex.GetLastInnerMessage());
+ return (HttpStatusCode.InternalServerError, pagedItems);
+ }
+ }
+
+ public async Task<(HttpStatusCode StatusCode, IList Data)> GetMailFoldersByUserIdAndFolderId(
+ string userId,
+ string folderId,
+ List? expandQueryParameters,
+ string? filterQueryParameter,
+ List? selectQueryParameters,
+ CancellationToken cancellationToken = default)
+ {
+ List pagedItems = [];
+ var count = 0;
+
+ try
+ {
+ var requestInformation = Client
+ .Users[userId]
+ .MailFolders[folderId]
+ .ChildFolders
+ .ToGetRequestInformation(
+ RequestConfigurationFactory.CreateForChildFolders(
+ expandQueryParameters,
+ filterQueryParameter,
+ selectQueryParameters));
+
+ var response = await Client.RequestAdapter.SendAsync(
+ requestInformation,
+ (_) => new MailFolderCollectionResponse(),
+ cancellationToken: cancellationToken);
+
+ if (response is null)
+ {
+ return (HttpStatusCode.InternalServerError, pagedItems);
+ }
+
+ var pageIterator = PageIterator.CreatePageIterator(
+ Client,
+ response,
+ item =>
+ {
+ pagedItems.Add(item);
+
+ count++;
+ if (count % 1000 == 0)
+ {
+ LogPageIteratorCount(nameof(MailFolder), count);
+ }
+
+ return true;
+ });
+
+ try
+ {
+ await pageIterator.IterateAsync(cancellationToken);
+ }
+ catch (ODataError odataError) when (odataError.ResponseStatusCode == (int)HttpStatusCode.TooManyRequests)
+ {
+ await Task.Delay(MicrosoftGraphConstants.RetryWaitDelayInMs, cancellationToken);
+
+ await pageIterator.IterateAsync(cancellationToken);
+ }
+ catch (ODataError odataError) when (odataError.ResponseStatusCode == (int)HttpStatusCode.Gone)
+ {
+ return (HttpStatusCode.Gone, pagedItems);
+ }
+
+ LogPageIteratorTotalCount(nameof(MailFolder), count);
+
+ return (HttpStatusCode.OK, pagedItems);
+ }
+ catch (ODataError odataError)
+ {
+ LogGetFailure(odataError.Error?.Message);
+ return (HttpStatusCode.InternalServerError, pagedItems);
+ }
+ catch (Exception ex)
+ {
+ LogGetFailure(ex.GetLastInnerMessage());
+ return (HttpStatusCode.InternalServerError, pagedItems);
+ }
+ }
+
+ public async Task<(HttpStatusCode StatusCode, IList Data)> GetMessagesByUserId(
+ string userId,
+ List? expandQueryParameters,
+ string? filterQueryParameter,
+ List? selectQueryParameters,
+ CancellationToken cancellationToken = default)
+ {
+ List pagedItems = [];
+ var count = 0;
+
+ try
+ {
+ var requestInformation = Client
+ .Users[userId]
+ .Messages
+ .ToGetRequestInformation(
+ RequestConfigurationFactory.CreateForMessagesUserId(
+ expandQueryParameters,
+ filterQueryParameter,
+ selectQueryParameters));
+
+ var response = await Client.RequestAdapter.SendAsync(
+ requestInformation,
+ (_) => new MessageCollectionResponse(),
+ cancellationToken: cancellationToken);
+
+ if (response is null)
+ {
+ return (HttpStatusCode.InternalServerError, pagedItems);
+ }
+
+ var pageIterator = PageIterator.CreatePageIterator(
+ Client,
+ response,
+ item =>
+ {
+ pagedItems.Add(item);
+
+ count++;
+ if (count % 1000 == 0)
+ {
+ LogPageIteratorCount(nameof(Message), count);
+ }
+
+ return true;
+ });
+
+ try
+ {
+ await pageIterator.IterateAsync(cancellationToken);
+ }
+ catch (ODataError odataError) when (odataError.ResponseStatusCode == (int)HttpStatusCode.TooManyRequests)
+ {
+ await Task.Delay(MicrosoftGraphConstants.RetryWaitDelayInMs, cancellationToken);
+
+ await pageIterator.IterateAsync(cancellationToken);
+ }
+ catch (ODataError odataError) when (odataError.ResponseStatusCode == (int)HttpStatusCode.Gone)
+ {
+ return (HttpStatusCode.Gone, pagedItems);
+ }
+
+ LogPageIteratorTotalCount(nameof(Message), count);
+
+ return (HttpStatusCode.OK, pagedItems);
+ }
+ catch (ODataError odataError)
+ {
+ LogGetFailure(odataError.Error?.Message);
+ return (HttpStatusCode.InternalServerError, pagedItems);
+ }
+ catch (Exception ex)
+ {
+ LogGetFailure(ex.GetLastInnerMessage());
+ return (HttpStatusCode.InternalServerError, pagedItems);
+ }
+ }
+
+ public async Task<(HttpStatusCode StatusCode, IList Data, string? DeltaToken)> GetMessagesByUserIdAndFolderId(
+ string userId,
+ string folderId,
+ List? expandQueryParameters,
+ string? filterQueryParameter,
+ List? selectQueryParameters,
+ string? deltaToken = null,
+ CancellationToken cancellationToken = default)
+ {
+ List pagedItems = [];
+
+ try
+ {
+ return string.IsNullOrEmpty(deltaToken)
+ ? await GetMessagesByUserIdAndFolderIdWithoutDeltaToken(
+ userId,
+ folderId,
+ filterQueryParameter,
+ selectQueryParameters,
+ cancellationToken)
+ : await GetMessagesByUserIdAndFolderIdWithDeltaToken(
+ userId,
+ folderId,
+ deltaToken,
+ expandQueryParameters,
+ filterQueryParameter,
+ selectQueryParameters,
+ cancellationToken);
+ }
+ catch (ODataError odataError)
+ {
+ LogGetFailure(odataError.Error?.Message);
+ return (HttpStatusCode.InternalServerError, pagedItems, null);
+ }
+ catch (Exception ex)
+ {
+ LogGetFailure(ex.GetLastInnerMessage());
+ return (HttpStatusCode.InternalServerError, pagedItems, null);
+ }
+ }
+
+ public async Task<(HttpStatusCode StatusCode, IList Data)> GetFileAttachmentsByUserIdAndMessageId(
+ string userId,
+ string messageId,
+ List? expandQueryParameters,
+ string? filterQueryParameter,
+ List? selectQueryParameters,
+ CancellationToken cancellationToken = default)
+ {
+ List pagedItems = [];
+ var count = 0;
+
+ try
+ {
+ var requestInformation = Client
+ .Users[userId]
+ .Messages[messageId]
+ .Attachments
+ .ToGetRequestInformation(
+ RequestConfigurationFactory.CreateForAttachments(
+ expandQueryParameters,
+ filterQueryParameter,
+ selectQueryParameters));
+
+ var response = await Client.RequestAdapter.SendAsync(
+ requestInformation,
+ (_) => new AttachmentCollectionResponse(),
+ cancellationToken: cancellationToken);
+
+ if (response is null)
+ {
+ return (HttpStatusCode.InternalServerError, pagedItems);
+ }
+
+ var pageIterator = PageIterator.CreatePageIterator(
+ Client,
+ response,
+ item =>
+ {
+ if (item is FileAttachment attachment)
+ {
+ pagedItems.Add(attachment);
+ }
+
+ count++;
+ if (count % 1000 == 0)
+ {
+ LogPageIteratorCount(nameof(FileAttachment), count);
+ }
+
+ return true;
+ });
+
+ try
+ {
+ await pageIterator.IterateAsync(cancellationToken);
+ }
+ catch (ODataError odataError) when (odataError.ResponseStatusCode == (int)HttpStatusCode.TooManyRequests)
+ {
+ await Task.Delay(MicrosoftGraphConstants.RetryWaitDelayInMs, cancellationToken);
+
+ await pageIterator.IterateAsync(cancellationToken);
+ }
+ catch (ODataError odataError) when (odataError.ResponseStatusCode == (int)HttpStatusCode.Gone)
+ {
+ return (HttpStatusCode.Gone, pagedItems);
+ }
+
+ LogPageIteratorTotalCount(nameof(FileAttachment), count);
+
+ return (HttpStatusCode.OK, pagedItems);
+ }
+ catch (ODataError odataError)
+ {
+ LogGetFailure(odataError.Error?.Message);
+ return (HttpStatusCode.InternalServerError, pagedItems);
+ }
+ catch (Exception ex)
+ {
+ LogGetFailure(ex.GetLastInnerMessage());
+ return (HttpStatusCode.InternalServerError, pagedItems);
+ }
+ }
+
+ private async Task<(HttpStatusCode StatusCode, IList Data, string? DeltaToken)> GetMessagesByUserIdAndFolderIdWithoutDeltaToken(
+ string userId,
+ string folderId,
+ string? filterQueryParameter,
+ List? selectQueryParameters,
+ CancellationToken cancellationToken = default)
+ {
+ List pagedItems = [];
+ var count = 0;
+
+ var requestInformation = Client
+ .Users[userId]
+ .MailFolders[folderId]
+ .Messages
+ .Delta // To get a delta Link included in the response
+ .ToGetRequestInformation(
+ RequestConfigurationFactory.CreateForMessagesDelta(
+ filterQueryParameter,
+ selectQueryParameters));
+
+ var response = await Client.RequestAdapter.SendAsync(
+ requestInformation,
+ (_) => new MessageCollectionResponse(),
+ cancellationToken: cancellationToken);
+
+ if (response is null)
+ {
+ return (HttpStatusCode.InternalServerError, pagedItems, null);
+ }
+
+ var pageIterator = PageIterator.CreatePageIterator(
+ Client,
+ response,
+ item =>
+ {
+ pagedItems.Add(item);
+
+ count++;
+ if (count % 1000 == 0)
+ {
+ LogPageIteratorCount(nameof(Message), count);
+ }
+
+ return true;
+ });
+
+ try
+ {
+ await pageIterator.IterateAsync(cancellationToken);
+ }
+ catch (ODataError odataError) when (odataError.ResponseStatusCode == (int)HttpStatusCode.TooManyRequests)
+ {
+ await Task.Delay(MicrosoftGraphConstants.RetryWaitDelayInMs, cancellationToken);
+
+ await pageIterator.IterateAsync(cancellationToken);
+ }
+ catch (ODataError odataError) when (odataError.ResponseStatusCode == (int)HttpStatusCode.Gone)
+ {
+ return (HttpStatusCode.Gone, pagedItems, null);
+ }
+ catch (Exception ex)
+ {
+ LogGetFailure(ex.GetLastInnerMessage());
+ return (HttpStatusCode.InternalServerError, pagedItems, null);
+ }
+
+ LogPageIteratorTotalCount(nameof(Message), count);
+
+ if (!response.AdditionalData.TryGetValue("@odata.deltaLink", out var deltaLink) ||
+ deltaLink is null)
+ {
+ return (HttpStatusCode.InternalServerError, pagedItems, null);
+ }
+
+ var sa = deltaLink.ToString()!.Split("deltatoken=", StringSplitOptions.RemoveEmptyEntries);
+ return sa.Length == 2
+ ? (HttpStatusCode.OK, pagedItems, sa[1])
+ : (HttpStatusCode.InternalServerError, pagedItems, null);
+ }
+
+ private async Task<(HttpStatusCode StatusCode, IList Data, string? DeltaToken)> GetMessagesByUserIdAndFolderIdWithDeltaToken(
+ string userId,
+ string folderId,
+ string deltaToken,
+ List? expandQueryParameters,
+ string? filterQueryParameter,
+ List? selectQueryParameters,
+ CancellationToken cancellationToken = default)
+ {
+ List pagedItems = [];
+ var count = 0;
+
+ var url = Client
+ .Users[userId]
+ .MailFolders[folderId]
+ .Messages
+ .ToGetRequestInformation(
+ RequestConfigurationFactory.CreateForMessagesMailFolder(
+ expandQueryParameters,
+ filterQueryParameter,
+ selectQueryParameters))
+ .URI
+ .ToString();
+
+ var saUrl = url.Split('?', StringSplitOptions.RemoveEmptyEntries);
+ url = saUrl.Length == 1
+ ? $"{url}/delta?$skipToken={deltaToken}"
+ : $"{saUrl[0]}/delta?$skipToken={deltaToken}&{saUrl[1]}";
+
+ var deltaRequestBuilder = new global::Microsoft.Graph.Users.Item.MailFolders.Item.Messages.Delta.DeltaRequestBuilder(
+ url,
+ Client.RequestAdapter);
+
+ var response = await deltaRequestBuilder.GetAsDeltaGetResponseAsync(cancellationToken: cancellationToken);
+ if (response is null)
+ {
+ return (HttpStatusCode.InternalServerError, pagedItems, null);
+ }
+
+ var pageIterator = PageIterator.CreatePageIterator(
+ Client,
+ response,
+ item =>
+ {
+ pagedItems.Add(item);
+
+ count++;
+ if (count % 1000 == 0)
+ {
+ LogPageIteratorCount(nameof(Message), count);
+ }
+
+ return true;
+ });
+
+ try
+ {
+ await pageIterator.IterateAsync(cancellationToken);
+ }
+ catch (ODataError odataError) when (odataError.ResponseStatusCode == (int)HttpStatusCode.TooManyRequests)
+ {
+ await Task.Delay(MicrosoftGraphConstants.RetryWaitDelayInMs, cancellationToken);
+
+ await pageIterator.IterateAsync(cancellationToken);
+ }
+ catch (ODataError odataError) when (odataError.ResponseStatusCode == (int)HttpStatusCode.Gone)
+ {
+ return (HttpStatusCode.Gone, pagedItems, null);
+ }
+ catch (Exception ex)
+ {
+ LogGetFailure(ex.GetLastInnerMessage());
+ return (HttpStatusCode.InternalServerError, pagedItems, null);
+ }
+
+ LogPageIteratorTotalCount(nameof(Message), count);
+
+ if (string.IsNullOrEmpty(response.OdataDeltaLink))
+ {
+ return (HttpStatusCode.InternalServerError, pagedItems, null);
+ }
+
+ var sa = response.OdataDeltaLink.Split("deltatoken=", StringSplitOptions.RemoveEmptyEntries);
+ return sa.Length == 2
+ ? (HttpStatusCode.OK, pagedItems, sa[1])
+ : (HttpStatusCode.InternalServerError, pagedItems, null);
+ }
+}
\ No newline at end of file
diff --git a/src/Atc.Microsoft.Graph.Client/Services/Sharepoint/ISharepointGraphService.cs b/src/Atc.Microsoft.Graph.Client/Services/Sharepoint/ISharepointGraphService.cs
new file mode 100644
index 0000000..791706a
--- /dev/null
+++ b/src/Atc.Microsoft.Graph.Client/Services/Sharepoint/ISharepointGraphService.cs
@@ -0,0 +1,23 @@
+namespace Atc.Microsoft.Graph.Client.Services.Sharepoint;
+
+public interface ISharepointGraphService
+{
+ Task<(HttpStatusCode StatusCode, IList Data)> GetSites(
+ List? expandQueryParameters,
+ string? filterQueryParameter,
+ List? selectQueryParameters,
+ CancellationToken cancellationToken = default);
+
+ Task<(HttpStatusCode StatusCode, Guid? SubscriptionId)> SetupSubscription(
+ Subscription subscription,
+ CancellationToken cancellationToken = default);
+
+ Task<(HttpStatusCode StatusCode, bool Succeeded)> RenewSubscription(
+ Guid subscriptionId,
+ DateTimeOffset expirationDate,
+ CancellationToken cancellationToken = default);
+
+ Task<(HttpStatusCode StatusCode, bool Succeeded)> DeleteSubscription(
+ Guid subscriptionId,
+ CancellationToken cancellationToken = default);
+}
\ No newline at end of file
diff --git a/src/Atc.Microsoft.Graph.Client/Services/Sharepoint/SharepointGraphService.cs b/src/Atc.Microsoft.Graph.Client/Services/Sharepoint/SharepointGraphService.cs
new file mode 100644
index 0000000..d5b9823
--- /dev/null
+++ b/src/Atc.Microsoft.Graph.Client/Services/Sharepoint/SharepointGraphService.cs
@@ -0,0 +1,192 @@
+namespace Atc.Microsoft.Graph.Client.Services.Sharepoint;
+
+public sealed class SharepointGraphService : GraphServiceClientWrapper, ISharepointGraphService
+{
+ public SharepointGraphService(
+ ILoggerFactory loggerFactory,
+ GraphServiceClient client)
+ : base(loggerFactory, client)
+ {
+ }
+
+ public async Task<(HttpStatusCode StatusCode, IList Data)> GetSites(
+ List? expandQueryParameters,
+ string? filterQueryParameter,
+ List? selectQueryParameters,
+ CancellationToken cancellationToken = default)
+ {
+ List pagedItems = [];
+ var count = 0;
+
+ try
+ {
+ var requestInformation = Client
+ .Sites
+ .ToGetRequestInformation(
+ RequestConfigurationFactory.CreateForSites(
+ expandQueryParameters,
+ filterQueryParameter,
+ selectQueryParameters));
+
+ var response = await Client.RequestAdapter.SendAsync(
+ requestInformation,
+ (_) => new SiteCollectionResponse(),
+ cancellationToken: cancellationToken);
+
+ if (response is null)
+ {
+ return (HttpStatusCode.InternalServerError, pagedItems);
+ }
+
+ var pageIterator = PageIterator.CreatePageIterator(
+ Client,
+ response,
+ item =>
+ {
+ pagedItems.Add(item);
+
+ count++;
+ if (count % 1000 == 0)
+ {
+ LogPageIteratorCount(nameof(Site), count);
+ }
+
+ return true;
+ });
+
+ try
+ {
+ await pageIterator.IterateAsync(cancellationToken);
+ }
+ catch (ODataError odataError) when (odataError.ResponseStatusCode == (int)HttpStatusCode.TooManyRequests)
+ {
+ await Task.Delay(MicrosoftGraphConstants.RetryWaitDelayInMs, cancellationToken);
+
+ await pageIterator.IterateAsync(cancellationToken);
+ }
+ catch (ODataError odataError) when (odataError.ResponseStatusCode == (int)HttpStatusCode.Gone)
+ {
+ return (HttpStatusCode.Gone, pagedItems);
+ }
+
+ LogPageIteratorTotalCount(nameof(Site), count);
+
+ return (HttpStatusCode.OK, pagedItems);
+ }
+ catch (ODataError odataError)
+ {
+ LogGetFailure(odataError.Error?.Message);
+ return (HttpStatusCode.InternalServerError, pagedItems);
+ }
+ catch (Exception ex)
+ {
+ LogGetFailure(ex.GetLastInnerMessage());
+ return (HttpStatusCode.InternalServerError, pagedItems);
+ }
+ }
+
+ public async Task<(HttpStatusCode StatusCode, Guid? SubscriptionId)> SetupSubscription(
+ Subscription subscription,
+ CancellationToken cancellationToken = default)
+ {
+ ArgumentNullException.ThrowIfNull(subscription);
+
+ try
+ {
+ Guid? subscriptionId = null;
+
+ await DownloadResiliencePipeline.ExecuteAsync(
+ async context =>
+ {
+ var graphSubscription = await Client.Subscriptions
+ .PostAsync(subscription, cancellationToken: context);
+
+ subscriptionId = graphSubscription?.Id is not null
+ ? Guid.Parse(graphSubscription.Id)
+ : null;
+
+ if (subscriptionId is null)
+ {
+ LogSubscriptionSetupFailed(subscription.Resource, "Subscription ID is null");
+ }
+
+ return subscriptionId;
+ },
+ cancellationToken);
+
+ return (HttpStatusCode.OK, subscriptionId);
+ }
+ catch (ODataError odataError)
+ {
+ if (odataError.Error?.Message?.Contains("timed out", StringComparison.OrdinalIgnoreCase) == true)
+ {
+ return (HttpStatusCode.RequestTimeout, null);
+ }
+
+ LogSubscriptionSetupFailed(subscription.Resource, odataError.Error?.Message);
+ return (HttpStatusCode.InternalServerError, null);
+ }
+ catch (Exception ex)
+ {
+ LogSubscriptionSetupFailed(subscription.Resource, ex.GetLastInnerMessage());
+ return (HttpStatusCode.InternalServerError, null);
+ }
+ }
+
+ public async Task<(HttpStatusCode StatusCode, bool Succeeded)> RenewSubscription(
+ Guid subscriptionId,
+ DateTimeOffset expirationDate,
+ CancellationToken cancellationToken = default)
+ {
+ try
+ {
+ var newSubscription = new Subscription
+ {
+ ExpirationDateTime = expirationDate,
+ };
+
+ await Client
+ .Subscriptions[subscriptionId.ToString()]
+ .PatchAsync(newSubscription, cancellationToken: cancellationToken);
+
+ return (HttpStatusCode.OK, true);
+ }
+ catch (ODataError odataError)
+ {
+ LogSubscriptionRenewalFailed(subscriptionId, expirationDate, odataError.Error?.Message);
+ return (HttpStatusCode.InternalServerError, false);
+ }
+ catch (Exception ex)
+ {
+ LogSubscriptionRenewalFailed(subscriptionId, expirationDate, ex.GetLastInnerMessage());
+ return (HttpStatusCode.InternalServerError, false);
+ }
+ }
+
+ public async Task<(HttpStatusCode StatusCode, bool Succeeded)> DeleteSubscription(
+ Guid subscriptionId,
+ CancellationToken cancellationToken = default)
+ {
+ try
+ {
+ await Client.Subscriptions[subscriptionId.ToString()]
+ .DeleteAsync(cancellationToken: cancellationToken);
+
+ return (HttpStatusCode.OK, true);
+ }
+ catch (ODataError odataError) when (odataError.ResponseStatusCode == (int)HttpStatusCode.NotFound)
+ {
+ return (HttpStatusCode.OK, true);
+ }
+ catch (ODataError odataError)
+ {
+ LogSubscriptionDeletionFailed(subscriptionId, odataError.Error?.Message);
+ return (HttpStatusCode.InternalServerError, false);
+ }
+ catch (Exception ex)
+ {
+ LogSubscriptionDeletionFailed(subscriptionId, ex.GetLastInnerMessage());
+ return (HttpStatusCode.InternalServerError, false);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Atc.Microsoft.Graph.Client/Services/Teams/ITeamsGraphService.cs b/src/Atc.Microsoft.Graph.Client/Services/Teams/ITeamsGraphService.cs
new file mode 100644
index 0000000..3aa4bc0
--- /dev/null
+++ b/src/Atc.Microsoft.Graph.Client/Services/Teams/ITeamsGraphService.cs
@@ -0,0 +1,10 @@
+namespace Atc.Microsoft.Graph.Client.Services.Teams;
+
+public interface ITeamsGraphService
+{
+ Task<(HttpStatusCode StatusCode, IList Data)> GetTeams(
+ List? expandQueryParameters,
+ string? filterQueryParameter,
+ List? selectQueryParameters,
+ CancellationToken cancellationToken = default);
+}
\ No newline at end of file
diff --git a/src/Atc.Microsoft.Graph.Client/Services/Teams/TeamsGraphService.cs b/src/Atc.Microsoft.Graph.Client/Services/Teams/TeamsGraphService.cs
new file mode 100644
index 0000000..5caf889
--- /dev/null
+++ b/src/Atc.Microsoft.Graph.Client/Services/Teams/TeamsGraphService.cs
@@ -0,0 +1,87 @@
+namespace Atc.Microsoft.Graph.Client.Services.Teams;
+
+public sealed class TeamsGraphService : GraphServiceClientWrapper, ITeamsGraphService
+{
+ public TeamsGraphService(
+ ILoggerFactory loggerFactory,
+ GraphServiceClient client)
+ : base(loggerFactory, client)
+ {
+ }
+
+ public async Task<(HttpStatusCode StatusCode, IList Data)> GetTeams(
+ List? expandQueryParameters,
+ string? filterQueryParameter,
+ List? selectQueryParameters,
+ CancellationToken cancellationToken = default)
+ {
+ List pagedItems = [];
+ var count = 0;
+
+ try
+ {
+ var requestInformation = Client
+ .Teams
+ .ToGetRequestInformation(
+ RequestConfigurationFactory.CreateForTeams(
+ expandQueryParameters,
+ filterQueryParameter,
+ selectQueryParameters));
+
+ var response = await Client.RequestAdapter.SendAsync(
+ requestInformation,
+ (_) => new TeamCollectionResponse(),
+ cancellationToken: cancellationToken);
+
+ if (response is null)
+ {
+ return (HttpStatusCode.InternalServerError, pagedItems);
+ }
+
+ var pageIterator = PageIterator.CreatePageIterator(
+ Client,
+ response,
+ item =>
+ {
+ pagedItems.Add(item);
+
+ count++;
+ if (count % 1000 == 0)
+ {
+ LogPageIteratorCount(nameof(Team), count);
+ }
+
+ return true;
+ });
+
+ try
+ {
+ await pageIterator.IterateAsync(cancellationToken);
+ }
+ catch (ODataError odataError) when (odataError.ResponseStatusCode == (int)HttpStatusCode.TooManyRequests)
+ {
+ await Task.Delay(MicrosoftGraphConstants.RetryWaitDelayInMs, cancellationToken);
+
+ await pageIterator.IterateAsync(cancellationToken);
+ }
+ catch (ODataError odataError) when (odataError.ResponseStatusCode == (int)HttpStatusCode.Gone)
+ {
+ return (HttpStatusCode.Gone, pagedItems);
+ }
+
+ LogPageIteratorTotalCount(nameof(Team), count);
+
+ return (HttpStatusCode.OK, pagedItems);
+ }
+ catch (ODataError odataError)
+ {
+ LogGetFailure(odataError.Error?.Message);
+ return (HttpStatusCode.InternalServerError, pagedItems);
+ }
+ catch (Exception ex)
+ {
+ LogGetFailure(ex.GetLastInnerMessage());
+ return (HttpStatusCode.InternalServerError, pagedItems);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Atc.Microsoft.Graph.Client/Services/Users/IUsersGraphService.cs b/src/Atc.Microsoft.Graph.Client/Services/Users/IUsersGraphService.cs
new file mode 100644
index 0000000..9bc96fa
--- /dev/null
+++ b/src/Atc.Microsoft.Graph.Client/Services/Users/IUsersGraphService.cs
@@ -0,0 +1,10 @@
+namespace Atc.Microsoft.Graph.Client.Services.Users;
+
+public interface IUsersGraphService
+{
+ Task<(HttpStatusCode StatusCode, IList Data)> GetUsers(
+ List? expandQueryParameters,
+ string? filterQueryParameter,
+ List? selectQueryParameters,
+ CancellationToken cancellationToken = default);
+}
\ No newline at end of file
diff --git a/src/Atc.Microsoft.Graph.Client/Services/Users/UsersGraphService.cs b/src/Atc.Microsoft.Graph.Client/Services/Users/UsersGraphService.cs
new file mode 100644
index 0000000..2516220
--- /dev/null
+++ b/src/Atc.Microsoft.Graph.Client/Services/Users/UsersGraphService.cs
@@ -0,0 +1,87 @@
+namespace Atc.Microsoft.Graph.Client.Services.Users;
+
+public sealed class UsersGraphService : GraphServiceClientWrapper, IUsersGraphService
+{
+ public UsersGraphService(
+ ILoggerFactory loggerFactory,
+ GraphServiceClient client)
+ : base(loggerFactory, client)
+ {
+ }
+
+ public async Task<(HttpStatusCode StatusCode, IList Data)> GetUsers(
+ List? expandQueryParameters,
+ string? filterQueryParameter,
+ List? selectQueryParameters,
+ CancellationToken cancellationToken = default)
+ {
+ List pagedItems = [];
+ var count = 0;
+
+ try
+ {
+ var requestInformation = Client
+ .Users
+ .ToGetRequestInformation(
+ RequestConfigurationFactory.CreateForUsers(
+ expandQueryParameters,
+ filterQueryParameter,
+ selectQueryParameters));
+
+ var response = await Client.RequestAdapter.SendAsync(
+ requestInformation,
+ (_) => new UserCollectionResponse(),
+ cancellationToken: cancellationToken);
+
+ if (response is null)
+ {
+ return (HttpStatusCode.InternalServerError, pagedItems);
+ }
+
+ var pageIterator = PageIterator.CreatePageIterator(
+ Client,
+ response,
+ item =>
+ {
+ pagedItems.Add(item);
+
+ count++;
+ if (count % 1000 == 0)
+ {
+ LogPageIteratorCount(nameof(User), count);
+ }
+
+ return true;
+ });
+
+ try
+ {
+ await pageIterator.IterateAsync(cancellationToken);
+ }
+ catch (ODataError odataError) when (odataError.ResponseStatusCode == (int)HttpStatusCode.TooManyRequests)
+ {
+ await Task.Delay(MicrosoftGraphConstants.RetryWaitDelayInMs, cancellationToken);
+
+ await pageIterator.IterateAsync(cancellationToken);
+ }
+ catch (ODataError odataError) when (odataError.ResponseStatusCode == (int)HttpStatusCode.Gone)
+ {
+ return (HttpStatusCode.Gone, pagedItems);
+ }
+
+ LogPageIteratorTotalCount(nameof(User), count);
+
+ return (HttpStatusCode.OK, pagedItems);
+ }
+ catch (ODataError odataError)
+ {
+ LogGetFailure(odataError.Error?.Message);
+ return (HttpStatusCode.InternalServerError, pagedItems);
+ }
+ catch (Exception ex)
+ {
+ LogGetFailure(ex.GetLastInnerMessage());
+ return (HttpStatusCode.InternalServerError, pagedItems);
+ }
+ }
+}
\ No newline at end of file