fix: Replace TokenClient with TokenDelegatingHandler

This commit is contained in:
Valters Melnalksnis
2022-06-16 18:45:54 +03:00
parent b2cff73599
commit da99d06272
5 changed files with 92 additions and 95 deletions
@@ -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));
}