fix: Replace TokenClient with TokenDelegatingHandler
This commit is contained in:
+16
-10
@@ -19,6 +19,7 @@ using VMelnalksnis.NordigenDotNet.Accounts;
|
||||
using VMelnalksnis.NordigenDotNet.Agreements;
|
||||
using VMelnalksnis.NordigenDotNet.Institutions;
|
||||
using VMelnalksnis.NordigenDotNet.Requisitions;
|
||||
using VMelnalksnis.NordigenDotNet.Tokens;
|
||||
|
||||
namespace VMelnalksnis.NordigenDotNet.DependencyInjection;
|
||||
|
||||
@@ -54,6 +55,8 @@ public static class ServiceCollectionExtensions
|
||||
.Bind(configuration.GetSection(NordigenOptions.SectionName))
|
||||
.ValidateDataAnnotations();
|
||||
|
||||
serviceCollection.AddHttpClient<TokenDelegatingHandler>(ConfigureNordigenClient);
|
||||
|
||||
return serviceCollection
|
||||
.AddTransient<INordigenClient, NordigenClient>()
|
||||
.AddTransient<IAccountClient, AccountClient>()
|
||||
@@ -61,19 +64,22 @@ public static class ServiceCollectionExtensions
|
||||
.AddTransient<IInstitutionClient, InstitutionClient>()
|
||||
.AddTransient<IRequisitionClient, RequisitionClient>()
|
||||
.AddTransient(provider => provider.GetRequiredService<IOptionsMonitor<NordigenOptions>>().CurrentValue)
|
||||
.AddHttpClient<NordigenHttpClient>((provider, client) =>
|
||||
{
|
||||
client.BaseAddress = provider.GetRequiredService<IOptionsMonitor<NordigenOptions>>().CurrentValue.BaseAddress;
|
||||
.AddHttpClient<NordigenHttpClient>(ConfigureNordigenClient)
|
||||
.AddHttpMessageHandler<TokenDelegatingHandler>();
|
||||
}
|
||||
|
||||
var assembly = typeof(INordigenClient).Assembly.GetName();
|
||||
private static void ConfigureNordigenClient(IServiceProvider provider, HttpClient client)
|
||||
{
|
||||
client.BaseAddress = provider.GetRequiredService<IOptionsMonitor<NordigenOptions>>().CurrentValue.BaseAddress;
|
||||
|
||||
var assemblyName = assembly.Name ??
|
||||
throw new InvalidOperationException($"Assembly {assembly.FullName} name is not specified");
|
||||
var assembly = typeof(INordigenClient).Assembly.GetName();
|
||||
|
||||
var assemblyVersion = assembly.Version ??
|
||||
throw new InvalidOperationException($"Assembly {assembly.FullName} version is not specified");
|
||||
var assemblyName = assembly.Name ??
|
||||
throw new InvalidOperationException($"Assembly {assembly.FullName} name is not specified");
|
||||
|
||||
client.DefaultRequestHeaders.UserAgent.Add(new(assemblyName, assemblyVersion.ToString()));
|
||||
});
|
||||
var assemblyVersion = assembly.Version ??
|
||||
throw new InvalidOperationException($"Assembly {assembly.FullName} version is not specified");
|
||||
|
||||
client.DefaultRequestHeaders.UserAgent.Add(new(assemblyName, assemblyVersion.ToString()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,42 +14,30 @@ using System.Threading.Tasks;
|
||||
using NodaTime;
|
||||
using NodaTime.Serialization.SystemTextJson;
|
||||
|
||||
using VMelnalksnis.NordigenDotNet.Tokens;
|
||||
|
||||
namespace VMelnalksnis.NordigenDotNet;
|
||||
|
||||
/// <summary><see cref="HttpClient"/> wrapper that uses the correct serialization settings for all requests.</summary>
|
||||
public sealed class NordigenHttpClient
|
||||
{
|
||||
private readonly HttpClient _httpClient;
|
||||
private readonly NordigenOptions _nordigenOptions;
|
||||
private readonly JsonSerializerOptions _serializerOptions;
|
||||
private readonly TokenClient _tokenClient;
|
||||
|
||||
/// <summary>Initializes a new instance of the <see cref="NordigenHttpClient"/> class.</summary>
|
||||
/// <param name="httpClient">Http client configured for making requests to the Nordigen API.</param>
|
||||
/// <param name="nordigenOptions">Options for connection to the Nordigen API.</param>
|
||||
/// <param name="dateTimeZoneProvider">Time zone provider for date and time serialization.</param>
|
||||
public NordigenHttpClient(
|
||||
HttpClient httpClient,
|
||||
NordigenOptions nordigenOptions,
|
||||
IDateTimeZoneProvider dateTimeZoneProvider)
|
||||
public NordigenHttpClient(HttpClient httpClient, IDateTimeZoneProvider dateTimeZoneProvider)
|
||||
{
|
||||
_httpClient = httpClient;
|
||||
_nordigenOptions = nordigenOptions;
|
||||
|
||||
_serializerOptions = new JsonSerializerOptions(JsonSerializerDefaults.Web)
|
||||
{
|
||||
Converters = { new JsonStringEnumConverter() },
|
||||
}.ConfigureForNodaTime(dateTimeZoneProvider);
|
||||
|
||||
_tokenClient = new(httpClient);
|
||||
}
|
||||
|
||||
internal async Task<TResult?> GetAsJson<TResult>(string requestUri, CancellationToken cancellationToken)
|
||||
{
|
||||
var client = await GetClient().ConfigureAwait(false);
|
||||
return await client.GetFromJsonAsync<TResult>(requestUri, _serializerOptions, cancellationToken).ConfigureAwait(false);
|
||||
return await _httpClient.GetFromJsonAsync<TResult>(requestUri, _serializerOptions, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
internal async IAsyncEnumerable<TResult> GetAsJsonPaginated<TResult>(
|
||||
@@ -84,39 +72,21 @@ public sealed class NordigenHttpClient
|
||||
|
||||
internal async Task<TResult?> PostAsJson<TRequest, TResult>(string requestUri, TRequest request)
|
||||
{
|
||||
var client = await GetClient().ConfigureAwait(false);
|
||||
var response = await client.PostAsJsonAsync(requestUri, request, _serializerOptions).ConfigureAwait(false);
|
||||
var response = await _httpClient.PostAsJsonAsync(requestUri, request, _serializerOptions).ConfigureAwait(false);
|
||||
await response.ThrowIfNotSuccessful().ConfigureAwait(false);
|
||||
return await response.Content.ReadFromJsonAsync<TResult>(_serializerOptions).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
internal async Task<TResult?> PutAsJson<TRequest, TResult>(string requestUri, TRequest request)
|
||||
{
|
||||
var client = await GetClient().ConfigureAwait(false);
|
||||
var response = await client.PutAsJsonAsync(requestUri, request, _serializerOptions).ConfigureAwait(false);
|
||||
var response = await _httpClient.PutAsJsonAsync(requestUri, request, _serializerOptions).ConfigureAwait(false);
|
||||
await response.ThrowIfNotSuccessful().ConfigureAwait(false);
|
||||
return await response.Content.ReadFromJsonAsync<TResult>(_serializerOptions).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
internal async Task Delete(string requestUri)
|
||||
{
|
||||
var client = await GetClient().ConfigureAwait(false);
|
||||
var response = await client.DeleteAsync(requestUri).ConfigureAwait(false);
|
||||
var response = await _httpClient.DeleteAsync(requestUri).ConfigureAwait(false);
|
||||
await response.ThrowIfNotSuccessful().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async Task<HttpClient> GetClient()
|
||||
{
|
||||
if (_httpClient.DefaultRequestHeaders.Authorization is not null)
|
||||
{
|
||||
return _httpClient;
|
||||
}
|
||||
|
||||
var token = await _tokenClient
|
||||
.New(new(_nordigenOptions.SecretId, _nordigenOptions.SecretKey))
|
||||
.ConfigureAwait(false);
|
||||
|
||||
_httpClient.DefaultRequestHeaders.Authorization = new("Bearer", token.Access);
|
||||
return _httpClient;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
// Copyright 2022 Valters Melnalksnis
|
||||
// Licensed under the Apache License 2.0.
|
||||
// See LICENSE file in the project root for full license information.
|
||||
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Json;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization.Metadata;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace VMelnalksnis.NordigenDotNet.Tokens;
|
||||
|
||||
internal sealed class TokenClient
|
||||
{
|
||||
private static readonly TokenSerializationContext _context = new(new(JsonSerializerDefaults.Web));
|
||||
private static readonly JsonTypeInfo<AccessToken> _accessTokenInfo = (JsonTypeInfo<AccessToken>)_context.GetTypeInfo(typeof(AccessToken));
|
||||
private static readonly JsonTypeInfo<Token> _tokenInfo = (JsonTypeInfo<Token>)_context.GetTypeInfo(typeof(Token));
|
||||
private static readonly JsonTypeInfo<TokenCreation> _tokenCreationInfo = (JsonTypeInfo<TokenCreation>)_context.GetTypeInfo(typeof(TokenCreation));
|
||||
private static readonly JsonTypeInfo<TokenRefresh> _tokenRefreshInfo = (JsonTypeInfo<TokenRefresh>)_context.GetTypeInfo(typeof(TokenRefresh));
|
||||
|
||||
private readonly HttpClient _httpClient;
|
||||
|
||||
internal TokenClient(HttpClient httpClient)
|
||||
{
|
||||
_httpClient = httpClient;
|
||||
}
|
||||
|
||||
internal async Task<Token> New(TokenCreation tokenCreation)
|
||||
{
|
||||
var tokenResponse = await _httpClient
|
||||
.PostAsJsonAsync(Routes.Tokens.New, tokenCreation, _tokenCreationInfo)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
await tokenResponse.ThrowIfNotSuccessful().ConfigureAwait(false);
|
||||
var token = await tokenResponse.Content.ReadFromJsonAsync(_tokenInfo).ConfigureAwait(false);
|
||||
return token!;
|
||||
}
|
||||
|
||||
internal async Task<AccessToken> Refresh(TokenRefresh tokenRefresh)
|
||||
{
|
||||
var tokenResponse = await _httpClient
|
||||
.PostAsJsonAsync(Routes.Tokens.Refresh, tokenRefresh, _tokenRefreshInfo)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
await tokenResponse.ThrowIfNotSuccessful().ConfigureAwait(false);
|
||||
var accessToken = await tokenResponse.Content.ReadFromJsonAsync(_accessTokenInfo).ConfigureAwait(false);
|
||||
|
||||
return accessToken!;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
// Copyright 2022 Valters Melnalksnis
|
||||
// Licensed under the Apache License 2.0.
|
||||
// See LICENSE file in the project root for full license information.
|
||||
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Json;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization.Metadata;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace VMelnalksnis.NordigenDotNet.Tokens;
|
||||
|
||||
/// <summary>Handles authorization tokens for <see cref="NordigenHttpClient"/>.</summary>
|
||||
public sealed class TokenDelegatingHandler : DelegatingHandler
|
||||
{
|
||||
private static readonly TokenSerializationContext _context = new(new(JsonSerializerDefaults.Web));
|
||||
private static readonly JsonTypeInfo<AccessToken> _accessTokenInfo = _context.GetTypeInfo<AccessToken>();
|
||||
private static readonly JsonTypeInfo<Token> _tokenInfo = _context.GetTypeInfo<Token>();
|
||||
private static readonly JsonTypeInfo<TokenCreation> _tokenCreationInfo = _context.GetTypeInfo<TokenCreation>();
|
||||
private static readonly JsonTypeInfo<TokenRefresh> _tokenRefreshInfo = _context.GetTypeInfo<TokenRefresh>();
|
||||
|
||||
private readonly HttpClient _httpClient;
|
||||
private readonly NordigenOptions _nordigenOptions;
|
||||
|
||||
/// <summary>Initializes a new instance of the <see cref="TokenDelegatingHandler"/> class.</summary>
|
||||
/// <param name="httpClient">Http client configured for making requests to the Nordigen API.</param>
|
||||
/// <param name="nordigenOptions">Options for connection to the Nordigen API.</param>
|
||||
public TokenDelegatingHandler(HttpClient httpClient, NordigenOptions nordigenOptions)
|
||||
{
|
||||
_httpClient = httpClient;
|
||||
_nordigenOptions = nordigenOptions;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override async Task<HttpResponseMessage> SendAsync(
|
||||
HttpRequestMessage request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var token = await CreateNewToken().ConfigureAwait(false);
|
||||
request.Headers.Authorization = new("Bearer", token.Access);
|
||||
|
||||
return await base.SendAsync(request, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async Task<Token> CreateNewToken()
|
||||
{
|
||||
var tokenCreation = new TokenCreation(_nordigenOptions.SecretId, _nordigenOptions.SecretKey);
|
||||
var tokenResponse = await _httpClient
|
||||
.PostAsJsonAsync(Routes.Tokens.New, tokenCreation, _tokenCreationInfo)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
await tokenResponse.ThrowIfNotSuccessful().ConfigureAwait(false);
|
||||
var token = await tokenResponse.Content.ReadFromJsonAsync(_tokenInfo).ConfigureAwait(false);
|
||||
return token!;
|
||||
}
|
||||
|
||||
private async Task<AccessToken> Refresh(TokenRefresh tokenRefresh)
|
||||
{
|
||||
var tokenResponse = await _httpClient
|
||||
.PostAsJsonAsync(Routes.Tokens.Refresh, tokenRefresh, _tokenRefreshInfo)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
await tokenResponse.ThrowIfNotSuccessful().ConfigureAwait(false);
|
||||
var accessToken = await tokenResponse.Content.ReadFromJsonAsync(_accessTokenInfo).ConfigureAwait(false);
|
||||
|
||||
return accessToken!;
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
// See LICENSE file in the project root for full license information.
|
||||
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Text.Json.Serialization.Metadata;
|
||||
|
||||
namespace VMelnalksnis.NordigenDotNet.Tokens;
|
||||
|
||||
@@ -13,4 +14,5 @@ namespace VMelnalksnis.NordigenDotNet.Tokens;
|
||||
[JsonSerializable(typeof(TokenRefresh))]
|
||||
internal partial class TokenSerializationContext : JsonSerializerContext
|
||||
{
|
||||
internal JsonTypeInfo<T> GetTypeInfo<T>() => (JsonTypeInfo<T>)GetTypeInfo(typeof(T));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user