diff --git a/Autodesk.Forge.sln b/Autodesk.Forge.sln index 169019f..e0f5463 100644 --- a/Autodesk.Forge.sln +++ b/Autodesk.Forge.sln @@ -9,6 +9,22 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Autodesk.Forge.Core.Test", EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Autodesk.Forge.Core.E2eTestHelpers", "src\Autodesk.Forge.Core.E2eTestHelpers\Autodesk.Forge.Core.E2eTestHelpers.csproj", "{66694EE2-D632-476D-B533-589AC9B975F3}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution", "Solution", "{BC49E389-CECC-489D-ACE8-BB97869391B9}" + ProjectSection(SolutionItems) = preProject + CHANGELOG.md = CHANGELOG.md + Directory.Build.props = Directory.Build.props + Directory.Build.targets = Directory.Build.targets + README.md = README.md + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{3E0D0E6A-0664-491F-93E8-0290B21C3D09}" + ProjectSection(SolutionItems) = preProject + src\Directory.Build.props = src\Directory.Build.props + src\Directory.Build.targets = src\Directory.Build.targets + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{6E857E79-2F5E-4B70-996D-A42A445F67B4}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -31,6 +47,11 @@ Global GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {E59655EF-C1BF-4318-B344-B2C6B67E1A74} = {3E0D0E6A-0664-491F-93E8-0290B21C3D09} + {B52C434A-3AA1-4CA7-9F88-39AA2C208A67} = {6E857E79-2F5E-4B70-996D-A42A445F67B4} + {66694EE2-D632-476D-B533-589AC9B975F3} = {3E0D0E6A-0664-491F-93E8-0290B21C3D09} + EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {D05CFF55-5724-433F-9941-337DC508F5FE} EndGlobalSection diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c4566d..fad5358 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +### 4.0.1 + +* Add Documentation in the `Autodesk.Forge.Core` project. + ### 4.0.0.0 * Migrate to .Net 8 diff --git a/Directory.Build.props b/Directory.Build.props index b6c4a44..11ddede 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,5 +1,6 @@ + 4.0.1 net8.0 enable diff --git a/src/Autodesk.Forge.Core.E2eTestHelpers/Autodesk.Forge.Core.E2eTestHelpers.csproj b/src/Autodesk.Forge.Core.E2eTestHelpers/Autodesk.Forge.Core.E2eTestHelpers.csproj index ad01318..3451b46 100644 --- a/src/Autodesk.Forge.Core.E2eTestHelpers/Autodesk.Forge.Core.E2eTestHelpers.csproj +++ b/src/Autodesk.Forge.Core.E2eTestHelpers/Autodesk.Forge.Core.E2eTestHelpers.csproj @@ -2,7 +2,6 @@ Shared code for Forge client sdks e2e tests - 4.0.0 false NU5100 diff --git a/src/Autodesk.Forge.Core/ApiResponse.cs b/src/Autodesk.Forge.Core/ApiResponse.cs index ed152b3..6ba4c67 100644 --- a/src/Autodesk.Forge.Core/ApiResponse.cs +++ b/src/Autodesk.Forge.Core/ApiResponse.cs @@ -23,6 +23,10 @@ namespace Autodesk.Forge.Core /// public class ApiResponse : IDisposable { + /// + /// Gets or sets the HTTP response message. + /// + /// The HTTP response message. public HttpResponseMessage HttpResponse { get; private set; } /// @@ -34,6 +38,9 @@ public ApiResponse(HttpResponseMessage response) this.HttpResponse = response; } + /// + /// Disposes the API response. + /// public void Dispose() { HttpResponse?.Dispose(); @@ -55,7 +62,7 @@ public class ApiResponse : ApiResponse /// Initializes a new instance of the class. /// /// Http response message. - /// content (parsed HTTP body) + /// content (parsed HTTP body) public ApiResponse(HttpResponseMessage response, T content) : base(response) { diff --git a/src/Autodesk.Forge.Core/Autodesk.Forge.Core.csproj b/src/Autodesk.Forge.Core/Autodesk.Forge.Core.csproj index e893aa4..d515086 100644 --- a/src/Autodesk.Forge.Core/Autodesk.Forge.Core.csproj +++ b/src/Autodesk.Forge.Core/Autodesk.Forge.Core.csproj @@ -2,7 +2,7 @@ Shared code for APS client sdks - 4.0.0 + true diff --git a/src/Autodesk.Forge.Core/ForgeAgentConfiguration.cs b/src/Autodesk.Forge.Core/ForgeAgentConfiguration.cs index 203d75d..673f57b 100644 --- a/src/Autodesk.Forge.Core/ForgeAgentConfiguration.cs +++ b/src/Autodesk.Forge.Core/ForgeAgentConfiguration.cs @@ -18,9 +18,19 @@ namespace Autodesk.Forge.Core { + /// + /// Represents the configuration for the Forge Agent. + /// public class ForgeAgentConfiguration { + /// + /// Gets or sets the client ID. + /// public string ClientId { get; init; } + + /// + /// Gets or sets the client secret. + /// public string ClientSecret { get; init; } } } diff --git a/src/Autodesk.Forge.Core/ForgeAgentHandler.cs b/src/Autodesk.Forge.Core/ForgeAgentHandler.cs index 14749be..521ac12 100644 --- a/src/Autodesk.Forge.Core/ForgeAgentHandler.cs +++ b/src/Autodesk.Forge.Core/ForgeAgentHandler.cs @@ -18,15 +18,33 @@ namespace Autodesk.Forge.Core { + /// + /// Represents a handler for Forge agents. + /// public class ForgeAgentHandler : DelegatingHandler { + /// + /// The default agent name. + /// public const string defaultAgentName = "default"; - + private string user; + + /// + /// Initializes a new instance of the class. + /// + /// The user associated with the agent. public ForgeAgentHandler(string user) { this.user = user; } + + /// + /// Sends an HTTP request asynchronously. + /// + /// The HTTP request message. + /// The cancellation token. + /// The task representing the asynchronous operation. protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { request.Options.TryAdd(ForgeConfiguration.AgentKey.Key, user); diff --git a/src/Autodesk.Forge.Core/ForgeConfiguration.cs b/src/Autodesk.Forge.Core/ForgeConfiguration.cs index 6a9db95..30852e7 100644 --- a/src/Autodesk.Forge.Core/ForgeConfiguration.cs +++ b/src/Autodesk.Forge.Core/ForgeConfiguration.cs @@ -18,19 +18,50 @@ namespace Autodesk.Forge.Core { + /// + /// Represents the configuration settings for the Forge SDK. + /// public class ForgeConfiguration { + /// + /// Represents the key for the Forge agent in the HTTP request options. + /// public static readonly HttpRequestOptionsKey AgentKey = new HttpRequestOptionsKey("Autodesk.Forge.Agent"); + /// + /// Represents the key for the Forge scope in the HTTP request options. + /// public static readonly HttpRequestOptionsKey ScopeKey = new HttpRequestOptionsKey("Autodesk.Forge.Scope"); + /// + /// Represents the key for the Forge timeout in the HTTP request options. + /// public static readonly HttpRequestOptionsKey TimeoutKey = new HttpRequestOptionsKey("Autodesk.Forge.Timeout"); + /// + /// Initializes a new instance of the class. + /// public ForgeConfiguration() { this.AuthenticationAddress = new Uri("https://developer.api.autodesk.com/authentication/v2/token"); } + + /// + /// Gets or sets the client ID. + /// public string ClientId { get; set; } + + /// + /// Gets or sets the client secret. + /// public string ClientSecret { get; set; } + + /// + /// Gets or sets the dictionary of Forge agent configurations. + /// public IDictionary Agents { get; set; } + + /// + /// Gets or sets the authentication address. + /// public Uri AuthenticationAddress { get; set; } } } diff --git a/src/Autodesk.Forge.Core/ForgeHandler.cs b/src/Autodesk.Forge.Core/ForgeHandler.cs index 55d7d66..822f234 100644 --- a/src/Autodesk.Forge.Core/ForgeHandler.cs +++ b/src/Autodesk.Forge.Core/ForgeHandler.cs @@ -23,33 +23,61 @@ namespace Autodesk.Forge.Core { + /// + /// Represents a handler for Forge API requests. + /// public class ForgeHandler : DelegatingHandler { private static SemaphoreSlim semaphore = new SemaphoreSlim(1, 1); private readonly Random rand = new Random(); private readonly IAsyncPolicy resiliencyPolicies; + /// + /// Gets or sets the Forge configuration options. + /// protected readonly IOptions configuration; + /// + /// Gets or sets the token cache. + /// protected ITokenCache TokenCache { get; private set; } private bool IsDefaultClient(string user) => string.IsNullOrEmpty(user) || user == ForgeAgentHandler.defaultAgentName; + /// + /// Initializes a new instance of the class. + /// + /// The Forge configuration options. public ForgeHandler(IOptions configuration) { this.configuration = configuration ?? throw new ArgumentNullException(nameof(configuration)); this.TokenCache = new TokenCache(); this.resiliencyPolicies = GetResiliencyPolicies(GetDefaultTimeout()); } + /// + /// Gets the default timeout value. + /// + /// The default timeout value. protected virtual TimeSpan GetDefaultTimeout() { - // use timeout greater than the forge gateways (10s), we handle the GatewayTimeout response - return TimeSpan.FromSeconds(15); + // use timeout greater than the forge gateways (10s), we handle the GatewayTimeout response + return TimeSpan.FromSeconds(15); } - protected virtual (int baseDelayInMs, int multiplier ) GetRetryParameters() + /// + /// Gets the retry parameters for resiliency policies. + /// + /// A tuple containing the base delay in milliseconds and the multiplier. + protected virtual (int baseDelayInMs, int multiplier) GetRetryParameters() { return (500, 1000); } + /// + /// Sends an HTTP request asynchronously. + /// + /// The HTTP request message. + /// The cancellation token. + /// The task representing the asynchronous operation. + /// Thrown when the request URI is null. protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { if (request.RequestUri == null) @@ -80,10 +108,13 @@ protected override async Task SendAsync(HttpRequestMessage } return await policies.ExecuteAsync(async (ct) => await base.SendAsync(request, ct), cancellationToken); } - + /// + /// Gets the token refresh policy. + /// A policy that attempts to retry exactly once when a 401 error is received after obtaining a new token. + /// + /// The token refresh policy. protected virtual IAsyncPolicy GetTokenRefreshPolicy() { - // A policy that attempts to retry exactly once when 401 error is received after obtaining a new token return Policy .HandleResult(r => r.StatusCode == HttpStatusCode.Unauthorized) .RetryAsync( @@ -91,7 +122,11 @@ protected virtual IAsyncPolicy GetTokenRefreshPolicy() onRetryAsync: async (outcome, retryNumber, context) => await RefreshTokenAsync(outcome.Result.RequestMessage, true, CancellationToken.None) ); } - + /// + /// Gets the resiliency policies for handling HTTP requests. + /// + /// The timeout value for the policies. + /// The resiliency policies. protected virtual IAsyncPolicy GetResiliencyPolicies(TimeSpan timeoutValue) { // Retry when HttpRequestException is thrown (low level network error) or @@ -149,6 +184,13 @@ protected virtual IAsyncPolicy GetResiliencyPolicies(TimeSp return Policy.WrapAsync(breaker, retry, timeout); } + /// + /// Refreshes the token asynchronously. + /// + /// The HTTP request message. + /// A flag indicating whether to ignore the cache and always refresh the token. + /// The cancellation token. + /// The task representing the asynchronous operation. protected virtual async Task RefreshTokenAsync(HttpRequestMessage request, bool ignoreCache, CancellationToken cancellationToken) { if (request.Options.TryGetValue(ForgeConfiguration.ScopeKey, out var scope)) @@ -176,6 +218,14 @@ protected virtual async Task RefreshTokenAsync(HttpRequestMessage request, bool } } } + + /// + /// Gets a 2-legged token asynchronously. + /// + /// The user. + /// The scope. + /// The cancellation token. + /// A tuple containing the token and its expiry time. protected virtual async Task<(string, TimeSpan)> Get2LeggedTokenAsync(string user, string scope, CancellationToken cancellationToken) { using (var request = new HttpRequestMessage()) diff --git a/src/Autodesk.Forge.Core/ForgeService.cs b/src/Autodesk.Forge.Core/ForgeService.cs index 3dec382..36eceec 100644 --- a/src/Autodesk.Forge.Core/ForgeService.cs +++ b/src/Autodesk.Forge.Core/ForgeService.cs @@ -20,15 +20,29 @@ namespace Autodesk.Forge.Core { + /// + /// Represents a service for interacting with the Autodesk Forge platform. + /// public class ForgeService { + /// + /// Initializes a new instance of the class with the specified . + /// + /// The instance to be used for making HTTP requests. public ForgeService(HttpClient client) { this.Client = client ?? throw new ArgumentNullException(nameof(client)); } + /// + /// Gets the instance used by the Forge service. + /// public HttpClient Client { get; private set; } + /// + /// Creates a default instance of the class. + /// + /// A default instance of the class. public static ForgeService CreateDefault() { var configuration = new ConfigurationBuilder() diff --git a/src/Autodesk.Forge.Core/HttpResponseMessageExtensions.cs b/src/Autodesk.Forge.Core/HttpResponseMessageExtensions.cs index 3f4a6b8..87f0622 100644 --- a/src/Autodesk.Forge.Core/HttpResponseMessageExtensions.cs +++ b/src/Autodesk.Forge.Core/HttpResponseMessageExtensions.cs @@ -19,8 +19,18 @@ namespace Autodesk.Forge.Core { + /// + /// Ensures that the HTTP response message is a success status code. Throws exceptions for non-success status codes. + /// public static class HttpResponseMessageExtensions { + /// + /// Ensures that the HTTP response message is a success status code. Throws exceptions for non-success status codes. + /// + /// The HTTP response message. + /// The original HTTP response message if it is a success status code. + /// Thrown when the server returns a TooManyRequests status code. + /// Thrown when the server returns a non-success status code other than TooManyRequests. public static async Task EnsureSuccessStatusCodeAsync(this HttpResponseMessage msg) { string errorMessage = string.Empty; @@ -51,14 +61,26 @@ public static async Task EnsureSuccessStatusCodeAsync(this } } + /// + /// Exception thrown when the server returns a TooManyRequests status code. + /// public class TooManyRequestsException : HttpRequestException { + /// + /// Exception thrown when the server returns a TooManyRequests status code. + /// + /// Exception message. + /// Status code. + /// Retry after time. public TooManyRequestsException(string message, HttpStatusCode statusCode, TimeSpan? retryAfter) :base(message, null, statusCode) { this.RetryAfter = retryAfter; } - + + /// + /// Retry after time. + /// public TimeSpan? RetryAfter { get; init; } } } diff --git a/src/Autodesk.Forge.Core/LegacySampleConfigurationProvider.cs b/src/Autodesk.Forge.Core/LegacySampleConfigurationProvider.cs index 332cf99..edc6752 100644 --- a/src/Autodesk.Forge.Core/LegacySampleConfigurationProvider.cs +++ b/src/Autodesk.Forge.Core/LegacySampleConfigurationProvider.cs @@ -19,8 +19,16 @@ namespace Autodesk.Forge.Core { + /// + /// Extensions for adding Forge alternative environment variables to the configuration builder. + /// public static class ForgeAlternativeConfigurationExtensions { + /// + /// Adds Forge alternative environment variables to the configuration builder. + /// + /// The configuration builder. + /// The configuration builder with Forge alternative environment variables added. public static IConfigurationBuilder AddForgeAlternativeEnvironmentVariables(this IConfigurationBuilder configurationBuilder) { configurationBuilder.Add(new ForgeAlternativeConfigurationSource()); @@ -28,16 +36,30 @@ public static IConfigurationBuilder AddForgeAlternativeEnvironmentVariables(this } } + /// + /// Represents a configuration source for loading Forge alternative configuration. + /// public class ForgeAlternativeConfigurationSource : IConfigurationSource { + /// + /// Builds the Forge alternative configuration provider. + /// + /// The configuration builder. + /// The Forge alternative configuration provider. public IConfigurationProvider Build(IConfigurationBuilder builder) { return new ForgeAlternativeConfigurationProvider(); } } + /// + /// Loads the Forge alternative configuration from environment variables. + /// public class ForgeAlternativeConfigurationProvider : ConfigurationProvider { + /// + /// Loads the Forge alternative configuration from environment variables. + /// public override void Load() { var id = Environment.GetEnvironmentVariable("FORGE_CLIENT_ID"); diff --git a/src/Autodesk.Forge.Core/Marshalling.cs b/src/Autodesk.Forge.Core/Marshalling.cs index 1a1b66f..775cd56 100644 --- a/src/Autodesk.Forge.Core/Marshalling.cs +++ b/src/Autodesk.Forge.Core/Marshalling.cs @@ -22,6 +22,9 @@ namespace Autodesk.Forge.Core { + /// + /// Marshalling utilities. + /// public partial class Marshalling { private static string ParameterToString(object obj) @@ -44,7 +47,7 @@ private static string ParameterToString(object obj) /// Object representation of the JSON string. public static async Task DeserializeAsync(HttpContent content) { - if (content==null) + if (content == null) { throw new ArgumentNullException(nameof(content)); } @@ -69,13 +72,21 @@ public static HttpContent Serialize(object obj) return new StringContent(JsonConvert.SerializeObject(obj), Encoding.UTF8, "application/json"); } + /// + /// Builds a request URI based on the provided relative path, route parameters, and query parameters. + /// + /// The relative path of the request URI. + /// The route parameters to be replaced in the relative path. + /// The query parameters to be added to the request URI. + /// The built request URI. + /// Thrown when relativePath, routeParameters, or queryParameters is null. public static Uri BuildRequestUri(string relativePath, IDictionary routeParameters, IDictionary queryParameters) { if (relativePath == null) { throw new ArgumentNullException(nameof(relativePath)); } - if (routeParameters==null) + if (routeParameters == null) { throw new ArgumentNullException(nameof(routeParameters)); } @@ -90,7 +101,7 @@ public static Uri BuildRequestUri(string relativePath, IDictionary\w+)\}", m => HttpUtility.UrlEncode(ParameterToString(routeParameters[m.Groups["key"].Value])).Replace("%2b","+")); + relativePath = Regex.Replace(relativePath, @"\{(?\w+)\}", m => HttpUtility.UrlEncode(ParameterToString(routeParameters[m.Groups["key"].Value])).Replace("%2b", "+")); // add query parameters var query = new StringBuilder(); @@ -102,7 +113,7 @@ public static Uri BuildRequestUri(string relativePath, IDictionary0) + if (query.Length > 0) { query.Insert(0, "?"); relativePath += query.ToString(); diff --git a/src/Autodesk.Forge.Core/ServiceCollectionExtensions.cs b/src/Autodesk.Forge.Core/ServiceCollectionExtensions.cs index c87b8d2..a3b03e9 100644 --- a/src/Autodesk.Forge.Core/ServiceCollectionExtensions.cs +++ b/src/Autodesk.Forge.Core/ServiceCollectionExtensions.cs @@ -20,6 +20,9 @@ namespace Autodesk.Forge.Core { + /// + /// Extensions for adding ForgeService to the IServiceCollection. + /// public static class ServiceCollectionExtensions { /// @@ -27,9 +30,9 @@ public static class ServiceCollectionExtensions /// the values underneath. /// Also adds ForgeService as a typed HttpClient with ForgeHandler as its MessageHandler. /// - /// - /// - /// + /// The IServiceCollection to add the ForgeService to. + /// The IConfiguration containing the Forge configuration. + /// The IHttpClientBuilder for further configuration. public static IHttpClientBuilder AddForgeService(this IServiceCollection services, IConfiguration configuration) { services.AddOptions(); @@ -39,6 +42,17 @@ public static IHttpClientBuilder AddForgeService(this IServiceCollection service .AddHttpMessageHandler(); } + + /// + /// Adds the ForgeService to the IServiceCollection with the provided user and configuration. + /// It configures the ForgeConfiguration using the "Forge" section of the provided configuration. + /// It also adds the ForgeHandler as a transient service. + /// Finally, it adds the ForgeService as a typed HttpClient with the ForgeHandler as its MessageHandler. + /// + /// The IServiceCollection to add the ForgeService to. + /// The user associated with the ForgeService. + /// The IConfiguration containing the Forge configuration. + /// The IHttpClientBuilder for further configuration. public static IHttpClientBuilder AddForgeService(this IServiceCollection services, string user, IConfiguration configuration) { services.AddOptions(); diff --git a/src/Autodesk.Forge.Core/TokenCache.cs b/src/Autodesk.Forge.Core/TokenCache.cs index 7b1a413..07725fa 100644 --- a/src/Autodesk.Forge.Core/TokenCache.cs +++ b/src/Autodesk.Forge.Core/TokenCache.cs @@ -18,9 +18,24 @@ namespace Autodesk.Forge.Core { + /// + /// Represents a cache for storing access tokens. + /// public interface ITokenCache { + /// + /// Adds an access token to the cache. + /// + /// The key associated with the access token. + /// The access token to be added. + /// The time span indicating the expiration time of the access token. void Add(string key, string value, TimeSpan expiresIn); + /// + /// Tries to get the access token from the cache. + /// + /// The key associated with the access token. + /// The retrieved access token, if found. + /// true if the access token is found in the cache and not expired; otherwise, false. bool TryGetValue(string key, out string value); } diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 141d217..48732e1 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -14,7 +14,7 @@ snupkg - - + + \ No newline at end of file