From f927ae9d1cb1d457659759b3333d6755a3f5bf0d Mon Sep 17 00:00:00 2001 From: NazarovMikhail Date: Wed, 21 Jan 2026 17:14:00 +0500 Subject: [PATCH 1/5] Drop RestHttpClientFromOptions. --- .../TestService.cs | 10 +- .../TestService.cs | 10 +- .../Models/RestHttpClientMock.cs | 25 +-- .../RestHttpClientFromOptionsTests.cs | 164 ------------------ .../RestHttpClientTests.cs | 27 ++- .../RestHttpClientFromOptions.cs | 71 -------- 6 files changed, 35 insertions(+), 272 deletions(-) delete mode 100644 src/Monq.Core.HttpClientExtensions.Tests/RestHttpClientFromOptionsTests.cs delete mode 100644 src/Monq.Core.HttpClientExtensions/RestHttpClientFromOptions.cs diff --git a/samples/Monq.Core.HttpClientExtensions.TestApp/TestService.cs b/samples/Monq.Core.HttpClientExtensions.TestApp/TestService.cs index a811d00..8098b4f 100644 --- a/samples/Monq.Core.HttpClientExtensions.TestApp/TestService.cs +++ b/samples/Monq.Core.HttpClientExtensions.TestApp/TestService.cs @@ -1,4 +1,4 @@ -using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using System; @@ -12,7 +12,7 @@ public interface ITestService Task TestApi(); } - public class TestService : RestHttpClientFromOptions, ITestService + public class TestService : RestHttpClient, ITestService { /// /// Инициализирует новый экземпляр класса . @@ -29,12 +29,10 @@ public TestService( ILoggerFactory loggerFactory, RestHttpClientOptions configuration, IHttpContextAccessor httpContextAccessor) : - base(optionsAccessor, - httpClient, + base(httpClient, loggerFactory, configuration, - httpContextAccessor, - optionsAccessor.Value.TestServiceUri) + httpContextAccessor) { } diff --git a/samples/Monq.Core.HttpClientExtensions.TestConsoleApp/TestService.cs b/samples/Monq.Core.HttpClientExtensions.TestConsoleApp/TestService.cs index 4671ad9..23b432d 100644 --- a/samples/Monq.Core.HttpClientExtensions.TestConsoleApp/TestService.cs +++ b/samples/Monq.Core.HttpClientExtensions.TestConsoleApp/TestService.cs @@ -1,4 +1,4 @@ -using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Monq.Core.HttpClientExtensions.TestApp; @@ -14,7 +14,7 @@ public interface ITestService Task TestApi(string auth); } - public class TestService : RestHttpClientFromOptions, ITestService + public class TestService : RestHttpClient, ITestService { ILogger _log; @@ -33,12 +33,10 @@ public TestService( ILoggerFactory loggerFactory, RestHttpClientOptions configuration, IHttpContextAccessor httpContextAccessor) : - base(optionsAccessor, - httpClient, + base(httpClient, loggerFactory, configuration, - httpContextAccessor, - optionsAccessor.Value.TestServiceUri) + httpContextAccessor) { _log = loggerFactory.CreateLogger(); } diff --git a/src/Monq.Core.HttpClientExtensions.Tests/Models/RestHttpClientMock.cs b/src/Monq.Core.HttpClientExtensions.Tests/Models/RestHttpClientMock.cs index 48be20e..48c9b37 100644 --- a/src/Monq.Core.HttpClientExtensions.Tests/Models/RestHttpClientMock.cs +++ b/src/Monq.Core.HttpClientExtensions.Tests/Models/RestHttpClientMock.cs @@ -1,4 +1,4 @@ -using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using System.Net.Http; @@ -23,27 +23,4 @@ public RestHttpClientMock(HttpClient httpClient, } } - - public class RestHttpClientFromOptionsMock : RestHttpClientFromOptions - { - /// - /// Initializes a new instance of the class. - /// - /// The options. - /// The HttpClient from http client factory. - /// The logger factory. - /// The configuration. - /// The HTTP context accessor. - /// The base Uri of all requests. For example: http://rsm.api.monq.cloud - public RestHttpClientFromOptionsMock(IOptions optionsAccessor, - HttpClient httpClient, - ILoggerFactory loggerFactory, - RestHttpClientOptions configuration, - IHttpContextAccessor httpContextAccessor, - string baseUri) - : base(optionsAccessor, httpClient, loggerFactory, configuration, httpContextAccessor, baseUri) - { - - } - } } diff --git a/src/Monq.Core.HttpClientExtensions.Tests/RestHttpClientFromOptionsTests.cs b/src/Monq.Core.HttpClientExtensions.Tests/RestHttpClientFromOptionsTests.cs deleted file mode 100644 index f2062e4..0000000 --- a/src/Monq.Core.HttpClientExtensions.Tests/RestHttpClientFromOptionsTests.cs +++ /dev/null @@ -1,164 +0,0 @@ -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using Monq.Core.HttpClientExtensions.Tests.Models; -using Monq.Core.HttpClientExtensions.Tests.Stubs; -using Moq; -using Moq.Protected; -using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using System.Net; -using System.Net.Http; -using System.Threading; -using System.Threading.Tasks; -using Xunit; - -namespace Monq.Core.HttpClientExtensions.Tests -{ - public class RestHttpClientFromOptionsTests - { - readonly Mock> _optionsMoq; - - readonly IList _testCollection = new List { - new Service { Id = 1, Name = "Service1" }, - new Service { Id = 2, Name = "Service2" } - }; - - readonly ILoggerFactory _loggerFactory = new StubLoggerFactory(new List()); - readonly RestHttpClientOptions _configuration = new RestHttpClientOptions(); - - public RestHttpClientFromOptionsTests() - { - _optionsMoq = new Mock>(); - _optionsMoq - .Setup(x => x.Value) - .Returns(new ServiceOptions() - { - ServiceUri = "http://unittest" - }); - } - - [Fact(DisplayName = "Проверка установки Bearer token из HttpContextAccessor.")] - public void ShouldProperlySetBearerTokenFromRequest() - { - var httpContext = new DefaultHttpContext(); - httpContext.Request.Headers.Add("Authorization", "Bearer token355"); - - var client = new HttpClient(CreateDefaultResponseHandler(HttpStatusCode.Unauthorized)); - - var httpService = CreateBasicHttpService(client, httpContext); - - Assert.Equal("token355", client.DefaultRequestHeaders?.Authorization?.Parameter); - Assert.Equal("Bearer", client.DefaultRequestHeaders?.Authorization?.Scheme); - } - - [Fact(DisplayName = "Проверка НЕустановки Bearer token из HttpContextAccessor.")] - public void ShouldNotSetBearerTokenFromRequestIfBearerNotValid() - { - var httpContext = new DefaultHttpContext(); - httpContext.Request.Headers.Add("Authorization", "Bearetoken355"); - var client = new HttpClient(CreateDefaultResponseHandler(HttpStatusCode.Unauthorized)); - - var httpService = CreateBasicHttpService(client, httpContext); - Assert.Null(client.DefaultRequestHeaders.Authorization); - } - - [Fact(DisplayName = "Добавить слэш в BaseUri, если требуется.")] - public void ShouldAddTrailingSlashToBaseUri() - { - var httpContext = new DefaultHttpContext(); - httpContext.Request.Headers.Add("Authorization", "Bearetoken355"); - - _optionsMoq - .Setup(x => x.Value) - .Returns(new ServiceOptions() - { - ServiceUri = "http://unittest" - }); - - var client = new HttpClient(CreateDefaultResponseHandler(HttpStatusCode.Unauthorized)); - - var httpService = CreateRestHttpClientFromOptions(client, httpContext, _optionsMoq.Object); - Assert.Equal(new Uri("http://unittest/"), client.BaseAddress); - } - - [Fact(DisplayName = "Выполнить запрос по абсолютному Uri.")] - public async Task ShouldProperlyMakeRequestByAbsoluteUri() - { - var httpContext = new DefaultHttpContext(); - httpContext.Request.Headers.Add("Authorization", "Bearetoken355"); - - _optionsMoq - .Setup(x => x.Value) - .Returns(new ServiceOptions() - { - ServiceUri = "http://unittest" - }); - - var client = new HttpClient(CreateDefaultResponseHandler(HttpStatusCode.OK)); - - var httpService = CreateRestHttpClientFromOptions(client, httpContext, _optionsMoq.Object); - var result = await httpService.Get>("http://unittest/api/services"); - - Assert.Equal(result.ResultObject, _testCollection); - } - - [Fact(DisplayName = "Выполнить запрос по относительному Uri.")] - public async Task ShouldProperlyMakeRequestByRelativeUri() - { - var httpContext = new DefaultHttpContext(); - httpContext.Request.Headers.Add("Authorization", "Bearetoken355"); - - _optionsMoq - .Setup(x => x.Value) - .Returns(new ServiceOptions() - { - ServiceUri = "http://unittest" - }); - - var client = new HttpClient(CreateDefaultResponseHandler(HttpStatusCode.OK)); - var httpService = CreateRestHttpClientFromOptions(client, httpContext, _optionsMoq.Object); - var result = await httpService.Get>("api/services", TimeSpan.FromSeconds(5)); - - Assert.Equal(result.ResultObject, _testCollection); - } - - RestHttpClientMock CreateBasicHttpService(HttpClient httpClient, HttpContext? httpContext) - { - return new RestHttpClientMock( - httpClient, - _loggerFactory, - _configuration, - new HttpContextAccessorStub(httpContext ?? new DefaultHttpContext())); - } - - RestHttpClientFromOptionsMock CreateRestHttpClientFromOptions(HttpClient httpClient, HttpContext? httpContext, IOptions optionsAccessor) - { - return new RestHttpClientFromOptionsMock( - optionsAccessor ?? _optionsMoq.Object, - httpClient, - _loggerFactory, - _configuration, - new HttpContextAccessorStub(httpContext ?? new DefaultHttpContext()), - optionsAccessor.Value.ServiceUri); - } - - HttpMessageHandler CreateDefaultResponseHandler(HttpStatusCode responseCode) - { - var modelJson = JsonConvert.SerializeObject(_testCollection); - - var mockHttpMessageHandler = new Mock(); - - mockHttpMessageHandler.Protected() - .Setup>("SendAsync", ItExpr.IsAny(), ItExpr.IsAny()) - .ReturnsAsync(new HttpResponseMessage - { - StatusCode = responseCode, - Content = new StringContent(modelJson), - }); - - return mockHttpMessageHandler.Object; - } - } -} \ No newline at end of file diff --git a/src/Monq.Core.HttpClientExtensions.Tests/RestHttpClientTests.cs b/src/Monq.Core.HttpClientExtensions.Tests/RestHttpClientTests.cs index cf3d171..c74527d 100644 --- a/src/Monq.Core.HttpClientExtensions.Tests/RestHttpClientTests.cs +++ b/src/Monq.Core.HttpClientExtensions.Tests/RestHttpClientTests.cs @@ -1,4 +1,4 @@ -using IdentityModel.Client; +using IdentityModel.Client; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; using Monq.Core.HttpClientExtensions.Exceptions; @@ -386,6 +386,31 @@ public async Task ShouldProperlyReuseHttpClientObjectInParallelWithHeaders() Assert.Equal(2 * totalRequests, results.Where(x => x.ResultObject is not null).SelectMany(x => x.ResultObject).Count()); } + [Fact(DisplayName = "Проверка установки Bearer token из HttpContextAccessor.")] + public void ShouldProperlySetBearerTokenFromRequest() + { + var httpContext = new DefaultHttpContext(); + httpContext.Request.Headers.Add("Authorization", "Bearer token355"); + + var client = new HttpClient(CreateDefaultResponseHandler(HttpStatusCode.Unauthorized, "{}")); + + var httpService = CreateRestHttpClient(client, httpContext); + + Assert.Equal("token355", client.DefaultRequestHeaders?.Authorization?.Parameter); + Assert.Equal("Bearer", client.DefaultRequestHeaders?.Authorization?.Scheme); + } + + [Fact(DisplayName = "Проверка НЕустановки Bearer token из HttpContextAccessor.")] + public void ShouldNotSetBearerTokenFromRequestIfBearerNotValid() + { + var httpContext = new DefaultHttpContext(); + httpContext.Request.Headers.Add("Authorization", "Bearetoken355"); + var client = new HttpClient(CreateDefaultResponseHandler(HttpStatusCode.Unauthorized, "{}")); + + var httpService = CreateRestHttpClient(client, httpContext); + Assert.Null(client.DefaultRequestHeaders.Authorization); + } + RestHttpClientMock CreateRestHttpClient(HttpClient httpClient, HttpContext? httpContext = null, RestHttpClientOptions? configuration = null) diff --git a/src/Monq.Core.HttpClientExtensions/RestHttpClientFromOptions.cs b/src/Monq.Core.HttpClientExtensions/RestHttpClientFromOptions.cs deleted file mode 100644 index 41ade1e..0000000 --- a/src/Monq.Core.HttpClientExtensions/RestHttpClientFromOptions.cs +++ /dev/null @@ -1,71 +0,0 @@ -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using System; -using System.Linq; -using System.Net.Http; - -namespace Monq.Core.HttpClientExtensions -{ - /// - /// Basic type of Http service that has a single access point in the form of BaseUri. - /// - public class RestHttpClientFromOptions : RestHttpClient - where TOptions : class, new() - { - /// - /// Base Uri Http service. - /// - protected string BaseUri { get; } - - /// - /// Initializes a new instance of the class. - /// - /// The HttpClient from http client factory. - /// The logger factory. - /// The configuration. - /// The HTTP context accessor. - /// The base Uri of all requests. For example: http://rsm.api.monq.cloud - public RestHttpClientFromOptions( - HttpClient httpClient, - ILoggerFactory loggerFactory, - RestHttpClientOptions configuration, - IHttpContextAccessor httpContextAccessor, - string baseUri) : base(httpClient, loggerFactory, configuration, httpContextAccessor) - { - if (string.IsNullOrWhiteSpace(baseUri)) - throw new ArgumentNullException(nameof(baseUri), "The base uri not set."); - BaseUri = AddTrailingSlash(baseUri); - - HttpClient.BaseAddress = new Uri(BaseUri); - } - - /// - /// Initializes a new instance of the class. - /// - /// The options. - /// The HttpClient from http client factory. - /// The logger factory. - /// The configuration. - /// The HTTP context accessor. - /// The base Uri of all requests. For example: http://rsm.api.monq.cloud - public RestHttpClientFromOptions( - IOptions optionsAccessor, - HttpClient httpClient, - ILoggerFactory loggerFactory, - RestHttpClientOptions configuration, - IHttpContextAccessor httpContextAccessor, - string baseUri) : this(httpClient, loggerFactory, configuration, httpContextAccessor, baseUri) - { - - } - - static string AddTrailingSlash(string baseUri) - { - if (baseUri.Last() != '/') - return baseUri + "/"; - - return baseUri; - } - } -} From 23a456b3687cace34549a16e81ae4b5ca83a0fa6 Mon Sep 17 00:00:00 2001 From: NazarovMikhail Date: Wed, 21 Jan 2026 17:23:55 +0500 Subject: [PATCH 2/5] =?UTF-8?q?=F0=9F=A7=B9=20Timeout=20setting.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Monq.Core.HttpClientExtensions/RestHttpClient.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/Monq.Core.HttpClientExtensions/RestHttpClient.cs b/src/Monq.Core.HttpClientExtensions/RestHttpClient.cs index 0f9dcf7..0e1928d 100644 --- a/src/Monq.Core.HttpClientExtensions/RestHttpClient.cs +++ b/src/Monq.Core.HttpClientExtensions/RestHttpClient.cs @@ -103,11 +103,6 @@ public RestHttpClient(HttpClient httpClient, _log = loggerFactory.CreateLogger(); - // To reuse the HttpClient instance, we will use the cancellation token to manage timeouts. - // To do this, you need to set the main timeout to the maximum value, - // because it will override the value specified in the cancellation token. - _httpClient.Timeout = System.Threading.Timeout.InfiniteTimeSpan; - if (HttpContextAccessor?.HttpContext is not null && HttpContextAccessor.HttpContext.Request.Headers.TryGetValue(AuthorizationHeader, out var authorizeHeader) && !string.IsNullOrEmpty(authorizeHeader)) From 750b6bb312169619953d652b253a58c6e4bdd61d Mon Sep 17 00:00:00 2001 From: NazarovMikhail Date: Thu, 22 Jan 2026 12:09:25 +0500 Subject: [PATCH 3/5] =?UTF-8?q?=F0=9F=94=96v6.0.3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Monq.Core.HttpClientExtensions.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Monq.Core.HttpClientExtensions/Monq.Core.HttpClientExtensions.csproj b/src/Monq.Core.HttpClientExtensions/Monq.Core.HttpClientExtensions.csproj index fc16864..30d4882 100644 --- a/src/Monq.Core.HttpClientExtensions/Monq.Core.HttpClientExtensions.csproj +++ b/src/Monq.Core.HttpClientExtensions/Monq.Core.HttpClientExtensions.csproj @@ -1,7 +1,7 @@  - 6.0.2 + 6.0.3 $(VersionSuffix) $(Version)-$(VersionSuffix) net6.0;net7.0;net8.0;net9.0;net10.0 From 0c6f4d07b205c37579252f8a2467e4534923f99b Mon Sep 17 00:00:00 2001 From: NazarovMikhail Date: Thu, 22 Jan 2026 12:13:58 +0500 Subject: [PATCH 4/5] =?UTF-8?q?=F0=9F=94=96v6.1.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Monq.Core.HttpClientExtensions.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Monq.Core.HttpClientExtensions/Monq.Core.HttpClientExtensions.csproj b/src/Monq.Core.HttpClientExtensions/Monq.Core.HttpClientExtensions.csproj index 30d4882..1ed7a47 100644 --- a/src/Monq.Core.HttpClientExtensions/Monq.Core.HttpClientExtensions.csproj +++ b/src/Monq.Core.HttpClientExtensions/Monq.Core.HttpClientExtensions.csproj @@ -1,7 +1,7 @@  - 6.0.3 + 6.1.0 $(VersionSuffix) $(Version)-$(VersionSuffix) net6.0;net7.0;net8.0;net9.0;net10.0 From 0d7a5064c7d0a3e898338013386bdd1aaeb3c5a0 Mon Sep 17 00:00:00 2001 From: 2nnar Date: Tue, 27 Jan 2026 13:25:55 +0300 Subject: [PATCH 5/5] =?UTF-8?q?=F0=9F=94=96=207.0.0,=20code=20cleanup,=20u?= =?UTF-8?q?pdate=20docs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .editorconfig | 1 + README.md | 122 +-- .../Controllers/ValuesController.cs | 31 +- .../Program.cs | 74 +- .../ServiceUriOptions.cs | 9 +- .../Startup.cs | 57 -- .../TestModel.cs | 15 +- .../TestService.cs | 58 +- .../Program.cs | 73 +- .../ServiceUriOptions.cs | 9 +- .../TestModel.cs | 15 +- .../TestService.cs | 73 +- .../Models/RestHttpClientMock.cs | 32 +- .../Models/Service.cs | 31 +- .../Models/ServiceOptions.cs | 7 - .../RestHttpClientTests.cs | 703 +++++++++--------- .../Stubs/HttpContextAccessorStub.cs | 17 +- .../Stubs/StubLogger.cs | 59 +- .../Stubs/StubLoggerFactory.cs | 55 +- .../Configuration/AuthConstants.cs | 2 +- .../Configuration/RestHttpClientOptions.cs | 4 +- .../MissingConfigurationException.cs | 3 +- .../Extensions/HostBuilderExtensions.cs | 2 +- .../Extensions/HttpClientExtensions.cs | 6 +- .../Monq.Core.HttpClientExtensions.csproj | 2 +- 25 files changed, 700 insertions(+), 760 deletions(-) delete mode 100644 samples/Monq.Core.HttpClientExtensions.TestApp/Startup.cs delete mode 100644 src/Monq.Core.HttpClientExtensions.Tests/Models/ServiceOptions.cs diff --git a/.editorconfig b/.editorconfig index 9e591c0..9362d49 100644 --- a/.editorconfig +++ b/.editorconfig @@ -295,4 +295,5 @@ dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:sil dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent dotnet_style_require_accessibility_modifiers = omit_if_default:suggestion dotnet_diagnostic.CA1851.severity = none +dotnet_diagnostic.CA1873.severity = none resharper_possible_multiple_enumeration_highlighting = none \ No newline at end of file diff --git a/README.md b/README.md index 458acfd..9abf13a 100644 --- a/README.md +++ b/README.md @@ -145,22 +145,14 @@ Objective: create a service for executing HTTP requests via REST interface in JS To solve this problem, you need to create an interface, implement this interface in a class, and connect the interface and implementation in DI. In this case, the interface makes it easy to unit test the service that uses the interface. -*ServiceUriOptions.cs* -```csharp -public class ServiceUriOptions -{ - public string ServiceUri { get; set; } = default!; -} -``` - *RemoteServiceModel.cs* ```csharp public class RemoteServiceModel { public int UserId { get; set; } public int Id { get; set; } - public string Title { get; set; } = default!; - public string Body { get; set; } = default!; + public string Title { get; set; } + public string Body { get; set; } } ``` @@ -172,34 +164,31 @@ public interface IRemoteServiceApiHttpService } ``` -The interface implementation must inherit from the class `RestHttpClient` or from `RestHttpClientFromOptions`. - -`RestHttpClientFromOptions` is a base class that provides an out-of-the-box `BaseUri` injection mechanism for `HttpClient`. - -`TOptions` is a class that is used to read settings from `asppsettings.json` for base addresses of services and is injected into `ServiceCollection` as `IOptions` +The interface implementation must inherit from the class `RestHttpClient`. Class implementation: ```csharp -public class DefaultRemoteServiceApiHttpService : RestHttpClientFromOptions, IRemoteServiceApiHttpService +public class DefaultRemoteServiceApiHttpService : RestHttpClient, IRemoteServiceApiHttpService { - public DefaultRemoteServiceApiHttpService(IOptions optionsAccessor, - HttpClient httpClient, - ILoggerFactory loggerFactory, - RestHttpClientOptions configuration, - IHttpContextAccessor httpContextAccessor) - : base(optionsAccessor, - httpClient, - loggerFactory, - configuration, - httpContextAccessor, - optionsAccessor.Value.ServiceUri) + + public DefaultRemoteServiceApiHttpService( + HttpClient httpClient, + ILoggerFactory loggerFactory, + RestHttpClientOptions configuration, + IHttpContextAccessor httpContextAccessor) + : base( + httpClient, + loggerFactory, + configuration, + httpContextAccessor) { + _baseUri = optionsAccessor.Value.ServiceUri; } public async Task> GetAllInstances() { - var uri = "api/instances"; + const string uri = "api/instances"; var result = await Get>(uri, TimeSpan.FromSeconds(10)); return result.ResultObject; @@ -207,7 +196,7 @@ public class DefaultRemoteServiceApiHttpService : RestHttpClientFromOptions()` method. +Such services are implemented via DI as HttpClient services and they must be added to DI over `AddHttpClient<>()` method. ```csharp public class Startup @@ -216,9 +205,12 @@ public class Startup { .... services.AddOptions(); - services.Configure(Configuration.GetSection("Services")); - services.AddHttpClient(); + services.AddHttpClient(x => + { + x.BaseAddress = new("service/uri"); + x.Timeout = TimeSpan.FromHours(1); + }); } } ``` @@ -226,22 +218,21 @@ public class Startup If you need to get access to other instances from the `ServiceCollection` collection inside the http service, then classic dependency injection is implemented. ```csharp -public class CachedRemoteServiceApiHttpService : RestHttpClientFromOptions, IRemoteServiceApiHttpService +public class CachedRemoteServiceApiHttpService : RestHttpClient, IRemoteServiceApiHttpService { readonly IMemoryCache _memoryCache; - public CachedRemoteServiceApiHttpService(IOptions optionsAccessor, - HttpClient httpClient, - ILoggerFactory loggerFactory, - RestHttpClientOptions configuration, - IHttpContextAccessor httpContextAccessor - IMemoryCache memoryCache) - : base(optionsAccessor, - httpClient, - loggerFactory, - configuration, - httpContextAccessor, - optionsAccessor.Value.ServiceUri) + public CachedRemoteServiceApiHttpService( + HttpClient httpClient, + ILoggerFactory loggerFactory, + RestHttpClientOptions configuration, + IHttpContextAccessor httpContextAccessor + IMemoryCache memoryCache) + : base( + httpClient, + loggerFactory, + configuration, + httpContextAccessor) { _memoryCache = memoryCache; } @@ -261,7 +252,7 @@ ILogger log ```csharp public async Task> GetAllInstances() { - var uri = "api/instances"; + const string uri = "api/instances"; var result = await Get>(uri, TimeSpan.FromSeconds(10)); return result; @@ -276,7 +267,7 @@ public async Task> FilterInsta if (filter is null || filter.Prop is null) return RestHttpResponseMessageWrapper.Empty>(); // using the response wrapper. - var uri = "api/instances"; + const string uri = "api/instances"; var result = await Get>(uri, TimeSpan.FromSeconds(10)); return result; @@ -329,17 +320,10 @@ Http services inherited from this class are easy to test. public class DefaultRemoteServiceApiHttpServiceTests { readonly ILogger _logger; - readonly Mock> _serviceUriOptionsMock; public DefaultRemoteServiceApiHttpServiceTests() { _logger = new StubLogger(); - - _serviceUriOptionsMock = new Mock>(); - _serviceUriOptionsMock.Setup(x => x.Value) - .Returns(new ServiceUriOptions() { - ServiceUri = "https://jsonplaceholder.typicode.com" - }); } [Fact] @@ -369,14 +353,13 @@ public class DefaultRemoteServiceApiHttpServiceTests Assert.Equal(model.UserId, firstInstance.UserId); } - DefaultRemoteServiceApiHttpService CreateApiService(HttpClient httpClient, HttpContext? httpContext, IOptions optionsAccessor) + DefaultRemoteServiceApiHttpService CreateApiService(HttpClient httpClient, HttpContext? httpContext = null) { - return new DefaultRemoteServiceApiHttpService(optionsAccessor ?? _optionsMoq.Object, - httpClient, - _loggerFactory, - null, - new HttpContextAccessorStub(httpContext ?? new DefaultHttpContext()), - optionsAccessor.Value.ServiceUri); + return new DefaultRemoteServiceApiHttpService( + httpClient, + _loggerFactory, + null, + new HttpContextAccessorStub(httpContext ?? new DefaultHttpContext())); } } ``` @@ -396,4 +379,21 @@ In the v5 the library was changed a lot. So you must follow migration steps. 9. In the class methods remove remove change all strings `client.Get()` or `client.Post()` and others to just `Get()` or `Post()`. 10. In the Startup.cs change `services.AddTransient()` to `servicese.AddHttpClient()` for all http services inherited from the `RestHttpClient` and `RestHttpClientFromOptions`. 11. In the Startup.cs change `services.AddScoped()` to `servicese.AddHttpClient()` for all http services inherited from the `RestHttpClient` and `RestHttpClientFromOptions`. -12. Change all unit tests to the new version described in the [Testing features](#testing-features). \ No newline at end of file +12. Change all unit tests to the new version described in the [Testing features](#testing-features). + +### Migration Guide to v7 + +1. Replace `RestHttpClientFromOptions` with `RestHttpClient` and remove unnecessary injections in implementation class constructor. +2. Move your underlying `HttpClient` configuration to `.AddHttpClient<>()` method. + +```c# +services.AddHttpClient((serviceProvider, client) => +{ + // Get base uri from application options. + var baseUri = serviceProvider.GetRequiredService>().Value.BaseUri; + client.BaseAddress = new(baseUri); + + // Override default HTTP client timeout (100 sec). + client.Timeout = System.Threading.Timeout.InfiniteTimeSpan; +}) +``` diff --git a/samples/Monq.Core.HttpClientExtensions.TestApp/Controllers/ValuesController.cs b/samples/Monq.Core.HttpClientExtensions.TestApp/Controllers/ValuesController.cs index 9398ed5..6751d78 100644 --- a/samples/Monq.Core.HttpClientExtensions.TestApp/Controllers/ValuesController.cs +++ b/samples/Monq.Core.HttpClientExtensions.TestApp/Controllers/ValuesController.cs @@ -1,24 +1,23 @@ -using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; +using System.Threading.Tasks; -namespace Monq.Core.HttpClientExtensions.TestApp.Controllers +namespace Monq.Core.HttpClientExtensions.TestApp.Controllers; + +[Route("api/test")] +public class TestController : Controller { - [Route("api/test")] - public class TestController : Controller - { - readonly ITestService _service; + readonly ITestService _service; - public TestController(ITestService service) - { - _service = service; - } + public TestController(ITestService service) + { + _service = service; + } - [HttpGet] - public async Task Get() - { - var result = await _service.TestApi(); + [HttpGet] + public async Task Get() + { + var result = await _service.TestApi(); - return Ok(result); - } + return Ok(result); } } diff --git a/samples/Monq.Core.HttpClientExtensions.TestApp/Program.cs b/samples/Monq.Core.HttpClientExtensions.TestApp/Program.cs index c9f72ae..8f18947 100644 --- a/samples/Monq.Core.HttpClientExtensions.TestApp/Program.cs +++ b/samples/Monq.Core.HttpClientExtensions.TestApp/Program.cs @@ -1,25 +1,57 @@ -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Hosting; +using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Options; +using Monq.Core.HttpClientExtensions; +using Monq.Core.HttpClientExtensions.TestApp; +using Polly; +using Polly.Extensions.Http; +using System; +using System.Net.Http; +using System.Text; -namespace Monq.Core.HttpClientExtensions.TestApp -{ - public class Program +var builder = WebApplication.CreateBuilder(args); +Console.OutputEncoding = Encoding.UTF8; + +builder.Host + .ConfigBasicHttpService(opts => + { + var headerOptions = new RestHttpClientHeaderOptions(); + headerOptions.AddForwardedHeader("X-Trace-Event-Id"); + headerOptions.AddForwardedHeader("Accept-Language"); + opts.ConfigHeaders(headerOptions); + }); + +builder.Services.AddOptions(); +builder.Services.AddHttpContextAccessor(); +builder.Services.AddLogging(); +builder.Services.Configure(x => x.TestServiceUri = "https://jsonplaceholder.typicode.com"); + +builder.Services + .AddHttpClient((serviceProvider, client) => { - public static void Main(string[] args) - { - CreateHostBuilder(args).Build().Run(); - } - - public static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) - .ConfigBasicHttpService(opts => - { - var headerOptions = new RestHttpClientHeaderOptions(); - headerOptions.AddForwardedHeader("X-Trace-Event-Id"); - headerOptions.AddForwardedHeader("Accept-Language"); - opts.ConfigHeaders(headerOptions); - }) - .ConfigureWebHostDefaults(webBuilder => webBuilder.UseStartup()); - } + var baseUri = serviceProvider.GetRequiredService>().Value.TestServiceUri; + client.BaseAddress = new(baseUri); + }) + .AddPolicyHandler(GetCircuitBreakerPolicy()); + +builder.Services + .AddControllers() + .AddJsonOptions(options => options.JsonSerializerOptions.PropertyNameCaseInsensitive = true); + +var app = builder.Build(); + +if (app.Environment.IsDevelopment()) + app.UseDeveloperExceptionPage(); + +app.UseRouting(); +app.MapControllers(); + +await app.RunAsync(); + +static IAsyncPolicy GetCircuitBreakerPolicy() +{ + return HttpPolicyExtensions + .HandleTransientHttpError() + .CircuitBreakerAsync(2, TimeSpan.FromSeconds(30)); } diff --git a/samples/Monq.Core.HttpClientExtensions.TestApp/ServiceUriOptions.cs b/samples/Monq.Core.HttpClientExtensions.TestApp/ServiceUriOptions.cs index 04112a9..c928d1f 100644 --- a/samples/Monq.Core.HttpClientExtensions.TestApp/ServiceUriOptions.cs +++ b/samples/Monq.Core.HttpClientExtensions.TestApp/ServiceUriOptions.cs @@ -1,7 +1,6 @@ -namespace Monq.Core.HttpClientExtensions.TestApp +namespace Monq.Core.HttpClientExtensions.TestApp; + +public class ServiceUriOptions { - public class ServiceUriOptions - { - public string TestServiceUri { get; set; } - } + public string TestServiceUri { get; set; } } diff --git a/samples/Monq.Core.HttpClientExtensions.TestApp/Startup.cs b/samples/Monq.Core.HttpClientExtensions.TestApp/Startup.cs deleted file mode 100644 index 6ff6859..0000000 --- a/samples/Monq.Core.HttpClientExtensions.TestApp/Startup.cs +++ /dev/null @@ -1,57 +0,0 @@ -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using Polly; -using Polly.Extensions.Http; -using System; -using System.Net.Http; - -namespace Monq.Core.HttpClientExtensions.TestApp -{ - public class Startup - { - public Startup(IConfiguration configuration) - { - Configuration = configuration; - } - - public IConfiguration Configuration { get; } - - // This method gets called by the runtime. Use this method to add services to the container. - public void ConfigureServices(IServiceCollection services) - { - services.AddOptions(); - services.AddHttpContextAccessor(); - services.AddLogging(); - services.Configure(x => x.TestServiceUri = "https://jsonplaceholder.typicode.com"); - - services.AddHttpClient() - .AddPolicyHandler(GetCircuitBreakerPolicy()); - - services.AddControllers() - .AddJsonOptions(options => - { - options.JsonSerializerOptions.PropertyNameCaseInsensitive = true; - }); - } - - // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure(IApplicationBuilder app, IWebHostEnvironment env) - { - if (env.IsDevelopment()) - app.UseDeveloperExceptionPage(); - - app.UseRouting(); - app.UseEndpoints(e => e.MapControllers()); - } - - static IAsyncPolicy GetCircuitBreakerPolicy() - { - return HttpPolicyExtensions - .HandleTransientHttpError() - .CircuitBreakerAsync(2, TimeSpan.FromSeconds(30)); - } - } -} diff --git a/samples/Monq.Core.HttpClientExtensions.TestApp/TestModel.cs b/samples/Monq.Core.HttpClientExtensions.TestApp/TestModel.cs index 7762c5c..6bda002 100644 --- a/samples/Monq.Core.HttpClientExtensions.TestApp/TestModel.cs +++ b/samples/Monq.Core.HttpClientExtensions.TestApp/TestModel.cs @@ -1,10 +1,9 @@ -namespace Monq.Core.HttpClientExtensions.TestApp +namespace Monq.Core.HttpClientExtensions.TestApp; + +public class TestModel { - public class TestModel - { - public int UserId { get; set; } - public int Id { get; set; } - public string Title { get; set; } = default!; - public string Body { get; set; } = default!; - } + public int UserId { get; set; } + public int Id { get; set; } + public string Title { get; set; } = default!; + public string Body { get; set; } = default!; } diff --git a/samples/Monq.Core.HttpClientExtensions.TestApp/TestService.cs b/samples/Monq.Core.HttpClientExtensions.TestApp/TestService.cs index 8098b4f..9aedb79 100644 --- a/samples/Monq.Core.HttpClientExtensions.TestApp/TestService.cs +++ b/samples/Monq.Core.HttpClientExtensions.TestApp/TestService.cs @@ -1,47 +1,39 @@ using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; using System; using System.Net.Http; using System.Threading.Tasks; -namespace Monq.Core.HttpClientExtensions.TestApp +namespace Monq.Core.HttpClientExtensions.TestApp; + +public interface ITestService { - public interface ITestService + Task TestApi(); +} + +public class TestService : RestHttpClient, ITestService +{ + /// + /// Инициализирует новый экземпляр класса . + /// + public TestService( + HttpClient httpClient, + ILoggerFactory loggerFactory, + RestHttpClientOptions configuration, + IHttpContextAccessor httpContextAccessor) + : base( + httpClient, + loggerFactory, + configuration, + httpContextAccessor) { - Task TestApi(); } - public class TestService : RestHttpClient, ITestService + public async Task TestApi() { - /// - /// Инициализирует новый экземпляр класса . - /// - /// The options. - /// The logger factory. - /// The configuration. - /// The HTTP context accessor. - /// The HTTP message invoker. - /// baseUri - Не указан базовый Uri сервиса. - public TestService( - IOptions optionsAccessor, - HttpClient httpClient, - ILoggerFactory loggerFactory, - RestHttpClientOptions configuration, - IHttpContextAccessor httpContextAccessor) : - base(httpClient, - loggerFactory, - configuration, - httpContextAccessor) - { - } - - public async Task TestApi() - { - var result = await Get("posts/1", TimeSpan.FromSeconds(10), - serializer: RestHttpClientSystemTextJsonSerializer.Default); + var result = await Get("posts/1", TimeSpan.FromSeconds(10), + serializer: RestHttpClientSystemTextJsonSerializer.Default); - return result.ResultObject!; - } + return result.ResultObject!; } } diff --git a/samples/Monq.Core.HttpClientExtensions.TestConsoleApp/Program.cs b/samples/Monq.Core.HttpClientExtensions.TestConsoleApp/Program.cs index 4f59a43..ea9dea0 100644 --- a/samples/Monq.Core.HttpClientExtensions.TestConsoleApp/Program.cs +++ b/samples/Monq.Core.HttpClientExtensions.TestConsoleApp/Program.cs @@ -1,55 +1,50 @@ -using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; using Monq.Core.HttpClientExtensions.TestApp; +using Monq.Core.HttpClientExtensions.TestConsoleApp; using System; using System.Linq; using System.Text; using System.Threading.Tasks; -namespace Monq.Core.HttpClientExtensions.TestConsoleApp -{ - class Program - { - static readonly DefaultHttpContext _httpContext = new DefaultHttpContext(); - - static async Task Main(string[] args) - { - Console.OutputEncoding = Encoding.UTF8; +Console.OutputEncoding = Encoding.UTF8; - _httpContext.Request.Headers.Add("X-C", "-1"); +var httpContext = new DefaultHttpContext(); +httpContext.Request.Headers.Append("X-C", "-1"); - var hostBuilder = new HostBuilder() - .ConfigBasicHttpService() - .ConfigureServices((host, services) => - { - services.AddHttpContextAccessor(); - services.Configure(x => x.TestServiceUri = "https://jsonplaceholder.typicode.com"); - - services.AddHttpClient(); - }) - .ConfigureLogging((host, log) => { log.SetMinimumLevel(LogLevel.Trace); log.AddConsole(); }) - .Build(); +using var host = Host.CreateDefaultBuilder(args) + .ConfigBasicHttpService() + .ConfigureServices((builder, services) => + { + services.AddHttpContextAccessor(); + services.Configure(x => x.TestServiceUri = "https://jsonplaceholder.typicode.com"); - var httpContextAccessor = hostBuilder.Services.GetRequiredService(); - httpContextAccessor.HttpContext = _httpContext; + services.AddSingleton(_ => new HttpContextAccessor { HttpContext = httpContext }); - var tasks = Enumerable.Range(1, 5).Select(i => - { - var scopeFactory = hostBuilder.Services.GetRequiredService(); - return ExecuteService(scopeFactory, i.ToString()); - }); + services.AddHttpClient((serviceProvider, client) => + { + var baseUri = serviceProvider.GetRequiredService>().Value.TestServiceUri; + client.BaseAddress = new(baseUri); + }); + }) + .ConfigureLogging((builder, log) => { log.SetMinimumLevel(LogLevel.Trace); log.AddConsole(); }) + .Build(); + +var tasks = Enumerable.Range(1, 5).Select(i => +{ + var scopeFactory = host.Services.GetRequiredService(); + return ExecuteService(scopeFactory, i.ToString()); +}); - await Task.WhenAll(tasks); - } +await Task.WhenAll(tasks); - static async Task ExecuteService(IServiceScopeFactory scopeFactory, string auth) - { - using var scope = scopeFactory.CreateScope(); - var service = scope.ServiceProvider.GetRequiredService(); - await service.TestApi(auth); - await service.TestApi(auth); - } - } +static async Task ExecuteService(IServiceScopeFactory scopeFactory, string auth) +{ + using var scope = scopeFactory.CreateScope(); + var service = scope.ServiceProvider.GetRequiredService(); + await service.TestApi(auth); + await service.TestApi(auth); } diff --git a/samples/Monq.Core.HttpClientExtensions.TestConsoleApp/ServiceUriOptions.cs b/samples/Monq.Core.HttpClientExtensions.TestConsoleApp/ServiceUriOptions.cs index 04112a9..c928d1f 100644 --- a/samples/Monq.Core.HttpClientExtensions.TestConsoleApp/ServiceUriOptions.cs +++ b/samples/Monq.Core.HttpClientExtensions.TestConsoleApp/ServiceUriOptions.cs @@ -1,7 +1,6 @@ -namespace Monq.Core.HttpClientExtensions.TestApp +namespace Monq.Core.HttpClientExtensions.TestApp; + +public class ServiceUriOptions { - public class ServiceUriOptions - { - public string TestServiceUri { get; set; } - } + public string TestServiceUri { get; set; } } diff --git a/samples/Monq.Core.HttpClientExtensions.TestConsoleApp/TestModel.cs b/samples/Monq.Core.HttpClientExtensions.TestConsoleApp/TestModel.cs index 1ed2f82..ab7ceed 100644 --- a/samples/Monq.Core.HttpClientExtensions.TestConsoleApp/TestModel.cs +++ b/samples/Monq.Core.HttpClientExtensions.TestConsoleApp/TestModel.cs @@ -1,10 +1,9 @@ -namespace Monq.Core.HttpClientExtensions.TestConsoleApp +namespace Monq.Core.HttpClientExtensions.TestConsoleApp; + +public class TestModel { - public class TestModel - { - public int UserId { get; set; } - public int Id { get; set; } - public string Title { get; set; } = default!; - public string Body { get; set; } = default!; - } + public int UserId { get; set; } + public int Id { get; set; } + public string Title { get; set; } = default!; + public string Body { get; set; } = default!; } diff --git a/samples/Monq.Core.HttpClientExtensions.TestConsoleApp/TestService.cs b/samples/Monq.Core.HttpClientExtensions.TestConsoleApp/TestService.cs index 23b432d..4b756fb 100644 --- a/samples/Monq.Core.HttpClientExtensions.TestConsoleApp/TestService.cs +++ b/samples/Monq.Core.HttpClientExtensions.TestConsoleApp/TestService.cs @@ -1,58 +1,49 @@ using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using Monq.Core.HttpClientExtensions.TestApp; using System; using System.Net.Http; -using System.Net.Http.Headers; +using System.Text.Json; using System.Threading.Tasks; -namespace Monq.Core.HttpClientExtensions.TestConsoleApp +namespace Monq.Core.HttpClientExtensions.TestConsoleApp; + +public interface ITestService +{ + Task TestApi(string auth); +} + +public class TestService : RestHttpClient, ITestService { - public interface ITestService + readonly ILogger _logger; + + /// + /// Инициализирует новый экземпляр класса . + /// + public TestService( + HttpClient httpClient, + ILoggerFactory loggerFactory, + RestHttpClientOptions configuration, + IHttpContextAccessor httpContextAccessor) + : base( + httpClient, + loggerFactory, + configuration, + httpContextAccessor) { - Task TestApi(string auth); + _logger = loggerFactory.CreateLogger(); } - public class TestService : RestHttpClient, ITestService + public async Task TestApi(string auth) { - ILogger _log; - - /// - /// Инициализирует новый экземпляр класса . - /// - /// The options. - /// The logger factory. - /// The configuration. - /// The HTTP context accessor. - /// The HTTP message invoker. - /// baseUri - Не указан базовый Uri сервиса. - public TestService( - IOptions optionsAccessor, - HttpClient httpClient, - ILoggerFactory loggerFactory, - RestHttpClientOptions configuration, - IHttpContextAccessor httpContextAccessor) : - base(httpClient, - loggerFactory, - configuration, - httpContextAccessor) - { - _log = loggerFactory.CreateLogger(); - } + _logger.LogInformation(JsonSerializer.Serialize(HttpClient.DefaultRequestHeaders)); - public async Task TestApi(string auth) + var headers = new HeaderDictionary { - //HttpClient.DefaultRequestHeaders.TryAddWithoutValidation("Authorization", $"Bearer {auth}"); - - _log.LogInformation(System.Text.Json.JsonSerializer.Serialize(HttpClient.DefaultRequestHeaders)); - - var headers = new HeaderDictionary(); - headers.Add("Authorization", $"Bearer {auth}"); + { "Authorization", $"Bearer {auth}" } + }; - var result = await Get("posts/1", TimeSpan.FromSeconds(10), headers); + var result = await Get("posts/1", TimeSpan.FromSeconds(10), headers); - return result.ResultObject!; - } + return result.ResultObject!; } } diff --git a/src/Monq.Core.HttpClientExtensions.Tests/Models/RestHttpClientMock.cs b/src/Monq.Core.HttpClientExtensions.Tests/Models/RestHttpClientMock.cs index 48c9b37..260365e 100644 --- a/src/Monq.Core.HttpClientExtensions.Tests/Models/RestHttpClientMock.cs +++ b/src/Monq.Core.HttpClientExtensions.Tests/Models/RestHttpClientMock.cs @@ -1,26 +1,24 @@ using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; using System.Net.Http; -namespace Monq.Core.HttpClientExtensions.Tests.Models +namespace Monq.Core.HttpClientExtensions.Tests.Models; + +public class RestHttpClientMock : RestHttpClient { - public class RestHttpClientMock : RestHttpClient + /// + /// Initializes a new instance of the class. + /// + /// The HttpClient from http client factory. + /// The logger factory. + /// The configuration. + /// The HTTP context accessor. + public RestHttpClientMock(HttpClient httpClient, + ILoggerFactory loggerFactory, + RestHttpClientOptions configuration, + IHttpContextAccessor httpContextAccessor) + : base(httpClient, loggerFactory, configuration, httpContextAccessor) { - /// - /// Initializes a new instance of the class. - /// - /// The HttpClient from http client factory. - /// The logger factory. - /// The configuration. - /// The HTTP context accessor. - public RestHttpClientMock(HttpClient httpClient, - ILoggerFactory loggerFactory, - RestHttpClientOptions configuration, - IHttpContextAccessor httpContextAccessor) - : base(httpClient, loggerFactory, configuration, httpContextAccessor) - { - } } } diff --git a/src/Monq.Core.HttpClientExtensions.Tests/Models/Service.cs b/src/Monq.Core.HttpClientExtensions.Tests/Models/Service.cs index 51e7124..d09c638 100644 --- a/src/Monq.Core.HttpClientExtensions.Tests/Models/Service.cs +++ b/src/Monq.Core.HttpClientExtensions.Tests/Models/Service.cs @@ -1,23 +1,22 @@ -namespace Monq.Core.HttpClientExtensions.Tests.Models +namespace Monq.Core.HttpClientExtensions.Tests.Models; + +public class Service { - public class Service - { - public int Id { get; set; } - public string? Name { get; set; } + public int Id { get; set; } + public string? Name { get; set; } - public override bool Equals(object? obj) + public override bool Equals(object? obj) + { + if (obj is Service s) { - if (obj is Service s) - { - return s.Id == Id && s.Name == Name; - } - - return false; + return s.Id == Id && s.Name == Name; } - public override int GetHashCode() - { - return Id.GetHashCode() | Name.GetHashCode(); - } + return false; + } + + public override int GetHashCode() + { + return Id.GetHashCode() | Name.GetHashCode(); } } diff --git a/src/Monq.Core.HttpClientExtensions.Tests/Models/ServiceOptions.cs b/src/Monq.Core.HttpClientExtensions.Tests/Models/ServiceOptions.cs deleted file mode 100644 index 6c41316..0000000 --- a/src/Monq.Core.HttpClientExtensions.Tests/Models/ServiceOptions.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Monq.Core.HttpClientExtensions.Tests.Models -{ - public class ServiceOptions - { - public string ServiceUri { get; set; } = default!; - } -} diff --git a/src/Monq.Core.HttpClientExtensions.Tests/RestHttpClientTests.cs b/src/Monq.Core.HttpClientExtensions.Tests/RestHttpClientTests.cs index c74527d..4c9a84a 100644 --- a/src/Monq.Core.HttpClientExtensions.Tests/RestHttpClientTests.cs +++ b/src/Monq.Core.HttpClientExtensions.Tests/RestHttpClientTests.cs @@ -17,424 +17,429 @@ using System.Threading.Tasks; using Xunit; -namespace Monq.Core.HttpClientExtensions.Tests +namespace Monq.Core.HttpClientExtensions.Tests; + +public class RestHttpClientTests { - public class RestHttpClientTests - { - static readonly CamelCasePropertyNamesContractResolver _jsonResolver = new CamelCasePropertyNamesContractResolver { NamingStrategy = new CamelCaseNamingStrategy { ProcessDictionaryKeys = true } }; + static readonly CamelCasePropertyNamesContractResolver _jsonResolver = new() { NamingStrategy = new CamelCaseNamingStrategy { ProcessDictionaryKeys = true } }; - const string TraceEventIdHeader = "X-Trace-Event-Id"; - const string UserspaceIdHeader = "X-Smon-Userspace-Id"; + const string TraceEventIdHeader = "X-Trace-Event-Id"; + const string UserspaceIdHeader = "X-Smon-Userspace-Id"; - readonly IList _loggers = new List(); - readonly ILoggerFactory _loggerFactory; - readonly RestHttpClientOptions _configuration = new RestHttpClientOptions(); + readonly IList _loggers = []; + readonly ILoggerFactory _loggerFactory; + readonly RestHttpClientOptions _configuration = new(); - public RestHttpClientTests() - { - _loggerFactory = new StubLoggerFactory(_loggers); - RestHttpClient.ResetAccessToken(); - RestHttpClient.ResetAuthorizationRequestHandler(); + public RestHttpClientTests() + { + _loggerFactory = new StubLoggerFactory(_loggers); + RestHttpClient.ResetAccessToken(); + RestHttpClient.ResetAuthorizationRequestHandler(); - _configuration.ConfigHeaders(new RestHttpClientHeaderOptions() - { - ForwardedHeaders = new HashSet() { TraceEventIdHeader, UserspaceIdHeader } - }); - } + _configuration.ConfigHeaders(new RestHttpClientHeaderOptions() + { + ForwardedHeaders = [TraceEventIdHeader, UserspaceIdHeader] + }); + } - [Fact(DisplayName = "Выполнить GET запрос по абсолютному URI.")] - public async Task ShouldProperlyMakeGetRequestByAbsoluteUri() + [Fact(DisplayName = "Выполнить GET запрос по абсолютному URI.")] + public async Task ShouldProperlyMakeGetRequestByAbsoluteUri() + { + var modelJson = JsonConvert.SerializeObject(new Service[] { - var modelJson = JsonConvert.SerializeObject(new Service[] { - new Service { Id = 1, Name = "Service1" }, - new Service { Id = 2, Name = "Service2" } - }); + new() { Id = 1, Name = "Service1" }, + new() { Id = 2, Name = "Service2" } + }); - const string uri = "http://unittest/api/services"; + const string uri = "http://unittest/api/services"; - var client = new HttpClient(CreateDefaultResponseHandler(HttpStatusCode.OK, modelJson)); - var restHttpClient = CreateRestHttpClient(client); + var client = new HttpClient(CreateDefaultResponseHandler(HttpStatusCode.OK, modelJson)); + var restHttpClient = CreateRestHttpClient(client); - var response = await restHttpClient.Get>(uri, TimeSpan.FromSeconds(10)); + var response = await restHttpClient.Get>(uri, TimeSpan.FromSeconds(10)); - var services = response.ResultObject?.ToList(); + var services = response.ResultObject?.ToList(); - Assert.NotNull(services); - Assert.Equal(2, services!.Count); - Assert.Equal(1, services[0].Id); - Assert.Equal("Service1", services[0].Name); - Assert.Equal(2, services[1].Id); - Assert.Equal("Service2", services[1].Name); + Assert.NotNull(services); + Assert.Equal(2, services!.Count); + Assert.Equal(1, services[0].Id); + Assert.Equal("Service1", services[0].Name); + Assert.Equal(2, services[1].Id); + Assert.Equal("Service2", services[1].Name); - var logger = _loggers.First(); + var logger = _loggers.First(); - Assert.Equal(2, logger.LoggingEvents.Count); - Assert.Equal($"Start downstream request GET {uri} with http forwarded headers=.", logger.LoggingEvents[0]); - Assert.Contains($"Downstream request GET {uri} with http forwarded headers= finished with StatusCode {(int)HttpStatusCode.OK} at", logger.LoggingEvents[1]); - } + Assert.Equal(2, logger.LoggingEvents.Count); + Assert.Equal($"Start downstream request GET {uri} with http forwarded headers=.", logger.LoggingEvents[0]); + Assert.Contains($"Downstream request GET {uri} with http forwarded headers= finished with StatusCode {(int)HttpStatusCode.OK} at", logger.LoggingEvents[1]); + } - [Fact(DisplayName = "Выполнить GET запрос по абсолютному URI с пробросом заголовков.", Skip = "DefaultRequestHeaders is not using now to send headers.")] - public async Task ShouldProperlyMakeGetRequestByAbsoluteUriWithForwardedHeaders() - { - var modelJson = JsonConvert.SerializeObject(new Service[] { - new Service { Id = 1, Name = "Service1" }, - new Service { Id = 2, Name = "Service2" } - }); - var traceEventId = Guid.NewGuid().ToString(); - const string userspaceId = "10"; - const string uri = "http://unittest/api/services"; - - var httpContext = new DefaultHttpContext(); - httpContext.Request.Headers.Add(TraceEventIdHeader, traceEventId); - httpContext.Request.Headers.Add(UserspaceIdHeader, userspaceId); - - var headerOptions = new RestHttpClientHeaderOptions(); - headerOptions.AddForwardedHeader(TraceEventIdHeader); - headerOptions.AddForwardedHeader(UserspaceIdHeader); - var conf = new RestHttpClientOptions(); - conf.ConfigHeaders(headerOptions); - - var client = new HttpClient(CreateDefaultResponseHandler(HttpStatusCode.OK, modelJson)); - var restHttpClient = CreateRestHttpClient(client, httpContext, conf); - - var response = await restHttpClient.Get>(uri, TimeSpan.FromSeconds(10)); - - Assert.True(client.DefaultRequestHeaders.Contains(TraceEventIdHeader)); - Assert.True(client.DefaultRequestHeaders.Contains(UserspaceIdHeader)); - - Assert.Equal(traceEventId, client.DefaultRequestHeaders.FirstOrDefault(x => x.Key == TraceEventIdHeader).Value.FirstOrDefault()); - Assert.Equal(userspaceId, client.DefaultRequestHeaders.FirstOrDefault(x => x.Key == UserspaceIdHeader).Value.FirstOrDefault()); - - var services = response.ResultObject?.ToList(); - - Assert.NotNull(services); - Assert.Equal(2, services!.Count); - Assert.Equal(1, services[0].Id); - Assert.Equal("Service1", services[0].Name); - Assert.Equal(2, services[1].Id); - Assert.Equal("Service2", services[1].Name); - var _logger = _loggers.First(); - Assert.Equal(2, _logger.LoggingEvents.Count); - Assert.Equal($"Start downstream request GET {uri} with http forwarded headers=[{TraceEventIdHeader}, {traceEventId}], [{UserspaceIdHeader}, {userspaceId}].", _logger.LoggingEvents[0]); - Assert.Contains($"Downstream request GET {uri} with http forwarded headers=[{TraceEventIdHeader}, {traceEventId}], [{UserspaceIdHeader}, {userspaceId}] finished with StatusCode {(int)HttpStatusCode.OK} at", _logger.LoggingEvents[1]); - } - - [Fact(DisplayName = "Выполнить GET запрос по относительному URI.")] - public async Task ShouldProperlyMakeGetRequestByRelativeUri() + [Fact(DisplayName = "Выполнить GET запрос по абсолютному URI с пробросом заголовков.", Skip = "DefaultRequestHeaders is not using now to send headers.")] + public async Task ShouldProperlyMakeGetRequestByAbsoluteUriWithForwardedHeaders() + { + var modelJson = JsonConvert.SerializeObject(new Service[] { - var modelJson = JsonConvert.SerializeObject(new Service[] { - new Service { Id = 1, Name = "Service1" }, - new Service { Id = 2, Name = "Service2" } - }); - - var baseUri = new Uri("http://unittest/"); - const string uri = "api/services"; - var fullUri = new Uri(baseUri, uri); - - var client = new HttpClient(CreateDefaultResponseHandler(HttpStatusCode.OK, modelJson)); - var restHttpClient = CreateRestHttpClient(client); - - client.BaseAddress = baseUri; - var response = await restHttpClient.Get>(uri, TimeSpan.FromSeconds(10)); - - var services = response.ResultObject?.ToList(); - - Assert.NotNull(services); - Assert.Equal(2, services!.Count); - Assert.Equal(1, services[0].Id); - Assert.Equal("Service1", services[0].Name); - Assert.Equal(2, services[1].Id); - Assert.Equal("Service2", services[1].Name); - var _logger = _loggers.First(); - Assert.Equal(2, _logger.LoggingEvents.Count); - Assert.Equal($"Start downstream request GET {fullUri} with http forwarded headers=.", _logger.LoggingEvents[0]); - Assert.Contains($"Downstream request GET {fullUri} with http forwarded headers= finished with StatusCode {(int)HttpStatusCode.OK} at", _logger.LoggingEvents[1]); - } - - [Fact(DisplayName = "Исключение на провалившемся GET запросе.")] - public void ShouldProperlyThrowResponseExceptionOnFailedGetRequest() + new() { Id = 1, Name = "Service1" }, + new() { Id = 2, Name = "Service2" } + }); + var traceEventId = Guid.NewGuid().ToString(); + const string userspaceId = "10"; + const string uri = "http://unittest/api/services"; + + var httpContext = new DefaultHttpContext(); + httpContext.Request.Headers.Append(TraceEventIdHeader, traceEventId); + httpContext.Request.Headers.Append(UserspaceIdHeader, userspaceId); + + var headerOptions = new RestHttpClientHeaderOptions(); + headerOptions.AddForwardedHeader(TraceEventIdHeader); + headerOptions.AddForwardedHeader(UserspaceIdHeader); + var conf = new RestHttpClientOptions(); + conf.ConfigHeaders(headerOptions); + + var client = new HttpClient(CreateDefaultResponseHandler(HttpStatusCode.OK, modelJson)); + var restHttpClient = CreateRestHttpClient(client, httpContext, conf); + + var response = await restHttpClient.Get>(uri, TimeSpan.FromSeconds(10)); + + Assert.True(client.DefaultRequestHeaders.Contains(TraceEventIdHeader)); + Assert.True(client.DefaultRequestHeaders.Contains(UserspaceIdHeader)); + + Assert.Equal(traceEventId, client.DefaultRequestHeaders.FirstOrDefault(x => x.Key == TraceEventIdHeader).Value.FirstOrDefault()); + Assert.Equal(userspaceId, client.DefaultRequestHeaders.FirstOrDefault(x => x.Key == UserspaceIdHeader).Value.FirstOrDefault()); + + var services = response.ResultObject?.ToList(); + + Assert.NotNull(services); + Assert.Equal(2, services!.Count); + Assert.Equal(1, services[0].Id); + Assert.Equal("Service1", services[0].Name); + Assert.Equal(2, services[1].Id); + Assert.Equal("Service2", services[1].Name); + var _logger = _loggers.First(); + Assert.Equal(2, _logger.LoggingEvents.Count); + Assert.Equal($"Start downstream request GET {uri} with http forwarded headers=[{TraceEventIdHeader}, {traceEventId}], [{UserspaceIdHeader}, {userspaceId}].", _logger.LoggingEvents[0]); + Assert.Contains($"Downstream request GET {uri} with http forwarded headers=[{TraceEventIdHeader}, {traceEventId}], [{UserspaceIdHeader}, {userspaceId}] finished with StatusCode {(int)HttpStatusCode.OK} at", _logger.LoggingEvents[1]); + } + + [Fact(DisplayName = "Выполнить GET запрос по относительному URI.")] + public async Task ShouldProperlyMakeGetRequestByRelativeUri() + { + var modelJson = JsonConvert.SerializeObject(new Service[] { - var modelJson = JsonConvert.SerializeObject(new { message = "Ошибка в запросе." }); + new() { Id = 1, Name = "Service1" }, + new() { Id = 2, Name = "Service2" } + }); + + var baseUri = new Uri("http://unittest/"); + const string uri = "api/services"; + var fullUri = new Uri(baseUri, uri); + + var client = new HttpClient(CreateDefaultResponseHandler(HttpStatusCode.OK, modelJson)); + var restHttpClient = CreateRestHttpClient(client); + + client.BaseAddress = baseUri; + var response = await restHttpClient.Get>(uri, TimeSpan.FromSeconds(10)); + + var services = response.ResultObject?.ToList(); + + Assert.NotNull(services); + Assert.Equal(2, services!.Count); + Assert.Equal(1, services[0].Id); + Assert.Equal("Service1", services[0].Name); + Assert.Equal(2, services[1].Id); + Assert.Equal("Service2", services[1].Name); + var _logger = _loggers.First(); + Assert.Equal(2, _logger.LoggingEvents.Count); + Assert.Equal($"Start downstream request GET {fullUri} with http forwarded headers=.", _logger.LoggingEvents[0]); + Assert.Contains($"Downstream request GET {fullUri} with http forwarded headers= finished with StatusCode {(int)HttpStatusCode.OK} at", _logger.LoggingEvents[1]); + } - const string uri = "http://unittest/api/services"; + [Fact(DisplayName = "Исключение на провалившемся GET запросе.")] + public void ShouldProperlyThrowResponseExceptionOnFailedGetRequest() + { + var modelJson = JsonConvert.SerializeObject(new { message = "Ошибка в запросе." }); - var client = new HttpClient(CreateDefaultResponseHandler(HttpStatusCode.BadRequest, modelJson)); - var restHttpClient = CreateRestHttpClient(client); + const string uri = "http://unittest/api/services"; - var ex = Assert.Throws(() => restHttpClient.Get>(uri, TimeSpan.FromSeconds(10)).GetAwaiter().GetResult()); + var client = new HttpClient(CreateDefaultResponseHandler(HttpStatusCode.BadRequest, modelJson)); + var restHttpClient = CreateRestHttpClient(client); - Assert.NotNull(ex); - Assert.Equal(modelJson, ex.ResponseData); - Assert.Equal((int)HttpStatusCode.BadRequest, (int)ex.StatusCode); - Assert.Contains($"Downstream request failed with status code {(int)HttpStatusCode.BadRequest} at", ex.Message); - Assert.Contains($"Response body: {modelJson}.", ex.Message); + var ex = Assert.Throws(() => restHttpClient.Get>(uri, TimeSpan.FromSeconds(10)).GetAwaiter().GetResult()); - var _logger = _loggers.First(); - Assert.Equal($"Start downstream request GET {uri} with http forwarded headers=.", _logger.LoggingEvents[0]); - Assert.Contains($"Downstream request GET {uri} with http forwarded headers= failed with StatusCode {(int)HttpStatusCode.BadRequest} at", _logger.LoggingEvents[1]); - Assert.Contains($"Request body: (null). Response body: {modelJson}.", _logger.LoggingEvents[1]); - } + Assert.NotNull(ex); + Assert.Equal(modelJson, ex.ResponseData); + Assert.Equal((int)HttpStatusCode.BadRequest, (int)ex.StatusCode); + Assert.Contains($"Downstream request failed with status code {(int)HttpStatusCode.BadRequest} at", ex.Message); + Assert.Contains($"Response body: {modelJson}.", ex.Message); - [Fact(DisplayName = "Пробросить текст исключения из нижестоящего сервиса на провалившемся GET запросе.")] - public void ShouldProperlyRethrowExceptionOnFailedGetRequest() - { - const string uri = "http://unittest/api/services"; + var _logger = _loggers.First(); + Assert.Equal($"Start downstream request GET {uri} with http forwarded headers=.", _logger.LoggingEvents[0]); + Assert.Contains($"Downstream request GET {uri} with http forwarded headers= failed with StatusCode {(int)HttpStatusCode.BadRequest} at", _logger.LoggingEvents[1]); + Assert.Contains($"Request body: (null). Response body: {modelJson}.", _logger.LoggingEvents[1]); + } - var mockHttpMessageHandler = new Mock(); + [Fact(DisplayName = "Пробросить текст исключения из нижестоящего сервиса на провалившемся GET запросе.")] + public void ShouldProperlyRethrowExceptionOnFailedGetRequest() + { + const string uri = "http://unittest/api/services"; - mockHttpMessageHandler.Protected() - .Setup>("SendAsync", ItExpr.IsAny(), ItExpr.IsAny()) - .Throws(new HttpRequestException("Request exception.")); + var mockHttpMessageHandler = new Mock(); - var client = new HttpClient(mockHttpMessageHandler.Object); - var restHttpClient = CreateRestHttpClient(client); + mockHttpMessageHandler.Protected() + .Setup>("SendAsync", ItExpr.IsAny(), ItExpr.IsAny()) + .Throws(new HttpRequestException("Request exception.")); - Exception ex = Assert.Throws(() => restHttpClient.Get>(uri, TimeSpan.FromSeconds(10)).GetAwaiter().GetResult()); + var client = new HttpClient(mockHttpMessageHandler.Object); + var restHttpClient = CreateRestHttpClient(client); - Assert.NotNull(ex); - Assert.Equal($"Request exception.", ex.Message); - var _logger = _loggers.First(); - Assert.Equal($"Start downstream request GET {uri} with http forwarded headers=.", _logger.LoggingEvents[0]); - Assert.Contains($"Downstream request GET {uri} with http forwarded headers= failed with Exception at", _logger.LoggingEvents[1]); - Assert.Contains("Request body: (null). Response body: . Exception message: Request exception.", _logger.LoggingEvents[1]); - } + Exception ex = Assert.Throws(() => restHttpClient.Get>(uri, TimeSpan.FromSeconds(10)).GetAwaiter().GetResult()); - [Fact(DisplayName = "Выбросить исключение на провалившемся POST запросе.")] - public void ShouldProperlyThrowResponseExceptionOnFailedPostRequest() - { - var requestModel = new Service() { Id = 12, Name = "Service1" }; - var requestJson = JsonConvert.SerializeObject(requestModel, new JsonSerializerSettings() { ContractResolver = _jsonResolver }); - var modelJson = JsonConvert.SerializeObject(new { message = "Серверная ошибка." }); + Assert.NotNull(ex); + Assert.Equal($"Request exception.", ex.Message); + var _logger = _loggers.First(); + Assert.Equal($"Start downstream request GET {uri} with http forwarded headers=.", _logger.LoggingEvents[0]); + Assert.Contains($"Downstream request GET {uri} with http forwarded headers= failed with Exception at", _logger.LoggingEvents[1]); + Assert.Contains("Request body: (null). Response body: . Exception message: Request exception.", _logger.LoggingEvents[1]); + } + + [Fact(DisplayName = "Выбросить исключение на провалившемся POST запросе.")] + public void ShouldProperlyThrowResponseExceptionOnFailedPostRequest() + { + var requestModel = new Service() { Id = 12, Name = "Service1" }; + var requestJson = JsonConvert.SerializeObject(requestModel, new JsonSerializerSettings() { ContractResolver = _jsonResolver }); + var modelJson = JsonConvert.SerializeObject(new { message = "Серверная ошибка." }); - const string uri = "http://unittest/api/services"; + const string uri = "http://unittest/api/services"; - var client = new HttpClient(CreateDefaultResponseHandler(HttpStatusCode.InternalServerError, modelJson)); - var restHttpClient = CreateRestHttpClient(client); + var client = new HttpClient(CreateDefaultResponseHandler(HttpStatusCode.InternalServerError, modelJson)); + var restHttpClient = CreateRestHttpClient(client); - var ex = Assert.Throws(() => restHttpClient.Post(uri, requestModel, TimeSpan.FromSeconds(10)).GetAwaiter().GetResult()); + var ex = Assert.Throws(() => restHttpClient.Post(uri, requestModel, TimeSpan.FromSeconds(10)).GetAwaiter().GetResult()); - Assert.NotNull(ex); - Assert.Equal(modelJson, ex.ResponseData); - Assert.Equal((int)HttpStatusCode.InternalServerError, (int)ex.StatusCode); - Assert.Contains($"Downstream request failed with status code {(int)HttpStatusCode.InternalServerError} at", ex.Message); - Assert.Contains($"Request body: {requestJson}. Response body: {modelJson}.", ex.Message); + Assert.NotNull(ex); + Assert.Equal(modelJson, ex.ResponseData); + Assert.Equal((int)HttpStatusCode.InternalServerError, (int)ex.StatusCode); + Assert.Contains($"Downstream request failed with status code {(int)HttpStatusCode.InternalServerError} at", ex.Message); + Assert.Contains($"Request body: {requestJson}. Response body: {modelJson}.", ex.Message); - var _logger = _loggers.First(); - Assert.Equal($"Start downstream request POST {uri} with http forwarded headers=.", _logger.LoggingEvents[0]); - Assert.Contains($"Downstream request POST {uri} with http forwarded headers= failed with StatusCode {(int)HttpStatusCode.InternalServerError} at", _logger.LoggingEvents[1]); - Assert.Contains($"Request body: {requestJson}. Response body: {modelJson}.", _logger.LoggingEvents[1]); - } + var _logger = _loggers.First(); + Assert.Equal($"Start downstream request POST {uri} with http forwarded headers=.", _logger.LoggingEvents[0]); + Assert.Contains($"Downstream request POST {uri} with http forwarded headers= failed with StatusCode {(int)HttpStatusCode.InternalServerError} at", _logger.LoggingEvents[1]); + Assert.Contains($"Request body: {requestJson}. Response body: {modelJson}.", _logger.LoggingEvents[1]); + } - [Fact(DisplayName = "Проверка правильности установки AccessToken.")] - public async Task ShouldProperlyGetAccessTokenIfAuthorizationRequestSet() + [Fact(DisplayName = "Проверка правильности установки AccessToken.")] + public async Task ShouldProperlyGetAccessTokenIfAuthorizationRequestSet() + { + var modelJson = JsonConvert.SerializeObject(new Service[] { - var modelJson = JsonConvert.SerializeObject(new Service[] { - new Service { Id = 1, Name = "Service1" }, - new Service { Id = 2, Name = "Service2" } - }); + new() { Id = 1, Name = "Service1" }, + new() { Id = 2, Name = "Service2" } + }); - const string uri = "http://unittest/api/services"; + const string uri = "http://unittest/api/services"; - var client = new HttpClient(CreateDefaultResponseHandler(HttpStatusCode.OK, modelJson)); - var restHttpClient = CreateRestHttpClient(client); + var client = new HttpClient(CreateDefaultResponseHandler(HttpStatusCode.OK, modelJson)); + var restHttpClient = CreateRestHttpClient(client); - var accessTokenRequestCounter = 0; - RestHttpClient.AuthorizationRequest += async _ => - { - accessTokenRequestCounter++; - - var tokenResponse = new TokenResponse(); - var json = System.Text.Json.JsonDocument.Parse("{ \"access_token\": \"eyJra\", \"refresh_token\": null, \"token_type\": \"Bearer\", \"expires_in\": 3600 }"); - typeof(TokenResponse).GetProperty(nameof(TokenResponse.Json))?.SetValue(tokenResponse, json.RootElement); - return await Task.FromResult(tokenResponse); - }; - - var response = await restHttpClient.Get>(uri, TimeSpan.FromSeconds(10)); - - Assert.Equal("eyJra", RestHttpClient.AccessToken?.AccessToken); - var services = response.ResultObject?.ToList(); - - Assert.NotNull(services); - Assert.Equal(2, services!.Count); - Assert.Equal(1, services[0].Id); - Assert.Equal("Service1", services[0].Name); - Assert.Equal(2, services[1].Id); - Assert.Equal("Service2", services[1].Name); - - var _logger = _loggers.First(); - Assert.Equal(4, _logger.LoggingEvents.Count); - Assert.Equal($"Start downstream request GET {uri} with http forwarded headers=.", _logger.LoggingEvents[0]); - Assert.Equal($"Requesting authentication token.", _logger.LoggingEvents[1]); - Assert.Contains("Authentication token request finished at ", _logger.LoggingEvents[2]); - Assert.Contains($"Downstream request GET {uri} with http forwarded headers= finished with StatusCode {(int)HttpStatusCode.OK} at", _logger.LoggingEvents[3]); - - Assert.Equal(1, accessTokenRequestCounter); - } - - [Fact(DisplayName = "Проверка переустановки AccessToken при 401 ответе.")] - public async Task ShouldProperlyRequestAccessTokenIf401Response() + var accessTokenRequestCounter = 0; + RestHttpClient.AuthorizationRequest += async _ => { - var modelJson = JsonConvert.SerializeObject(new Service[] { - new Service { Id = 1, Name = "Service1" }, - new Service { Id = 2, Name = "Service2" } - }); + accessTokenRequestCounter++; + + var tokenResponse = new TokenResponse(); + var json = System.Text.Json.JsonDocument.Parse("{ \"access_token\": \"eyJra\", \"refresh_token\": null, \"token_type\": \"Bearer\", \"expires_in\": 3600 }"); + typeof(TokenResponse).GetProperty(nameof(TokenResponse.Json))?.SetValue(tokenResponse, json.RootElement); + return await Task.FromResult(tokenResponse); + }; + + var response = await restHttpClient.Get>(uri, TimeSpan.FromSeconds(10)); + + Assert.Equal("eyJra", RestHttpClient.AccessToken?.AccessToken); + var services = response.ResultObject?.ToList(); + + Assert.NotNull(services); + Assert.Equal(2, services!.Count); + Assert.Equal(1, services[0].Id); + Assert.Equal("Service1", services[0].Name); + Assert.Equal(2, services[1].Id); + Assert.Equal("Service2", services[1].Name); + + var _logger = _loggers.First(); + Assert.Equal(4, _logger.LoggingEvents.Count); + Assert.Equal($"Start downstream request GET {uri} with http forwarded headers=.", _logger.LoggingEvents[0]); + Assert.Equal($"Requesting authentication token.", _logger.LoggingEvents[1]); + Assert.Contains("Authentication token request finished at ", _logger.LoggingEvents[2]); + Assert.Contains($"Downstream request GET {uri} with http forwarded headers= finished with StatusCode {(int)HttpStatusCode.OK} at", _logger.LoggingEvents[3]); + + Assert.Equal(1, accessTokenRequestCounter); + } - const string uri = "http://unittest/api/services"; + [Fact(DisplayName = "Проверка переустановки AccessToken при 401 ответе.")] + public async Task ShouldProperlyRequestAccessTokenIf401Response() + { + var modelJson = JsonConvert.SerializeObject(new Service[] + { + new() { Id = 1, Name = "Service1" }, + new() { Id = 2, Name = "Service2" } + }); - var client = new HttpClient(CreateDefaultResponseHandler(HttpStatusCode.Unauthorized, modelJson)); - var restHttpClient = CreateRestHttpClient(client); + const string uri = "http://unittest/api/services"; - var accessTokenRequestCounter = 0; - RestHttpClient.AuthorizationRequest += async _ => - { - accessTokenRequestCounter++; - var tokenResponse = new TokenResponse(); - var json = System.Text.Json.JsonDocument.Parse($"{{\"access_token\": \"{(accessTokenRequestCounter == 2 ? "trsJ34" : "eyJra")}\", \"refresh_token\": null, \"token_type\": \"Bearer\", \"expires_in\": 3600 }}"); - typeof(TokenResponse).GetProperty(nameof(TokenResponse.Json))?.SetValue(tokenResponse, json.RootElement); - return await Task.FromResult(tokenResponse); - }; + var client = new HttpClient(CreateDefaultResponseHandler(HttpStatusCode.Unauthorized, modelJson)); + var restHttpClient = CreateRestHttpClient(client); - await Assert.ThrowsAsync(async () => await restHttpClient.Get>(uri, TimeSpan.FromSeconds(10))); + var accessTokenRequestCounter = 0; + RestHttpClient.AuthorizationRequest += async _ => + { + accessTokenRequestCounter++; + var tokenResponse = new TokenResponse(); + var json = System.Text.Json.JsonDocument.Parse($"{{\"access_token\": \"{(accessTokenRequestCounter == 2 ? "trsJ34" : "eyJra")}\", \"refresh_token\": null, \"token_type\": \"Bearer\", \"expires_in\": 3600 }}"); + typeof(TokenResponse).GetProperty(nameof(TokenResponse.Json))?.SetValue(tokenResponse, json.RootElement); + return await Task.FromResult(tokenResponse); + }; - var _logger = _loggers.First(); - Assert.Equal($"Start downstream request GET {uri} with http forwarded headers=.", _logger.LoggingEvents[0]); - Assert.Equal($"Requesting authentication token.", _logger.LoggingEvents[1]); - Assert.Contains("Authentication token request finished at ", _logger.LoggingEvents[2]); - Assert.Equal($"Requesting authentication token.", _logger.LoggingEvents[3]); - Assert.Contains("Authentication token request finished at ", _logger.LoggingEvents[4]); - Assert.Contains($"Downstream request GET {uri} with http forwarded headers= failed with StatusCode {(int)HttpStatusCode.Unauthorized} at", _logger.LoggingEvents[5]); - Assert.Contains($"Request body: (null). Response body: {modelJson}.", _logger.LoggingEvents[5]); + await Assert.ThrowsAsync(async () => await restHttpClient.Get>(uri, TimeSpan.FromSeconds(10))); - Assert.Equal("trsJ34", RestHttpClient.AccessToken?.AccessToken); + var _logger = _loggers.First(); + Assert.Equal($"Start downstream request GET {uri} with http forwarded headers=.", _logger.LoggingEvents[0]); + Assert.Equal($"Requesting authentication token.", _logger.LoggingEvents[1]); + Assert.Contains("Authentication token request finished at ", _logger.LoggingEvents[2]); + Assert.Equal($"Requesting authentication token.", _logger.LoggingEvents[3]); + Assert.Contains("Authentication token request finished at ", _logger.LoggingEvents[4]); + Assert.Contains($"Downstream request GET {uri} with http forwarded headers= failed with StatusCode {(int)HttpStatusCode.Unauthorized} at", _logger.LoggingEvents[5]); + Assert.Contains($"Request body: (null). Response body: {modelJson}.", _logger.LoggingEvents[5]); + Assert.Equal("trsJ34", RestHttpClient.AccessToken?.AccessToken); - Assert.Equal(2, accessTokenRequestCounter); - } - [Fact(DisplayName = "Выполнить сериализацию данных в теле запроса в формает CamelCase.")] - public async Task ShouldProperlySerializeJsonAsCamelCase() - { - var model = new Service() { Id = 12, Name = "Service1" }; + Assert.Equal(2, accessTokenRequestCounter); + } + + [Fact(DisplayName = "Выполнить сериализацию данных в теле запроса в формает CamelCase.")] + public async Task ShouldProperlySerializeJsonAsCamelCase() + { + var model = new Service() { Id = 12, Name = "Service1" }; - const string uri = "http://unittest/api/services"; + const string uri = "http://unittest/api/services"; - var client = new HttpClient(CreateDefaultResponseHandler(HttpStatusCode.OK, JsonConvert.SerializeObject(model))); - var restHttpClient = CreateRestHttpClient(client); + var client = new HttpClient(CreateDefaultResponseHandler(HttpStatusCode.OK, JsonConvert.SerializeObject(model))); + var restHttpClient = CreateRestHttpClient(client); - var result = await restHttpClient.Post(uri, model); - var serializedRequestBody = await result.OriginalResponse.RequestMessage!.Content!.ReadAsStringAsync(); + var result = await restHttpClient.Post(uri, model); + var serializedRequestBody = await result.OriginalResponse.RequestMessage!.Content!.ReadAsStringAsync(); - Assert.Equal("{\"id\":12,\"name\":\"Service1\"}", serializedRequestBody); + Assert.Equal("{\"id\":12,\"name\":\"Service1\"}", serializedRequestBody); - } + } - [Fact(DisplayName = "Повторное использование экземпляра HttpClient.")] - public async Task ShouldProperlyReuseHttpClientObject() + [Fact(DisplayName = "Повторное использование экземпляра HttpClient.")] + public async Task ShouldProperlyReuseHttpClientObject() + { + var modelJson = JsonConvert.SerializeObject(new Service[] { - var modelJson = JsonConvert.SerializeObject(new Service[] { - new Service { Id = 1, Name = "Service1" }, - new Service { Id = 2, Name = "Service2" } - }); + new() { Id = 1, Name = "Service1" }, + new() { Id = 2, Name = "Service2" } + }); - const string uri = "http://unittest/api/services"; + const string uri = "http://unittest/api/services"; - var client = new HttpClient(CreateDefaultResponseHandler(HttpStatusCode.OK, modelJson)); - var restHttpClient = CreateRestHttpClient(client); + var client = new HttpClient(CreateDefaultResponseHandler(HttpStatusCode.OK, modelJson)); + var restHttpClient = CreateRestHttpClient(client); - var response = await restHttpClient.Get>(uri, TimeSpan.FromSeconds(10)); + var response = await restHttpClient.Get>(uri, TimeSpan.FromSeconds(10)); - Assert.Equal(2, response.ResultObject?.Count()); + Assert.Equal(2, response.ResultObject?.Count()); - // Повторный запрос. - response = await restHttpClient.Get>(uri, TimeSpan.FromSeconds(10)); - Assert.Equal(2, response.ResultObject?.Count()); + // Повторный запрос. + response = await restHttpClient.Get>(uri, TimeSpan.FromSeconds(10)); + Assert.Equal(2, response.ResultObject?.Count()); - } + } - [Fact(DisplayName = "Повторное использование экземпляра HttpClient в параллельной обработке, с пробросом заголовков.")] - public async Task ShouldProperlyReuseHttpClientObjectInParallelWithHeaders() - { - var modelJson = JsonConvert.SerializeObject(new Service[] { - new Service { Id = 1, Name = "Service1" }, - new Service { Id = 2, Name = "Service2" } - }); - - const string uri = "http://unittest/api/services"; - - var headerOptions = new RestHttpClientHeaderOptions(); - headerOptions.AddForwardedHeader(TraceEventIdHeader); - headerOptions.AddForwardedHeader(UserspaceIdHeader); - var conf = new RestHttpClientOptions(); - conf.ConfigHeaders(headerOptions); - - var httpContext = new DefaultHttpContext(); - httpContext.Request.Headers.Add(TraceEventIdHeader, "eventId"); - httpContext.Request.Headers.Add(UserspaceIdHeader, "12"); - const int totalRequests = 1000; - - var client = new HttpClient(CreateDefaultResponseHandler(HttpStatusCode.OK, modelJson)); - var restHttpClient = CreateRestHttpClient(client, httpContext, conf); - - var firstResult = await restHttpClient.Get>(uri, TimeSpan.FromSeconds(10)); - Assert.Equal(2, firstResult.ResultObject?.Count()); - - var results = Enumerable.Range(0, totalRequests).AsParallel().AsOrdered().WithDegreeOfParallelism(10) - .Select(_ => restHttpClient.Get>(uri, TimeSpan.FromSeconds(10)).GetAwaiter().GetResult()) - .AsSequential() - .ToList(); - Assert.Empty(results.Where(x => x.OriginalResponse?.RequestMessage?.Headers.Count() != 2)); - Assert.Equal(2 * totalRequests, results.Where(x => x.ResultObject is not null).SelectMany(x => x.ResultObject).Count()); - } - - [Fact(DisplayName = "Проверка установки Bearer token из HttpContextAccessor.")] - public void ShouldProperlySetBearerTokenFromRequest() + [Fact(DisplayName = "Повторное использование экземпляра HttpClient в параллельной обработке, с пробросом заголовков.")] + public async Task ShouldProperlyReuseHttpClientObjectInParallelWithHeaders() + { + var modelJson = JsonConvert.SerializeObject(new Service[] { - var httpContext = new DefaultHttpContext(); - httpContext.Request.Headers.Add("Authorization", "Bearer token355"); + new() { Id = 1, Name = "Service1" }, + new() { Id = 2, Name = "Service2" } + }); + + const string uri = "http://unittest/api/services"; + + var headerOptions = new RestHttpClientHeaderOptions(); + headerOptions.AddForwardedHeader(TraceEventIdHeader); + headerOptions.AddForwardedHeader(UserspaceIdHeader); + var conf = new RestHttpClientOptions(); + conf.ConfigHeaders(headerOptions); + + var httpContext = new DefaultHttpContext(); + httpContext.Request.Headers.Append(TraceEventIdHeader, "eventId"); + httpContext.Request.Headers.Append(UserspaceIdHeader, "12"); + const int totalRequests = 1000; + + var client = new HttpClient(CreateDefaultResponseHandler(HttpStatusCode.OK, modelJson)); + var restHttpClient = CreateRestHttpClient(client, httpContext, conf); + + var firstResult = await restHttpClient.Get>(uri, TimeSpan.FromSeconds(10)); + Assert.Equal(2, firstResult.ResultObject?.Count()); + + var results = Enumerable.Range(0, totalRequests).AsParallel().AsOrdered().WithDegreeOfParallelism(10) + .Select(_ => restHttpClient.Get>(uri, TimeSpan.FromSeconds(10)).GetAwaiter().GetResult()) + .AsSequential() + .ToList(); + Assert.DoesNotContain(results, x => x.OriginalResponse?.RequestMessage?.Headers.Count() != 2); + Assert.Equal(2 * totalRequests, results.Where(x => x.ResultObject is not null).SelectMany(x => x.ResultObject).Count()); + } - var client = new HttpClient(CreateDefaultResponseHandler(HttpStatusCode.Unauthorized, "{}")); + [Fact(DisplayName = "Проверка установки Bearer token из HttpContextAccessor.")] + public void ShouldProperlySetBearerTokenFromRequest() + { + var httpContext = new DefaultHttpContext(); + httpContext.Request.Headers.Append("Authorization", "Bearer token355"); - var httpService = CreateRestHttpClient(client, httpContext); + var client = new HttpClient(CreateDefaultResponseHandler(HttpStatusCode.Unauthorized, "{}")); + _ = CreateRestHttpClient(client, httpContext); - Assert.Equal("token355", client.DefaultRequestHeaders?.Authorization?.Parameter); - Assert.Equal("Bearer", client.DefaultRequestHeaders?.Authorization?.Scheme); - } + Assert.Equal("token355", client.DefaultRequestHeaders?.Authorization?.Parameter); + Assert.Equal("Bearer", client.DefaultRequestHeaders?.Authorization?.Scheme); + } - [Fact(DisplayName = "Проверка НЕустановки Bearer token из HttpContextAccessor.")] - public void ShouldNotSetBearerTokenFromRequestIfBearerNotValid() - { - var httpContext = new DefaultHttpContext(); - httpContext.Request.Headers.Add("Authorization", "Bearetoken355"); - var client = new HttpClient(CreateDefaultResponseHandler(HttpStatusCode.Unauthorized, "{}")); + [Fact(DisplayName = "Проверка НЕустановки Bearer token из HttpContextAccessor.")] + public void ShouldNotSetBearerTokenFromRequestIfBearerNotValid() + { + var httpContext = new DefaultHttpContext(); + httpContext.Request.Headers.Append("Authorization", "Bearetoken355"); - var httpService = CreateRestHttpClient(client, httpContext); - Assert.Null(client.DefaultRequestHeaders.Authorization); - } + var client = new HttpClient(CreateDefaultResponseHandler(HttpStatusCode.Unauthorized, "{}")); + _ = CreateRestHttpClient(client, httpContext); - RestHttpClientMock CreateRestHttpClient(HttpClient httpClient, - HttpContext? httpContext = null, - RestHttpClientOptions? configuration = null) - { - return new RestHttpClientMock( - httpClient, - _loggerFactory, - configuration ?? _configuration, - new HttpContextAccessorStub(httpContext ?? new DefaultHttpContext())); - } - - HttpMessageHandler CreateDefaultResponseHandler(HttpStatusCode responseCode, string jsonResponseBody) - { - var mockHttpMessageHandler = new Mock(); - - mockHttpMessageHandler.Protected() - .Setup>("SendAsync", ItExpr.IsAny(), ItExpr.IsAny()) - .ReturnsAsync(new HttpResponseMessage - { - StatusCode = responseCode, - Content = new StringContent(jsonResponseBody), - }); - - return mockHttpMessageHandler.Object; - } + Assert.Null(client.DefaultRequestHeaders.Authorization); + } + + RestHttpClientMock CreateRestHttpClient( + HttpClient httpClient, + HttpContext? httpContext = null, + RestHttpClientOptions? configuration = null) + => new( + httpClient, + _loggerFactory, + configuration ?? _configuration, + new HttpContextAccessorStub(httpContext ?? new DefaultHttpContext())); + + static HttpMessageHandler CreateDefaultResponseHandler(HttpStatusCode responseCode, string jsonResponseBody) + { + var mockHttpMessageHandler = new Mock(); + + mockHttpMessageHandler.Protected() + .Setup>("SendAsync", ItExpr.IsAny(), ItExpr.IsAny()) + .ReturnsAsync(new HttpResponseMessage + { + StatusCode = responseCode, + Content = new StringContent(jsonResponseBody), + }); + + return mockHttpMessageHandler.Object; } } diff --git a/src/Monq.Core.HttpClientExtensions.Tests/Stubs/HttpContextAccessorStub.cs b/src/Monq.Core.HttpClientExtensions.Tests/Stubs/HttpContextAccessorStub.cs index 0181d62..6f3e078 100644 --- a/src/Monq.Core.HttpClientExtensions.Tests/Stubs/HttpContextAccessorStub.cs +++ b/src/Monq.Core.HttpClientExtensions.Tests/Stubs/HttpContextAccessorStub.cs @@ -1,14 +1,13 @@ -using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http; -namespace Monq.Core.HttpClientExtensions.Tests.Stubs +namespace Monq.Core.HttpClientExtensions.Tests.Stubs; + +public class HttpContextAccessorStub : IHttpContextAccessor { - public class HttpContextAccessorStub : IHttpContextAccessor + public HttpContextAccessorStub(HttpContext defaultHttpContext) { - public HttpContextAccessorStub(HttpContext defaultHttpContext) - { - HttpContext = defaultHttpContext; - } - - public HttpContext HttpContext { get; set; } + HttpContext = defaultHttpContext; } + + public HttpContext HttpContext { get; set; } } diff --git a/src/Monq.Core.HttpClientExtensions.Tests/Stubs/StubLogger.cs b/src/Monq.Core.HttpClientExtensions.Tests/Stubs/StubLogger.cs index 2cbf2da..cdf38ef 100644 --- a/src/Monq.Core.HttpClientExtensions.Tests/Stubs/StubLogger.cs +++ b/src/Monq.Core.HttpClientExtensions.Tests/Stubs/StubLogger.cs @@ -1,40 +1,39 @@ -using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; -namespace Monq.Core.HttpClientExtensions.Tests.Stubs +namespace Monq.Core.HttpClientExtensions.Tests.Stubs; + +public class StubLogger : ILogger { - public class StubLogger : ILogger + public List LoggingEvents { get; set; } = new List(); + + public void Log(LogLevel logLevel, int eventId, object state, Exception exception, Func formatter) + { + LoggingEvents.Add(state.ToString()); + } + + public bool IsEnabled(LogLevel logLevel) + { + return false; + } + + public IDisposable? BeginScopeImpl(object state) + { + return null; + } + + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) { - public List LoggingEvents { get; set; } = new List(); - - public void Log(LogLevel logLevel, int eventId, object state, Exception exception, Func formatter) - { - LoggingEvents.Add(state.ToString()); - } - - public bool IsEnabled(LogLevel logLevel) - { - return false; - } - - public IDisposable? BeginScopeImpl(object state) - { - return null; - } - - public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) - { - LoggingEvents.Add(state.ToString()); - } - - public IDisposable? BeginScope(TState state) - { - return null; - } + LoggingEvents.Add(state.ToString()); } - public class StubLogger : StubLogger, ILogger + public IDisposable? BeginScope(TState state) { + return null; } } + +public class StubLogger : StubLogger, ILogger +{ +} diff --git a/src/Monq.Core.HttpClientExtensions.Tests/Stubs/StubLoggerFactory.cs b/src/Monq.Core.HttpClientExtensions.Tests/Stubs/StubLoggerFactory.cs index 50e19ec..36a054a 100644 --- a/src/Monq.Core.HttpClientExtensions.Tests/Stubs/StubLoggerFactory.cs +++ b/src/Monq.Core.HttpClientExtensions.Tests/Stubs/StubLoggerFactory.cs @@ -1,39 +1,38 @@ -using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; -namespace Monq.Core.HttpClientExtensions.Tests.Stubs +namespace Monq.Core.HttpClientExtensions.Tests.Stubs; + +public class StubLoggerFactory : ILoggerFactory { - public class StubLoggerFactory : ILoggerFactory - { - readonly IList _loggers; + readonly IList _loggers; - public StubLoggerFactory(IList loggers) - { - _loggers = loggers; - } + public StubLoggerFactory(IList loggers) + { + _loggers = loggers; + } - public void AddProvider(ILoggerProvider provider) - { - throw new NotImplementedException(); - } + public void AddProvider(ILoggerProvider provider) + { + throw new NotImplementedException(); + } - public ILogger CreateLog() - { - var logger = new StubLogger(); - _loggers.Add(logger); - return logger; - } + public ILogger CreateLog() + { + var logger = new StubLogger(); + _loggers.Add(logger); + return logger; + } - public ILogger CreateLogger(string categoryName) - { - var logger = new StubLogger(); - _loggers.Add(logger); - return logger; - } + public ILogger CreateLogger(string categoryName) + { + var logger = new StubLogger(); + _loggers.Add(logger); + return logger; + } - public void Dispose() - { - } + public void Dispose() + { } } diff --git a/src/Monq.Core.HttpClientExtensions/Configuration/AuthConstants.cs b/src/Monq.Core.HttpClientExtensions/Configuration/AuthConstants.cs index 3beed69..45c5c86 100644 --- a/src/Monq.Core.HttpClientExtensions/Configuration/AuthConstants.cs +++ b/src/Monq.Core.HttpClientExtensions/Configuration/AuthConstants.cs @@ -1,4 +1,4 @@ -namespace Monq.Core.HttpClientExtensions +namespace Monq.Core.HttpClientExtensions { internal static class AuthConstants { diff --git a/src/Monq.Core.HttpClientExtensions/Configuration/RestHttpClientOptions.cs b/src/Monq.Core.HttpClientExtensions/Configuration/RestHttpClientOptions.cs index 973f466..7093246 100644 --- a/src/Monq.Core.HttpClientExtensions/Configuration/RestHttpClientOptions.cs +++ b/src/Monq.Core.HttpClientExtensions/Configuration/RestHttpClientOptions.cs @@ -1,4 +1,4 @@ -namespace Monq.Core.HttpClientExtensions +namespace Monq.Core.HttpClientExtensions { /// /// Basic http client configuration. @@ -8,7 +8,7 @@ public class RestHttpClientOptions /// /// Configuration of handling http-headers. /// - public RestHttpClientHeaderOptions RestHttpClientHeaderOptions { get; protected set; } = + public RestHttpClientHeaderOptions RestHttpClientHeaderOptions { get; protected set; } = new RestHttpClientHeaderOptions(); /// diff --git a/src/Monq.Core.HttpClientExtensions/Exceptions/MissingConfigurationException.cs b/src/Monq.Core.HttpClientExtensions/Exceptions/MissingConfigurationException.cs index 44410ab..497e6f5 100644 --- a/src/Monq.Core.HttpClientExtensions/Exceptions/MissingConfigurationException.cs +++ b/src/Monq.Core.HttpClientExtensions/Exceptions/MissingConfigurationException.cs @@ -1,5 +1,4 @@ -using System; -using System.Runtime.Serialization; +using System; namespace Monq.Core.HttpClientExtensions.Exceptions { diff --git a/src/Monq.Core.HttpClientExtensions/Extensions/HostBuilderExtensions.cs b/src/Monq.Core.HttpClientExtensions/Extensions/HostBuilderExtensions.cs index b9c2916..741b262 100644 --- a/src/Monq.Core.HttpClientExtensions/Extensions/HostBuilderExtensions.cs +++ b/src/Monq.Core.HttpClientExtensions/Extensions/HostBuilderExtensions.cs @@ -42,7 +42,7 @@ public static IHostBuilder ConfigureStaticAuthentication(this IHostBuilder hostB Policy = new DiscoveryPolicy { RequireHttps = requireHttps } }; var disco = await client.GetDiscoveryDocumentAsync(discoveryDocumentRequest); - if (disco.IsError) + if (disco.IsError) throw new DiscoveryEndpointException(disco.Error, disco.Exception); var request = new ClientCredentialsTokenRequest diff --git a/src/Monq.Core.HttpClientExtensions/Extensions/HttpClientExtensions.cs b/src/Monq.Core.HttpClientExtensions/Extensions/HttpClientExtensions.cs index da63a44..367786a 100644 --- a/src/Monq.Core.HttpClientExtensions/Extensions/HttpClientExtensions.cs +++ b/src/Monq.Core.HttpClientExtensions/Extensions/HttpClientExtensions.cs @@ -57,9 +57,9 @@ public static Task DeleteAsJsonAsync(this HttpClient httpCl [RequiresUnreferencedCode( "Serializers is incompatible with trimming.")] - static async Task MakeRequest(HttpClient httpClient, - string requestType, - string uri, + static async Task MakeRequest(HttpClient httpClient, + string requestType, + string uri, object value) { var cts = new CancellationTokenSource(); diff --git a/src/Monq.Core.HttpClientExtensions/Monq.Core.HttpClientExtensions.csproj b/src/Monq.Core.HttpClientExtensions/Monq.Core.HttpClientExtensions.csproj index 1ed7a47..2bb0827 100644 --- a/src/Monq.Core.HttpClientExtensions/Monq.Core.HttpClientExtensions.csproj +++ b/src/Monq.Core.HttpClientExtensions/Monq.Core.HttpClientExtensions.csproj @@ -1,7 +1,7 @@  - 6.1.0 + 7.0.0 $(VersionSuffix) $(Version)-$(VersionSuffix) net6.0;net7.0;net8.0;net9.0;net10.0