From da99d062726d837282e0cf0dbdf086018572ec28 Mon Sep 17 00:00:00 2001 From: Valters Melnalksnis Date: Thu, 16 Jun 2022 18:45:54 +0300 Subject: [PATCH] fix: Replace TokenClient with TokenDelegatingHandler --- .../ServiceCollectionExtensions.cs | 26 ++++--- .../NordigenHttpClient.cs | 40 ++--------- .../Tokens/TokenClient.cs | 50 -------------- .../Tokens/TokenDelegatingHandler.cs | 69 +++++++++++++++++++ .../Tokens/TokenSerializationContext.cs | 2 + 5 files changed, 92 insertions(+), 95 deletions(-) delete mode 100644 source/VMelnalksnis.NordigenDotNet/Tokens/TokenClient.cs create mode 100644 source/VMelnalksnis.NordigenDotNet/Tokens/TokenDelegatingHandler.cs diff --git a/source/VMelnalksnis.NordigenDotNet.DependencyInjection/ServiceCollectionExtensions.cs b/source/VMelnalksnis.NordigenDotNet.DependencyInjection/ServiceCollectionExtensions.cs index ea47c35..486da16 100644 --- a/source/VMelnalksnis.NordigenDotNet.DependencyInjection/ServiceCollectionExtensions.cs +++ b/source/VMelnalksnis.NordigenDotNet.DependencyInjection/ServiceCollectionExtensions.cs @@ -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(ConfigureNordigenClient); + return serviceCollection .AddTransient() .AddTransient() @@ -61,19 +64,22 @@ public static class ServiceCollectionExtensions .AddTransient() .AddTransient() .AddTransient(provider => provider.GetRequiredService>().CurrentValue) - .AddHttpClient((provider, client) => - { - client.BaseAddress = provider.GetRequiredService>().CurrentValue.BaseAddress; + .AddHttpClient(ConfigureNordigenClient) + .AddHttpMessageHandler(); + } - var assembly = typeof(INordigenClient).Assembly.GetName(); + private static void ConfigureNordigenClient(IServiceProvider provider, HttpClient client) + { + client.BaseAddress = provider.GetRequiredService>().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())); } } diff --git a/source/VMelnalksnis.NordigenDotNet/NordigenHttpClient.cs b/source/VMelnalksnis.NordigenDotNet/NordigenHttpClient.cs index e48711c..066b4c1 100644 --- a/source/VMelnalksnis.NordigenDotNet/NordigenHttpClient.cs +++ b/source/VMelnalksnis.NordigenDotNet/NordigenHttpClient.cs @@ -14,42 +14,30 @@ using System.Threading.Tasks; using NodaTime; using NodaTime.Serialization.SystemTextJson; -using VMelnalksnis.NordigenDotNet.Tokens; - namespace VMelnalksnis.NordigenDotNet; /// wrapper that uses the correct serialization settings for all requests. public sealed class NordigenHttpClient { private readonly HttpClient _httpClient; - private readonly NordigenOptions _nordigenOptions; private readonly JsonSerializerOptions _serializerOptions; - private readonly TokenClient _tokenClient; /// Initializes a new instance of the class. /// Http client configured for making requests to the Nordigen API. - /// Options for connection to the Nordigen API. /// Time zone provider for date and time serialization. - 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 GetAsJson(string requestUri, CancellationToken cancellationToken) { - var client = await GetClient().ConfigureAwait(false); - return await client.GetFromJsonAsync(requestUri, _serializerOptions, cancellationToken).ConfigureAwait(false); + return await _httpClient.GetFromJsonAsync(requestUri, _serializerOptions, cancellationToken).ConfigureAwait(false); } internal async IAsyncEnumerable GetAsJsonPaginated( @@ -84,39 +72,21 @@ public sealed class NordigenHttpClient internal async Task PostAsJson(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(_serializerOptions).ConfigureAwait(false); } internal async Task PutAsJson(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(_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 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; - } } diff --git a/source/VMelnalksnis.NordigenDotNet/Tokens/TokenClient.cs b/source/VMelnalksnis.NordigenDotNet/Tokens/TokenClient.cs deleted file mode 100644 index 1be9085..0000000 --- a/source/VMelnalksnis.NordigenDotNet/Tokens/TokenClient.cs +++ /dev/null @@ -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 _accessTokenInfo = (JsonTypeInfo)_context.GetTypeInfo(typeof(AccessToken)); - private static readonly JsonTypeInfo _tokenInfo = (JsonTypeInfo)_context.GetTypeInfo(typeof(Token)); - private static readonly JsonTypeInfo _tokenCreationInfo = (JsonTypeInfo)_context.GetTypeInfo(typeof(TokenCreation)); - private static readonly JsonTypeInfo _tokenRefreshInfo = (JsonTypeInfo)_context.GetTypeInfo(typeof(TokenRefresh)); - - private readonly HttpClient _httpClient; - - internal TokenClient(HttpClient httpClient) - { - _httpClient = httpClient; - } - - internal async Task 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 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!; - } -} diff --git a/source/VMelnalksnis.NordigenDotNet/Tokens/TokenDelegatingHandler.cs b/source/VMelnalksnis.NordigenDotNet/Tokens/TokenDelegatingHandler.cs new file mode 100644 index 0000000..019544b --- /dev/null +++ b/source/VMelnalksnis.NordigenDotNet/Tokens/TokenDelegatingHandler.cs @@ -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; + +/// Handles authorization tokens for . +public sealed class TokenDelegatingHandler : DelegatingHandler +{ + private static readonly TokenSerializationContext _context = new(new(JsonSerializerDefaults.Web)); + private static readonly JsonTypeInfo _accessTokenInfo = _context.GetTypeInfo(); + private static readonly JsonTypeInfo _tokenInfo = _context.GetTypeInfo(); + private static readonly JsonTypeInfo _tokenCreationInfo = _context.GetTypeInfo(); + private static readonly JsonTypeInfo _tokenRefreshInfo = _context.GetTypeInfo(); + + private readonly HttpClient _httpClient; + private readonly NordigenOptions _nordigenOptions; + + /// Initializes a new instance of the class. + /// Http client configured for making requests to the Nordigen API. + /// Options for connection to the Nordigen API. + public TokenDelegatingHandler(HttpClient httpClient, NordigenOptions nordigenOptions) + { + _httpClient = httpClient; + _nordigenOptions = nordigenOptions; + } + + /// + protected override async Task 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 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 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!; + } +} diff --git a/source/VMelnalksnis.NordigenDotNet/Tokens/TokenSerializationContext.cs b/source/VMelnalksnis.NordigenDotNet/Tokens/TokenSerializationContext.cs index 7de9286..afe24fa 100644 --- a/source/VMelnalksnis.NordigenDotNet/Tokens/TokenSerializationContext.cs +++ b/source/VMelnalksnis.NordigenDotNet/Tokens/TokenSerializationContext.cs @@ -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 GetTypeInfo() => (JsonTypeInfo)GetTypeInfo(typeof(T)); }