Skip to content

Commit

Permalink
Add Documentation in Autodesk.Forge.Core (#165)
Browse files Browse the repository at this point in the history
  • Loading branch information
ricaun authored Jul 25, 2024
1 parent 8151dae commit b5b4065
Show file tree
Hide file tree
Showing 17 changed files with 259 additions and 20 deletions.
21 changes: 21 additions & 0 deletions Autodesk.Forge.sln
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
### 4.0.1

* Add Documentation in the `Autodesk.Forge.Core` project.

### 4.0.0.0

* Migrate to .Net 8
Expand Down
1 change: 1 addition & 0 deletions Directory.Build.props
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<Project>
<PropertyGroup>
<Version>4.0.1</Version>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

<PropertyGroup>
<Description>Shared code for Forge client sdks e2e tests</Description>
<PackageVersion>4.0.0</PackageVersion>
<IsTestProject>false</IsTestProject>
<NoWarn>NU5100</NoWarn>
</PropertyGroup>
Expand Down
9 changes: 8 additions & 1 deletion src/Autodesk.Forge.Core/ApiResponse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ namespace Autodesk.Forge.Core
/// </summary>
public class ApiResponse : IDisposable
{
/// <summary>
/// Gets or sets the HTTP response message.
/// </summary>
/// <value>The HTTP response message.</value>
public HttpResponseMessage HttpResponse { get; private set; }

/// <summary>
Expand All @@ -34,6 +38,9 @@ public ApiResponse(HttpResponseMessage response)
this.HttpResponse = response;
}

/// <summary>
/// Disposes the API response.
/// </summary>
public void Dispose()
{
HttpResponse?.Dispose();
Expand All @@ -55,7 +62,7 @@ public class ApiResponse<T> : ApiResponse
/// Initializes a new instance of the <see cref="ApiResponse&lt;T&gt;" /> class.
/// </summary>
/// <param name="response">Http response message.</param>
/// <param name="data">content (parsed HTTP body)</param>
/// <param name="content">content (parsed HTTP body)</param>
public ApiResponse(HttpResponseMessage response, T content)
: base(response)
{
Expand Down
2 changes: 1 addition & 1 deletion src/Autodesk.Forge.Core/Autodesk.Forge.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

<PropertyGroup>
<Description>Shared code for APS client sdks</Description>
<PackageVersion>4.0.0</PackageVersion>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>

<ItemGroup>
Expand Down
10 changes: 10 additions & 0 deletions src/Autodesk.Forge.Core/ForgeAgentConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,19 @@

namespace Autodesk.Forge.Core
{
/// <summary>
/// Represents the configuration for the Forge Agent.
/// </summary>
public class ForgeAgentConfiguration
{
/// <summary>
/// Gets or sets the client ID.
/// </summary>
public string ClientId { get; init; }

/// <summary>
/// Gets or sets the client secret.
/// </summary>
public string ClientSecret { get; init; }
}
}
20 changes: 19 additions & 1 deletion src/Autodesk.Forge.Core/ForgeAgentHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,33 @@

namespace Autodesk.Forge.Core
{
/// <summary>
/// Represents a handler for Forge agents.
/// </summary>
public class ForgeAgentHandler : DelegatingHandler
{
/// <summary>
/// The default agent name.
/// </summary>
public const string defaultAgentName = "default";

private string user;

/// <summary>
/// Initializes a new instance of the <see cref="ForgeAgentHandler"/> class.
/// </summary>
/// <param name="user">The user associated with the agent.</param>
public ForgeAgentHandler(string user)
{
this.user = user;
}

/// <summary>
/// Sends an HTTP request asynchronously.
/// </summary>
/// <param name="request">The HTTP request message.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The task representing the asynchronous operation.</returns>
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
request.Options.TryAdd(ForgeConfiguration.AgentKey.Key, user);
Expand Down
31 changes: 31 additions & 0 deletions src/Autodesk.Forge.Core/ForgeConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,50 @@

namespace Autodesk.Forge.Core
{
/// <summary>
/// Represents the configuration settings for the Forge SDK.
/// </summary>
public class ForgeConfiguration
{
/// <summary>
/// Represents the key for the Forge agent in the HTTP request options.
/// </summary>
public static readonly HttpRequestOptionsKey<string> AgentKey = new HttpRequestOptionsKey<string>("Autodesk.Forge.Agent");
/// <summary>
/// Represents the key for the Forge scope in the HTTP request options.
/// </summary>
public static readonly HttpRequestOptionsKey<string> ScopeKey = new HttpRequestOptionsKey<string>("Autodesk.Forge.Scope");
/// <summary>
/// Represents the key for the Forge timeout in the HTTP request options.
/// </summary>
public static readonly HttpRequestOptionsKey<int> TimeoutKey = new HttpRequestOptionsKey<int>("Autodesk.Forge.Timeout");

/// <summary>
/// Initializes a new instance of the <see cref="ForgeConfiguration"/> class.
/// </summary>
public ForgeConfiguration()
{
this.AuthenticationAddress = new Uri("https://developer.api.autodesk.com/authentication/v2/token");
}

/// <summary>
/// Gets or sets the client ID.
/// </summary>
public string ClientId { get; set; }

/// <summary>
/// Gets or sets the client secret.
/// </summary>
public string ClientSecret { get; set; }

/// <summary>
/// Gets or sets the dictionary of Forge agent configurations.
/// </summary>
public IDictionary<string, ForgeAgentConfiguration> Agents { get; set; }

/// <summary>
/// Gets or sets the authentication address.
/// </summary>
public Uri AuthenticationAddress { get; set; }
}
}
62 changes: 56 additions & 6 deletions src/Autodesk.Forge.Core/ForgeHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,33 +23,61 @@

namespace Autodesk.Forge.Core
{
/// <summary>
/// Represents a handler for Forge API requests.
/// </summary>
public class ForgeHandler : DelegatingHandler
{
private static SemaphoreSlim semaphore = new SemaphoreSlim(1, 1);
private readonly Random rand = new Random();
private readonly IAsyncPolicy<HttpResponseMessage> resiliencyPolicies;

/// <summary>
/// Gets or sets the Forge configuration options.
/// </summary>
protected readonly IOptions<ForgeConfiguration> configuration;

/// <summary>
/// Gets or sets the token cache.
/// </summary>
protected ITokenCache TokenCache { get; private set; }

private bool IsDefaultClient(string user) => string.IsNullOrEmpty(user) || user == ForgeAgentHandler.defaultAgentName;

/// <summary>
/// Initializes a new instance of the <see cref="ForgeHandler"/> class.
/// </summary>
/// <param name="configuration">The Forge configuration options.</param>
public ForgeHandler(IOptions<ForgeConfiguration> configuration)
{
this.configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
this.TokenCache = new TokenCache();
this.resiliencyPolicies = GetResiliencyPolicies(GetDefaultTimeout());
}
/// <summary>
/// Gets the default timeout value.
/// </summary>
/// <returns>The default timeout value.</returns>
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()
/// <summary>
/// Gets the retry parameters for resiliency policies.
/// </summary>
/// <returns>A tuple containing the base delay in milliseconds and the multiplier.</returns>
protected virtual (int baseDelayInMs, int multiplier) GetRetryParameters()
{
return (500, 1000);
}
/// <summary>
/// Sends an HTTP request asynchronously.
/// </summary>
/// <param name="request">The HTTP request message.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The task representing the asynchronous operation.</returns>
/// <exception cref="ArgumentNullException">Thrown when the request URI is null.</exception>
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
if (request.RequestUri == null)
Expand Down Expand Up @@ -80,18 +108,25 @@ protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage
}
return await policies.ExecuteAsync(async (ct) => await base.SendAsync(request, ct), cancellationToken);
}

/// <summary>
/// Gets the token refresh policy.
/// A policy that attempts to retry exactly once when a 401 error is received after obtaining a new token.
/// </summary>
/// <returns>The token refresh policy.</returns>
protected virtual IAsyncPolicy<HttpResponseMessage> GetTokenRefreshPolicy()
{
// A policy that attempts to retry exactly once when 401 error is received after obtaining a new token
return Policy
.HandleResult<HttpResponseMessage>(r => r.StatusCode == HttpStatusCode.Unauthorized)
.RetryAsync(
retryCount: 1,
onRetryAsync: async (outcome, retryNumber, context) => await RefreshTokenAsync(outcome.Result.RequestMessage, true, CancellationToken.None)
);
}

/// <summary>
/// Gets the resiliency policies for handling HTTP requests.
/// </summary>
/// <param name="timeoutValue">The timeout value for the policies.</param>
/// <returns>The resiliency policies.</returns>
protected virtual IAsyncPolicy<HttpResponseMessage> GetResiliencyPolicies(TimeSpan timeoutValue)
{
// Retry when HttpRequestException is thrown (low level network error) or
Expand Down Expand Up @@ -149,6 +184,13 @@ protected virtual IAsyncPolicy<HttpResponseMessage> GetResiliencyPolicies(TimeSp
return Policy.WrapAsync<HttpResponseMessage>(breaker, retry, timeout);
}

/// <summary>
/// Refreshes the token asynchronously.
/// </summary>
/// <param name="request">The HTTP request message.</param>
/// <param name="ignoreCache">A flag indicating whether to ignore the cache and always refresh the token.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The task representing the asynchronous operation.</returns>
protected virtual async Task RefreshTokenAsync(HttpRequestMessage request, bool ignoreCache, CancellationToken cancellationToken)
{
if (request.Options.TryGetValue(ForgeConfiguration.ScopeKey, out var scope))
Expand Down Expand Up @@ -176,6 +218,14 @@ protected virtual async Task RefreshTokenAsync(HttpRequestMessage request, bool
}
}
}

/// <summary>
/// Gets a 2-legged token asynchronously.
/// </summary>
/// <param name="user">The user.</param>
/// <param name="scope">The scope.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A tuple containing the token and its expiry time.</returns>
protected virtual async Task<(string, TimeSpan)> Get2LeggedTokenAsync(string user, string scope, CancellationToken cancellationToken)
{
using (var request = new HttpRequestMessage())
Expand Down
14 changes: 14 additions & 0 deletions src/Autodesk.Forge.Core/ForgeService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,29 @@

namespace Autodesk.Forge.Core
{
/// <summary>
/// Represents a service for interacting with the Autodesk Forge platform.
/// </summary>
public class ForgeService
{
/// <summary>
/// Initializes a new instance of the <see cref="ForgeService"/> class with the specified <see cref="HttpClient"/>.
/// </summary>
/// <param name="client">The <see cref="HttpClient"/> instance to be used for making HTTP requests.</param>
public ForgeService(HttpClient client)
{
this.Client = client ?? throw new ArgumentNullException(nameof(client));
}

/// <summary>
/// Gets the <see cref="HttpClient"/> instance used by the Forge service.
/// </summary>
public HttpClient Client { get; private set; }

/// <summary>
/// Creates a default instance of the <see cref="ForgeService"/> class.
/// </summary>
/// <returns>A default instance of the <see cref="ForgeService"/> class.</returns>
public static ForgeService CreateDefault()
{
var configuration = new ConfigurationBuilder()
Expand Down
Loading

0 comments on commit b5b4065

Please sign in to comment.