diff --git a/commercetools.Sdk/Examples/commercetools.Api.ApmExample/Extensions/ClientInjectionSetup.cs b/commercetools.Sdk/Examples/commercetools.Api.ApmExample/Extensions/ClientInjectionSetup.cs index b6923393e55..7e64e9a7a99 100644 --- a/commercetools.Sdk/Examples/commercetools.Api.ApmExample/Extensions/ClientInjectionSetup.cs +++ b/commercetools.Sdk/Examples/commercetools.Api.ApmExample/Extensions/ClientInjectionSetup.cs @@ -31,11 +31,14 @@ public static IHttpClientBuilder UseCommercetoolsScopedClient(this IServiceColle { var clientConfiguration = configuration.GetSection(clientName).Get(); var tokenProvider = serviceProvider.GetService(); - var client = ClientFactory.Create(clientName, clientConfiguration, - serviceProvider.GetService(), - serviceProvider.GetService(), - tokenProvider); - client.Name = clientName; + var client = new ClientBuilder() + { + ClientConfiguration = clientConfiguration, + ClientName = clientName, + TokenProvider = tokenProvider, + SerializerService = serviceProvider.GetService(), + HttpClient = serviceProvider.GetService().CreateClient(clientName) + }.Build(); return client; }); diff --git a/commercetools.Sdk/Examples/commercetools.Api.CheckoutApp/Extensions/ClientInjectionSetup.cs b/commercetools.Sdk/Examples/commercetools.Api.CheckoutApp/Extensions/ClientInjectionSetup.cs index dbe59a26ba6..ed8ec765059 100644 --- a/commercetools.Sdk/Examples/commercetools.Api.CheckoutApp/Extensions/ClientInjectionSetup.cs +++ b/commercetools.Sdk/Examples/commercetools.Api.CheckoutApp/Extensions/ClientInjectionSetup.cs @@ -31,11 +31,14 @@ public static IHttpClientBuilder UseCommercetoolsScopedClient(this IServiceColle { var clientConfiguration = configuration.GetSection(clientName).Get(); var tokenProvider = serviceProvider.GetService(); - var client = ClientFactory.Create(clientName, clientConfiguration, - serviceProvider.GetService(), - serviceProvider.GetService(), - tokenProvider); - client.Name = clientName; + var client = new ClientBuilder() + { + ClientName = clientName, + ClientConfiguration = clientConfiguration, + TokenProvider = tokenProvider, + SerializerService =serviceProvider.GetService(), + HttpClient = serviceProvider.GetService().CreateClient(clientName) + }.Build(); return client; }); diff --git a/commercetools.Sdk/Examples/commercetools.Api.ConsoleApp/Program.cs b/commercetools.Sdk/Examples/commercetools.Api.ConsoleApp/Program.cs index 87c849062e7..86e4ab21650 100644 --- a/commercetools.Sdk/Examples/commercetools.Api.ConsoleApp/Program.cs +++ b/commercetools.Sdk/Examples/commercetools.Api.ConsoleApp/Program.cs @@ -32,13 +32,15 @@ public static async Task Main(string[] args) ProjectKey = "" }; var clientFactory = serviceProvider.GetService(); - var client = ClientFactory.Create( - clientName, - config, - clientFactory, - serviceProvider.GetService(), - TokenProviderFactory.CreateClientCredentialsTokenProvider(config, clientFactory) - ); + var client = new ClientBuilder() + { + ClientConfiguration = config, + ClientName = clientName, + TokenProvider = TokenProviderFactory.CreateClientCredentialsTokenProvider(config, clientFactory), + SerializerService = serviceProvider.GetService(), + HttpClient = clientFactory.CreateClient(clientName) + }.Build(); + var project = await new ApiRoot(client) .WithProjectKey(config.ProjectKey) .Get() diff --git a/commercetools.Sdk/Examples/commercetools.Api.NewRelicExample/Extensions/ClientInjectionSetup.cs b/commercetools.Sdk/Examples/commercetools.Api.NewRelicExample/Extensions/ClientInjectionSetup.cs index 4f568027a9e..d244ad89398 100644 --- a/commercetools.Sdk/Examples/commercetools.Api.NewRelicExample/Extensions/ClientInjectionSetup.cs +++ b/commercetools.Sdk/Examples/commercetools.Api.NewRelicExample/Extensions/ClientInjectionSetup.cs @@ -31,10 +31,14 @@ public static IHttpClientBuilder UseCommercetoolsScopedClient(this IServiceColle { var clientConfiguration = configuration.GetSection(clientName).Get(); var tokenProvider = serviceProvider.GetService(); - var client = ClientFactory.Create(clientName, clientConfiguration, - serviceProvider.GetService(), - serviceProvider.GetService(), - tokenProvider); + var client = new ClientBuilder() + { + ClientConfiguration = clientConfiguration, + ClientName = clientName, + TokenProvider = tokenProvider, + SerializerService = serviceProvider.GetService(), + HttpClient = serviceProvider.GetService().CreateClient(clientName) + }.Build(); client.Name = clientName; return client; }); diff --git a/commercetools.Sdk/IntegrationTests/commercetools.Api.IntegrationTests/LoggingTest.cs b/commercetools.Sdk/IntegrationTests/commercetools.Api.IntegrationTests/LoggingTest.cs index 27d876d0358..8e45a0d76b3 100644 --- a/commercetools.Sdk/IntegrationTests/commercetools.Api.IntegrationTests/LoggingTest.cs +++ b/commercetools.Sdk/IntegrationTests/commercetools.Api.IntegrationTests/LoggingTest.cs @@ -17,6 +17,45 @@ namespace commercetools.Api.IntegrationTests; public class LoggingTest { + [Fact] + public async void DefaultLogger() + { + var configuration = new ConfigurationBuilder(). + AddJsonFile("appsettings.test.Development.json", true). + AddEnvironmentVariables(). + AddUserSecrets(). + AddEnvironmentVariables("CTP_"). + Build(); + var clientConfiguration = configuration.GetSection("Client").Get(); + var loggerClientConf = new ConfigurationBuilder() + .AddInMemoryCollection(new Dictionary() + { + { "LoggerClient:ClientId", clientConfiguration.ClientId}, + { "LoggerClient:ClientSecret", clientConfiguration.ClientSecret}, + { "LoggerClient:ProjectKey", clientConfiguration.ProjectKey}, + }) + .Build(); + var logger = new TestLogger(); + var loggerFactory = LoggerFactory.Create(builder => + { + builder + .AddFilter("System.Net.Http.HttpClient", LogLevel.None) // disable HTTP client default logging + .AddProvider(new TestLoggerProvider(logger)); + }); + var s = new ServiceCollection(); + s.AddSingleton(loggerFactory); + s.UseCommercetoolsApi(loggerClientConf, "LoggerClient"); + var p = s.BuildServiceProvider(); + + var apiRoot = p.GetService(); + + await apiRoot.Get().ExecuteAsync(); + + var messages = logger.GetLogMessages(); + Assert.StartsWith("GET https://api.europe-west1.gcp.commercetools.com/" + clientConfiguration.ProjectKey, messages.TrimEnd()); + } + + [Fact] public async void CustomLogger() { @@ -55,7 +94,7 @@ public async void CustomLogger() var messages = logger.GetLogMessages(); Assert.Equal("GET https://api.europe-west1.gcp.commercetools.com/" + clientConfiguration.ProjectKey, messages.TrimEnd()); } - + public class CustomLoggerHandler : DelegatingHandler { private readonly ILogger logger; diff --git a/commercetools.Sdk/IntegrationTests/commercetools.Api.IntegrationTests/Me/MeIntegrationTests.cs b/commercetools.Sdk/IntegrationTests/commercetools.Api.IntegrationTests/Me/MeIntegrationTests.cs index 4e193d78dc4..48566f26b10 100644 --- a/commercetools.Sdk/IntegrationTests/commercetools.Api.IntegrationTests/Me/MeIntegrationTests.cs +++ b/commercetools.Sdk/IntegrationTests/commercetools.Api.IntegrationTests/Me/MeIntegrationTests.cs @@ -113,12 +113,13 @@ private void CreateMeClient() CustomerServices.CustomerPassword)); //Create MeClient - _meClient = ClientFactory.Create( - "MeClient", - meClientConfig, - httpClientFactory, - serializerService, - passwordTokenProvider); + _meClient = new ClientBuilder { + ClientName = "MeClient", + ClientConfiguration = meClientConfig, + TokenProvider = passwordTokenProvider, + HttpClient = httpClientFactory.CreateClient("MeClient"), + SerializerService = serializerService, + }.Build(); } } } \ No newline at end of file diff --git a/commercetools.Sdk/IntegrationTests/commercetools.Api.IntegrationTests/MiddlewareTest.cs b/commercetools.Sdk/IntegrationTests/commercetools.Api.IntegrationTests/MiddlewareTest.cs new file mode 100644 index 00000000000..7750b7ba25e --- /dev/null +++ b/commercetools.Sdk/IntegrationTests/commercetools.Api.IntegrationTests/MiddlewareTest.cs @@ -0,0 +1,66 @@ +using System.Collections.Generic; +using commercetools.Base.Client; +using commercetools.Base.Client.Middlewares; +using commercetools.Sdk.Api; +using commercetools.Sdk.Api.Client; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Xunit; + +namespace commercetools.Api.IntegrationTests; + +public class MiddlewareTest +{ + [Fact] + public async void not_found_middleware_stream_client() + { + var configuration = new ConfigurationBuilder(). + AddJsonFile("appsettings.test.Development.json", true). + AddEnvironmentVariables(). + AddUserSecrets(). + AddEnvironmentVariables("CTP_"). + Build(); + + var s = new ServiceCollection(); + s.UseCommercetoolsApi(configuration, "Client", options: new ClientOptions() { ReadResponseAsStream = true},middlewares: new List() + { + new NotFoundMiddleware() + }); + var p = s.BuildServiceProvider(); + + var apiConfig = configuration.GetSection("Client").Get(); + var apiRoot = p.GetService(); + + Assert.Equal("Client", apiRoot.ClientName); + var category = await apiRoot.Categories().WithKey("unknown-key").Get().ExecuteAsync().ConfigureAwait(false); + + Assert.Null(category); + } + + [Fact] + public async void not_found_middleware_string_client() + { + var configuration = new ConfigurationBuilder(). + AddJsonFile("appsettings.test.Development.json", true). + AddEnvironmentVariables(). + AddUserSecrets(). + AddEnvironmentVariables("CTP_"). + Build(); + + var s = new ServiceCollection(); + s.UseCommercetoolsApi(configuration, "Client", options: new ClientOptions() { ReadResponseAsStream = false},middlewares: new List() + { + new NotFoundMiddleware() + }); + var p = s.BuildServiceProvider(); + + var apiConfig = configuration.GetSection("Client").Get(); + var apiRoot = p.GetService(); + + Assert.Equal("Client", apiRoot.ClientName); + var category = await apiRoot.Categories().WithKey("unknown-key").Get().ExecuteAsync().ConfigureAwait(false); + + Assert.Null(category); + } + +} \ No newline at end of file diff --git a/commercetools.Sdk/IntegrationTests/commercetools.Api.IntegrationTests/MultipleClientsTest.cs b/commercetools.Sdk/IntegrationTests/commercetools.Api.IntegrationTests/MultipleClientsTest.cs index 9261eb2eb4f..37caa426c07 100644 --- a/commercetools.Sdk/IntegrationTests/commercetools.Api.IntegrationTests/MultipleClientsTest.cs +++ b/commercetools.Sdk/IntegrationTests/commercetools.Api.IntegrationTests/MultipleClientsTest.cs @@ -1,6 +1,8 @@ +using System.Collections.Generic; using System.Linq; using commercetools.Api.IntegrationTests; using commercetools.Base.Client; +using commercetools.Base.Client.Middlewares; using commercetools.Sdk.Api; using commercetools.Sdk.Api.Extensions; using commercetools.Sdk.ImportApi.Extensions; @@ -82,6 +84,5 @@ public async void api_and_import_create_root() } - } } \ No newline at end of file diff --git a/commercetools.Sdk/IntegrationTests/commercetools.Api.IntegrationTests/ServiceProviderFixture.cs b/commercetools.Sdk/IntegrationTests/commercetools.Api.IntegrationTests/ServiceProviderFixture.cs index 638c169ffae..65087d5dcab 100644 --- a/commercetools.Sdk/IntegrationTests/commercetools.Api.IntegrationTests/ServiceProviderFixture.cs +++ b/commercetools.Sdk/IntegrationTests/commercetools.Api.IntegrationTests/ServiceProviderFixture.cs @@ -1,6 +1,8 @@ using System; +using System.Collections.Generic; using commercetools.Sdk.Api.Models.Errors; using commercetools.Base.Client; +using commercetools.Base.Client.Error; using commercetools.Sdk.Api; using commercetools.Sdk.Api.Serialization; using Microsoft.Extensions.Configuration; @@ -9,7 +11,7 @@ namespace commercetools.Api.IntegrationTests { - public class ServiceProviderFixture + public sealed class ServiceProviderFixture { private readonly ServiceProvider serviceProvider; private readonly IConfiguration configuration; @@ -20,20 +22,44 @@ public ServiceProviderFixture() //services.AddLogging(configure => configure.AddConsole()); this.configuration = new ConfigurationBuilder(). + AddInMemoryCollection( + new Dictionary() + { + { "Logging:LogLevel:commercetoolsLoggerHandler", "Warning"}, + { "Logging:LogLevel:System.Net.Http.HttpClient", "Warning"}, + }). AddJsonFile("appsettings.test.Development.json", true). AddEnvironmentVariables(). AddUserSecrets(). AddEnvironmentVariables("CTP_"). Build(); - var useStreamClient = Enum.Parse(configuration.GetValue("ClientType", "String")) == ClientType.Stream; + var useStreamClient = Enum.Parse(configuration.GetValue("ClientType", "String")) != ClientType.String; services.UseCommercetoolsApi(configuration, "Client", options: new ClientOptions { ReadResponseAsStream = useStreamClient }); + services.AddLogging(c => c.AddConfiguration(configuration.GetSection("Logging"))); services.AddLogging(c => c.AddProvider(new InMemoryLoggerProvider())); + services.AddLogging(c => c.AddSimpleConsole(o => + { + o.UseUtcTimestamp = true; + o.IncludeScopes = true; + o.TimestampFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss.FFFFFFK "; + o.SingleLine = true; + })); services.SetupClient( "MeClient", errorTypeMapper => typeof(ErrorResponse), s => s.GetService() ); + services.AddSingleton(new LoggerHandlerOptions() + { + ResponseLogEvent = LogLevel.Information, + DefaultExceptionLogEvent = LogLevel.Warning, + ExceptionLogEvents = new Dictionary() + { + { typeof(NotFoundException), LogLevel.Information }, + { typeof(ConcurrentModificationException), LogLevel.Information} + } + }); this.serviceProvider = services.BuildServiceProvider(); //set default ProjectKey diff --git a/commercetools.Sdk/IntegrationTests/commercetools.GraphQL.Api.IntegrationTests/ServiceProviderFixture.cs b/commercetools.Sdk/IntegrationTests/commercetools.GraphQL.Api.IntegrationTests/ServiceProviderFixture.cs index 968da45207a..a8a54b7a9c1 100644 --- a/commercetools.Sdk/IntegrationTests/commercetools.GraphQL.Api.IntegrationTests/ServiceProviderFixture.cs +++ b/commercetools.Sdk/IntegrationTests/commercetools.GraphQL.Api.IntegrationTests/ServiceProviderFixture.cs @@ -8,7 +8,7 @@ namespace commercetools.GraphQL.Api.IntegrationTests { - public class ServiceProviderFixture + public sealed class ServiceProviderFixture { private readonly ServiceProvider serviceProvider; private readonly IConfiguration configuration; @@ -24,7 +24,7 @@ public ServiceProviderFixture() AddUserSecrets(). AddEnvironmentVariables("CTP_"). Build(); - var useStreamClient = Enum.Parse(configuration.GetValue("ClientType", "String")) == ClientType.Stream; + var useStreamClient = Enum.Parse(configuration.GetValue("ClientType", "String")) != ClientType.String; services.UseCommercetoolsApi(configuration, "Client", options: new ClientOptions { ReadResponseAsStream = useStreamClient }); services.AddLogging(c => c.AddProvider(new InMemoryLoggerProvider())); diff --git a/commercetools.Sdk/IntegrationTests/commercetools.ImportApi.IntegrationTests/ServiceProviderFixture.cs b/commercetools.Sdk/IntegrationTests/commercetools.ImportApi.IntegrationTests/ServiceProviderFixture.cs index 804dcbe858d..370c2fea1aa 100644 --- a/commercetools.Sdk/IntegrationTests/commercetools.ImportApi.IntegrationTests/ServiceProviderFixture.cs +++ b/commercetools.Sdk/IntegrationTests/commercetools.ImportApi.IntegrationTests/ServiceProviderFixture.cs @@ -6,7 +6,7 @@ namespace commercetools.ImportApi.IntegrationTests { - public class ServiceProviderFixture + public sealed class ServiceProviderFixture { private readonly ServiceProvider serviceProvider; private readonly IConfiguration configuration; @@ -22,7 +22,7 @@ public ServiceProviderFixture() AddEnvironmentVariables("CTP_"). Build(); - var useStreamClient = Enum.Parse(configuration.GetValue("ClientType", "String")) == ClientType.Stream; + var useStreamClient = Enum.Parse(configuration.GetValue("ClientType", "String")) != ClientType.String; services.UseCommercetoolsImportApi(configuration, "ImportClient", options: new ClientOptions { ReadResponseAsStream = useStreamClient }); this.serviceProvider = services.BuildServiceProvider(); diff --git a/commercetools.Sdk/Tests/commercetools.Sdk.Api.Tests/ClientsFactoryTests.cs b/commercetools.Sdk/Tests/commercetools.Sdk.Api.Tests/ClientsFactoryTests.cs index 02a87e383c9..f69cd29ce24 100644 --- a/commercetools.Sdk/Tests/commercetools.Sdk.Api.Tests/ClientsFactoryTests.cs +++ b/commercetools.Sdk/Tests/commercetools.Sdk.Api.Tests/ClientsFactoryTests.cs @@ -62,6 +62,47 @@ public void TestUserAgent() Assert.Equal("commercetools-sdk-dotnet-v2", agent.Product.Name); } + [Fact] + public void TestClientConfigValidationBuilder() + { + //arrange + var s = new ServiceCollection(); + s.UseCommercetoolsApiSerialization(); + var p = s.BuildServiceProvider(); + var serializerService = p.GetService(); + var clientConfig = new ClientConfiguration + { + ClientId = "ClientId", + ClientSecret = "ClientSecret", + ProjectKey = "test", + ApiBaseAddress = "https://api.europe-west1.gcp.commercetools.com", + AuthorizationBaseAddress = "https://auth.europe-west1.gcp.commercetools.com/" + }; + + //act + Exception validationEx = null; + try + { + var tokenProvider = TokenProviderFactory + .CreateClientCredentialsTokenProvider(clientConfig, null); + + new ClientBuilder { + ClientName = "test", + ClientConfiguration = clientConfig, + HttpClient = null, + SerializerService = serializerService, + TokenProvider = tokenProvider}.Build(); + } + catch (Exception e) + { + validationEx = e; + } + + //assert + Assert.NotNull(validationEx); + Assert.IsType(validationEx); + } + [Fact] public void TestClientConfigValidation() { diff --git a/commercetools.Sdk/commercetools.Base.Client/ApiMethod.cs b/commercetools.Sdk/commercetools.Base.Client/ApiMethod.cs index f75ad5a50fb..8249767798b 100644 --- a/commercetools.Sdk/commercetools.Base.Client/ApiMethod.cs +++ b/commercetools.Sdk/commercetools.Base.Client/ApiMethod.cs @@ -74,7 +74,6 @@ public virtual HttpRequestMessage Build() { var requestPath = new Uri(RequestUrl + ToQueryString(QueryParams), UriKind.Relative); var request = new HttpRequestMessage(); - request.Version = HttpVersion.Version20; request.Method = this.Method; request.RequestUri = requestPath; request.AddHeaders(Headers); diff --git a/commercetools.Sdk/commercetools.Base.Client/ClientBuilder.cs b/commercetools.Sdk/commercetools.Base.Client/ClientBuilder.cs new file mode 100644 index 00000000000..b358faa77b4 --- /dev/null +++ b/commercetools.Sdk/commercetools.Base.Client/ClientBuilder.cs @@ -0,0 +1,88 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Net.Http; +using commercetools.Base.Client.Middlewares; +using commercetools.Base.Client.Tokens; +using commercetools.Base.Serialization; + +namespace commercetools.Base.Client; + +public class ClientBuilder +{ + public string ClientName { get; set; } + public IClientConfiguration ClientConfiguration { get; set; } + public ISerializerService SerializerService { get; set; } + public ITokenProvider TokenProvider { get; set; } + public bool ReadResponseAsStream { get; set; } = true; + public ICorrelationIdProvider CorrelationIdProvider { get; set; } + + [Obsolete("Set default HttpVersion in HttpClient instead")] + public Version HttpVersion { get; set; } + public IEnumerable Middlewares { get; set; } = new List(); + public HttpClient HttpClient { get; set; } + + public IClient Build() + { + Validator.ValidateObject(ClientConfiguration, new ValidationContext(ClientConfiguration), true); + if (ReadResponseAsStream && SerializerService is IStreamSerializerService streamSerializer) + { + return new StreamCtpClient( + CreateMiddlewareStack(ClientConfiguration, HttpClient, TokenProvider, Middlewares, true, CorrelationIdProvider, HttpVersion), + streamSerializer, + ClientName + ); + } + return new CtpClient( + CreateMiddlewareStack(ClientConfiguration, HttpClient, TokenProvider, Middlewares, false, CorrelationIdProvider, HttpVersion), + SerializerService, + ClientName + ); + } + + public static Middleware CreateMiddlewareStack(IClientConfiguration configuration, + HttpClient httpClient, ITokenProvider tokenProvider, IEnumerable middlewares, bool readResponseAsStream = false, ICorrelationIdProvider correlationIdProvider = null, Version httpVersion = null) + { + httpClient.BaseAddress = new Uri(configuration.ApiBaseAddress); + + List handlers = new List() + { + CreateAuthMiddleware(tokenProvider), + CreateCorrelationIdMiddleware( + correlationIdProvider ?? new DefaultCorrelationIdProvider(configuration) + ) + }; + if (httpVersion != null) + { + handlers.Add(CreateVersionMiddleware(httpVersion)); + } + if (middlewares != null) + handlers.AddRange(middlewares); + + var httpMiddleware = + readResponseAsStream ? (DelegatingMiddleware)new StreamHttpMiddleware(httpClient) : new HttpMiddleware(httpClient); + foreach (var handler in handlers) + { + handler.InnerMiddleware = httpMiddleware; + httpMiddleware = handler; + } + + return httpMiddleware; + } + + public static AuthorizationMiddleware CreateAuthMiddleware(ITokenProvider tokenProvider) + { + return new AuthorizationMiddleware(tokenProvider); + } + + public static CorrelationIdMiddleware CreateCorrelationIdMiddleware( + ICorrelationIdProvider correlationIdProvider) + { + return new CorrelationIdMiddleware(correlationIdProvider); + } + + public static VersionMiddleware CreateVersionMiddleware(Version httpVersion) + { + return new VersionMiddleware(httpVersion); + } +} \ No newline at end of file diff --git a/commercetools.Sdk/commercetools.Base.Client/ClientFactory.cs b/commercetools.Sdk/commercetools.Base.Client/ClientFactory.cs index 7b52ef32b57..4f634a9c206 100644 --- a/commercetools.Sdk/commercetools.Base.Client/ClientFactory.cs +++ b/commercetools.Sdk/commercetools.Base.Client/ClientFactory.cs @@ -8,8 +8,10 @@ namespace commercetools.Base.Client { + [Obsolete("Use ClientBuilder instead")] public class ClientFactory { + [Obsolete("Use ClientBuilder instead")] public static IClient Create( string clientName, IClientConfiguration configuration, @@ -20,65 +22,44 @@ public static IClient Create( ICorrelationIdProvider correlationIdProvider = null, Version httpVersion = null) { - Validator.ValidateObject(configuration, new ValidationContext(configuration), true); - if (readResponseAsStream && serializerService is IStreamSerializerService streamSerializer) - { - return new StreamCtpClient( - CreateMiddlewareStack(clientName, configuration, factory, tokenProvider, true, correlationIdProvider, httpVersion), - streamSerializer, - clientName - ); - } - return new CtpClient( - CreateMiddlewareStack(clientName, configuration, factory, tokenProvider, false, correlationIdProvider, httpVersion), - serializerService, - clientName - ); + return new ClientBuilder() + { + ClientName = clientName, + ClientConfiguration = configuration, + HttpClient = factory.CreateClient(clientName), + SerializerService = serializerService, + TokenProvider = tokenProvider, + ReadResponseAsStream = readResponseAsStream, + CorrelationIdProvider = correlationIdProvider, + HttpVersion = httpVersion + }.Build(); } + [Obsolete("Use ClientBuilder.CreateMiddlewareStack instead")] public static Middleware CreateMiddlewareStack(string clientName, IClientConfiguration configuration, IHttpClientFactory factory, ITokenProvider tokenProvider, bool readResponseAsStream = false, ICorrelationIdProvider correlationIdProvider = null, Version httpVersion = null) { - var httpClient = factory.CreateClient(clientName); - httpClient.BaseAddress = new Uri(configuration.ApiBaseAddress); - - List handlers = new List() - { - CreateAuthMiddleware(tokenProvider), - CreateCorrelationIdMiddleware( - correlationIdProvider ?? new DefaultCorrelationIdProvider(configuration) - ) - }; - if (httpVersion != null) - { - handlers.Add(CreateVersionMiddleware(httpVersion)); - } - - var httpMiddleware = - readResponseAsStream ? (DelegatingMiddleware)new StreamHttpMiddleware(httpClient) : new HttpMiddleware(httpClient); - foreach (var handler in handlers) - { - handler.InnerMiddleware = httpMiddleware; - httpMiddleware = handler; - } - - return httpMiddleware; + return ClientBuilder.CreateMiddlewareStack(configuration, factory.CreateClient(clientName), tokenProvider, new List(), + readResponseAsStream, correlationIdProvider, httpVersion); } + [Obsolete("Use ClientBuilder.CreateAuthMiddleware instead")] public static AuthorizationMiddleware CreateAuthMiddleware(ITokenProvider tokenProvider) { - return new AuthorizationMiddleware(tokenProvider); + return ClientBuilder.CreateAuthMiddleware(tokenProvider); } + [Obsolete("Use ClientBuilder.CreateCorrelationIdMiddleware instead")] public static CorrelationIdMiddleware CreateCorrelationIdMiddleware( ICorrelationIdProvider correlationIdProvider) { - return new CorrelationIdMiddleware(correlationIdProvider); + return ClientBuilder.CreateCorrelationIdMiddleware(correlationIdProvider); } + [Obsolete("Use ClientBuilder.CreateVersionMiddleware instead")] public static VersionMiddleware CreateVersionMiddleware(Version httpVersion) { - return new VersionMiddleware(httpVersion); + return ClientBuilder.CreateVersionMiddleware(httpVersion); } } } \ No newline at end of file diff --git a/commercetools.Sdk/commercetools.Base.Client/ClientOptions.cs b/commercetools.Sdk/commercetools.Base.Client/ClientOptions.cs index 892624aadb5..fcd09b39cf3 100644 --- a/commercetools.Sdk/commercetools.Base.Client/ClientOptions.cs +++ b/commercetools.Sdk/commercetools.Base.Client/ClientOptions.cs @@ -8,8 +8,8 @@ public class ClientOptions public DecompressionMethods DecompressionMethods { get; set; } = DecompressionMethods.Deflate | DecompressionMethods.GZip; - public bool ReadResponseAsStream { get; set; } = false; + public bool ReadResponseAsStream { get; set; } = true; - public Version UseHttpVersion { get; set; } = null; + public Version UseHttpVersion { get; set; } = HttpVersion.Version20; } } \ No newline at end of file diff --git a/commercetools.Sdk/commercetools.Base.Client/CtpClient.cs b/commercetools.Sdk/commercetools.Base.Client/CtpClient.cs index 9e3e8bc33c5..efced614b22 100644 --- a/commercetools.Sdk/commercetools.Base.Client/CtpClient.cs +++ b/commercetools.Sdk/commercetools.Base.Client/CtpClient.cs @@ -46,6 +46,10 @@ public async Task ExecuteAsJsonAsync(HttpRequestMessage requestMessage, public async Task> SendAsync(HttpRequestMessage requestMessage, CancellationToken cancellationToken = default) { var result = await SendAsJsonAsync(requestMessage, cancellationToken); + if (string.IsNullOrEmpty(result.Body)) + { + return new ApiResponse(result.StatusCode, result.ReasonPhrase, result.HttpHeaders, default); + } var body = this.SerializerService.Deserialize(result.Body); return new ApiResponse(result.StatusCode, result.ReasonPhrase, result.HttpHeaders, body); } diff --git a/commercetools.Sdk/commercetools.Base.Client/DefaultHttpLogger.cs b/commercetools.Sdk/commercetools.Base.Client/DefaultHttpLogger.cs new file mode 100644 index 00000000000..b0aa49e7b51 --- /dev/null +++ b/commercetools.Sdk/commercetools.Base.Client/DefaultHttpLogger.cs @@ -0,0 +1,111 @@ +using System; +using System.Linq; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; + +namespace commercetools.Base.Client +{ + public class DefaultHttpLogger : IHttpLogger + { + public async Task LogRequestBody(ILogger logger, LogLevel logLevel, HttpRequestMessage request) + { + if (logger.IsEnabled(logLevel)) + { + var body = await (request.Content?.ReadAsStringAsync() ?? Task.FromResult("")); + logger.Log(logLevel, "{HttpMethod} {Uri} {Headers} {Body}", request.Method.Method, + request.RequestUri.AbsoluteUri, RedactAuthorizationHeader(request.Headers), SecuredBody(body)); + } + } + + public async Task LogResponseBody(ILogger logger, LogLevel logLevel, HttpRequestMessage request, HttpResponseMessage response, long elapsed) + { + if (logger.IsEnabled(logLevel)) + { + var body = await (response.Content?.ReadAsStringAsync() ?? Task.FromResult("")); + logger.Log(logLevel, "{HttpMethod} {Uri} {StatusCode} {Timing} {Headers} {Body}", request.Method.Method, + request.RequestUri.AbsoluteUri, (int)response.StatusCode, elapsed, RedactAuthorizationHeader(request.Headers), SecuredBody(body)); + } + } + + public void Log(ILogger logger, LogLevel logLevel, HttpRequestMessage request) + { + if (logger.IsEnabled(logLevel)) + { + logger.Log(logLevel, "{HttpMethod} {Uri} {Headers}", request.Method.Method, + request.RequestUri.AbsoluteUri, RedactAuthorizationHeader(request.Headers)); + } + } + + public void Log(ILogger logger, LogLevel level, HttpRequestMessage request, HttpResponseMessage response, long elapsed) + { + if (logger.IsEnabled(level)) + { + logger.Log(level, "{HttpMethod} {Uri} {StatusCode} {Timing} {CorrelationId} {ServerTiming}", request.Method.Method, + request.RequestUri.AbsoluteUri, (int)response.StatusCode, elapsed, GetCorrelationId(response.Headers), GetServerTiming(response.Headers)); + } + } + + public void Log(ILogger logger, LogLevel logLevel, HttpRequestMessage request, ApiHttpException exception, long elapsed) + { + if (logger.IsEnabled(logLevel)) + { + logger.Log(logLevel, "{HttpMethod} {Uri} {StatusCode} {Timing} {CorrelationId} {ServerTiming}", request.Method.Method, + request.RequestUri.AbsoluteUri, exception.StatusCode, elapsed, GetCorrelationId(exception.Headers), GetServerTiming(exception.Headers)); + } + } + + + private static string RedactAuthorizationHeader(HttpRequestHeaders headers) + { + var headString = from header in headers + where header.Key.ToLower() != "authorization" + select header.Key + ": " + string.Join(", ", header.Value); + + return "[" + string.Join(", ", headString) + "]"; + } + + private static string SecuredBody(string body) + { + if (body != null) + return Regex.Replace(body, "(\"\\w*([Pp]ass|access_token|refresh_token)\\w*\"):\\W*\"[^\"]*\"", + "$1:\"**removed from output**\""); + return null; + } + + private static string GetCorrelationId(ApiHttpHeaders headers) + { + return headers.GetFirst("X-Correlation-Id") ?? "-"; + } + + private static string GetCorrelationId(HttpResponseHeaders headers) + { + return GetHeader(headers, "X-Correlation-ID"); + } + + private static string GetHeader(HttpResponseHeaders headers, string headerName) + { + var headerValue = "-"; + + if (headers.TryGetValues(headerName, out var values)) + { + headerValue = values.First(); + } + + return headerValue; + } + + private static string GetServerTiming(HttpResponseHeaders headers) + { + return GetHeader(headers, "Server-Timing"); + } + + private static string GetServerTiming(ApiHttpHeaders headers) + { + return headers.GetFirst("Server-Timing") ?? "-"; + } + + } +} \ No newline at end of file diff --git a/commercetools.Sdk/commercetools.Base.Client/DependencyInjectionSetup.cs b/commercetools.Sdk/commercetools.Base.Client/DependencyInjectionSetup.cs index 114cb3d91da..151eb445aa2 100644 --- a/commercetools.Sdk/commercetools.Base.Client/DependencyInjectionSetup.cs +++ b/commercetools.Sdk/commercetools.Base.Client/DependencyInjectionSetup.cs @@ -25,15 +25,16 @@ public static IDictionary UseHttpApi(this IServiceCo Func serializerFactory, Func errorResponseTypeMapper, Func tokenProviderSupplier, - ClientOptions options = null) + ClientOptions options = null, + IEnumerable middlewares = null) { options ??= new ClientOptions(); if (clients.Count() == 1) { - return services.UseSingleClient(configuration, clients.First(), serializerFactory, errorResponseTypeMapper, tokenProviderSupplier, options); + return services.UseSingleClient(configuration, clients.First(), serializerFactory, errorResponseTypeMapper, tokenProviderSupplier, options, middlewares); } - return services.UseMultipleClients(configuration, clients, serializerFactory, errorResponseTypeMapper, tokenProviderSupplier, options); + return services.UseMultipleClients(configuration, clients, serializerFactory, errorResponseTypeMapper, tokenProviderSupplier, options, middlewares); } private static IDictionary UseMultipleClients(this IServiceCollection services, @@ -41,7 +42,8 @@ private static IDictionary UseMultipleClients(this I Func serializerFactory, Func errorResponseTypeMapper, Func tokenProviderSupplier, - ClientOptions options) + ClientOptions options, + IEnumerable middlewares = null) { var builders = new ConcurrentDictionary(); @@ -55,14 +57,17 @@ private static IDictionary UseMultipleClients(this I builders.TryAdd(clientName, services.SetupClient(clientName, errorResponseTypeMapper, serializerFactory, options)); services.AddSingleton(serviceProvider => { - var client = ClientFactory.Create(clientName, clientConfiguration, - serviceProvider.GetService(), - serializerFactory(serviceProvider), - tokenProviderSupplier(clientName, configuration, serviceProvider), - options.ReadResponseAsStream, - serviceProvider.GetService(), - options.UseHttpVersion); - client.Name = clientName; + + var client = new ClientBuilder { + ClientName = clientName, + ClientConfiguration = clientConfiguration, + HttpClient = serviceProvider.GetService().CreateClient(clientName), + SerializerService = serializerFactory(serviceProvider), + TokenProvider = tokenProviderSupplier(clientName, configuration, serviceProvider), + ReadResponseAsStream = options.ReadResponseAsStream, + CorrelationIdProvider = serviceProvider.GetService(), + Middlewares = middlewares + }.Build(); return client; }); }); @@ -75,20 +80,24 @@ private static IDictionary UseSingleClient(this ISer Func serializerFactory, Func errorResponseTypeMapper, Func tokenProviderSupplier, - ClientOptions options) + ClientOptions options, + IEnumerable middlewares = null) { IClientConfiguration clientConfiguration = configuration.GetSection(clientName).Get(); Validator.ValidateObject(clientConfiguration, new ValidationContext(clientConfiguration), true); services.AddSingleton(serviceProvider => { - var client = ClientFactory.Create(clientName, clientConfiguration, - serviceProvider.GetService(), - serializerFactory(serviceProvider), - tokenProviderSupplier(clientName, configuration, serviceProvider), - options.ReadResponseAsStream, - serviceProvider.GetService(), - options.UseHttpVersion); + var client = new ClientBuilder { + ClientName = clientName, + ClientConfiguration = clientConfiguration, + HttpClient = serviceProvider.GetService().CreateClient(clientName), + SerializerService = serializerFactory(serviceProvider), + TokenProvider = tokenProviderSupplier(clientName, configuration, serviceProvider), + ReadResponseAsStream = options.ReadResponseAsStream, + CorrelationIdProvider = serviceProvider.GetService(), + Middlewares = middlewares + }.Build(); client.Name = clientName; return client; @@ -105,6 +114,8 @@ public static IHttpClientBuilder SetupClient(this IServiceCollection services, s options ??= new ClientOptions(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); var httpClientBuilder = services.AddHttpClient(clientName) .ConfigureHttpClient((provider, client) => { @@ -112,22 +123,27 @@ public static IHttpClientBuilder SetupClient(this IServiceCollection services, s { client.DefaultRequestHeaders.AcceptEncoding.ParseAdd("gzip"); } + if (options.DecompressionMethods.HasFlag(DecompressionMethods.Deflate)) { client.DefaultRequestHeaders.AcceptEncoding.ParseAdd("deflate"); } + + client.DefaultRequestVersion = options.UseHttpVersion; + var userAgentProvider = provider.GetService() ?? new UserAgentProvider(); client.DefaultRequestHeaders.UserAgent.ParseAdd(userAgentProvider.UserAgent); }) .ConfigureHttpMessageHandlerBuilder(builder => { - builder.PrimaryHandler = new HttpClientHandler + builder.PrimaryHandler = new SocketsHttpHandler() { AutomaticDecompression = options.DecompressionMethods }; }) - .AddHttpMessageHandler(c => new ErrorHandler(message => serializerFactory(c).Deserialize(errorResponseTypeMapper(message), message.ExtractResponseBody()))) - .AddHttpMessageHandler(c => c.GetService().Create()); + .AddHttpMessageHandler(c => c.GetService().Create()) + .AddHttpMessageHandler(c => new ErrorHandler(message => + serializerFactory(c).Deserialize(errorResponseTypeMapper(message), message.ExtractResponseBody()))); return httpClientBuilder; } diff --git a/commercetools.Sdk/commercetools.Base.Client/EmptyContent.cs b/commercetools.Sdk/commercetools.Base.Client/EmptyContent.cs new file mode 100644 index 00000000000..53bfdb28a29 --- /dev/null +++ b/commercetools.Sdk/commercetools.Base.Client/EmptyContent.cs @@ -0,0 +1,10 @@ +using System.Net.Http; + +namespace commercetools.Base.Client; + +public class EmptyContent : StringContent +{ + public EmptyContent() : base(string.Empty) + { + } +} \ No newline at end of file diff --git a/commercetools.Sdk/commercetools.Base.Client/ErrorHandler.cs b/commercetools.Sdk/commercetools.Base.Client/ErrorHandler.cs index b16b090fd39..04ab0e059b2 100644 --- a/commercetools.Sdk/commercetools.Base.Client/ErrorHandler.cs +++ b/commercetools.Sdk/commercetools.Base.Client/ErrorHandler.cs @@ -7,20 +7,20 @@ namespace commercetools.Base.Client { public class ErrorHandler : DelegatingHandler { - private Func errorResponseBodyMapper; - - public ErrorHandler(Func errorResponseBodyMapper) + private readonly Func _errorResponseBodyMapper; + + public ErrorHandler(Func errorResponseBodyMapper) { - this.errorResponseBodyMapper = errorResponseBodyMapper; + this._errorResponseBodyMapper = errorResponseBodyMapper; } protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { var response = await base.SendAsync(request, cancellationToken).ConfigureAwait(false); - if (response != null && !response.IsSuccessStatusCode) + if (response is { IsSuccessStatusCode: false }) { - var exception = ExceptionFactory.Create(request, response, errorResponseBodyMapper); + var exception = ExceptionFactory.Create(request, response, _errorResponseBodyMapper); throw exception; } diff --git a/commercetools.Sdk/commercetools.Base.Client/IHttpLogger.cs b/commercetools.Sdk/commercetools.Base.Client/IHttpLogger.cs new file mode 100644 index 00000000000..8a4297f3ebe --- /dev/null +++ b/commercetools.Sdk/commercetools.Base.Client/IHttpLogger.cs @@ -0,0 +1,18 @@ +using System.Net.Http; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; + +namespace commercetools.Base.Client +{ + public interface IHttpLogger + { + void Log(ILogger logger, LogLevel level, HttpRequestMessage request); + void Log(ILogger logger, LogLevel level, HttpRequestMessage request, HttpResponseMessage response, long elapsed); + void Log(ILogger logger, LogLevel logLevel, HttpRequestMessage request, ApiHttpException exception, long elapsed); + + Task LogRequestBody(ILogger logger, LogLevel logLevel, HttpRequestMessage request); + + Task LogResponseBody(ILogger logger, LogLevel logLevel, HttpRequestMessage request, + HttpResponseMessage response, long elapsed); + } +} \ No newline at end of file diff --git a/commercetools.Sdk/commercetools.Base.Client/ILoggerHandlerOptions.cs b/commercetools.Sdk/commercetools.Base.Client/ILoggerHandlerOptions.cs new file mode 100644 index 00000000000..778b351c5e9 --- /dev/null +++ b/commercetools.Sdk/commercetools.Base.Client/ILoggerHandlerOptions.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using Microsoft.Extensions.Logging; + +namespace commercetools.Base.Client +{ + public interface ILoggerHandlerOptions + { + LogLevel ResponseLogEvent { get; } + LogLevel DefaultExceptionLogEvent { get; } + Dictionary ExceptionLogEvents { get; } + } +} \ No newline at end of file diff --git a/commercetools.Sdk/commercetools.Base.Client/LoggerHandler.cs b/commercetools.Sdk/commercetools.Base.Client/LoggerHandler.cs index 42dcaea4f98..3b3f0626fc1 100644 --- a/commercetools.Sdk/commercetools.Base.Client/LoggerHandler.cs +++ b/commercetools.Sdk/commercetools.Base.Client/LoggerHandler.cs @@ -1,4 +1,6 @@ using System; +using System.Diagnostics; +using System.Linq; using System.Net.Http; using System.Threading; using System.Threading.Tasks; @@ -8,32 +10,54 @@ namespace commercetools.Base.Client { public class LoggerHandler : DelegatingHandler { - private readonly ILoggerFactory loggerFactory; + private readonly ILoggerFactory _loggerFactory; - public LoggerHandler(ILoggerFactory loggerFactory) + private readonly IHttpLogger _httpLogger; + + private readonly ILoggerHandlerOptions _loggerHandlerOptions; + + public LoggerHandler(ILoggerFactory loggerFactory, IHttpLogger httpLogger = null, ILoggerHandlerOptions options = null) { - this.loggerFactory = loggerFactory; + _loggerFactory = loggerFactory; + _httpLogger = httpLogger ?? new DefaultHttpLogger(); + _loggerHandlerOptions = options ?? new LoggerHandlerOptions(); } protected override async Task SendAsync( HttpRequestMessage request, CancellationToken cancellationToken) { - var logger = this.loggerFactory.CreateLogger("commercetoolsLoggerHandler"); + var logger = _loggerFactory.CreateLogger("commercetoolsLoggerHandler"); if (request == null) { throw new ArgumentNullException(nameof(request)); } - using (Log.BeginRequestPipelineScope(logger, request)) + _httpLogger.Log(logger, LogLevel.Debug, request); + await _httpLogger.LogRequestBody(logger, LogLevel.Trace, request).ConfigureAwait(false); + var watch = Stopwatch.StartNew(); + try { - Log.RequestPipelineStart(logger, request); var response = await base.SendAsync(request, cancellationToken).ConfigureAwait(false); - Log.RequestPipelineEnd(logger, response); - + watch.Stop(); + + _httpLogger.Log(logger, (int)response.StatusCode < 400 ? _loggerHandlerOptions.ResponseLogEvent : _loggerHandlerOptions.DefaultExceptionLogEvent, request, response, watch.ElapsedMilliseconds); + await _httpLogger.LogResponseBody(logger, LogLevel.Trace, request, response, watch.ElapsedMilliseconds); + return response; } + catch (ApiHttpException e) + { + watch.Stop(); + var defaultLevel = _loggerHandlerOptions.DefaultExceptionLogEvent; + var exceptionLevel = _loggerHandlerOptions.ExceptionLogEvents + .Where(pair => pair.Key.IsInstanceOfType(e)) + .Select(pair => pair.Value) + .ToArray(); + _httpLogger.Log(logger, exceptionLevel.Any() ? exceptionLevel.First() : defaultLevel, request, e, watch.ElapsedMilliseconds); + throw; + } } } } \ No newline at end of file diff --git a/commercetools.Sdk/commercetools.Base.Client/LoggerHandlerFactory.cs b/commercetools.Sdk/commercetools.Base.Client/LoggerHandlerFactory.cs index aa0543190ab..c78e977813f 100644 --- a/commercetools.Sdk/commercetools.Base.Client/LoggerHandlerFactory.cs +++ b/commercetools.Sdk/commercetools.Base.Client/LoggerHandlerFactory.cs @@ -5,16 +5,22 @@ namespace commercetools.Base.Client { public class LoggerHandlerFactory : ILoggerHandlerFactory { - private readonly ILoggerFactory loggerFactory; + private readonly ILoggerFactory _loggerFactory; - public LoggerHandlerFactory(ILoggerFactory loggerFactory) + private readonly IHttpLogger _httpLogger; + + private readonly ILoggerHandlerOptions _handlerOptions; + + public LoggerHandlerFactory(ILoggerFactory loggerFactory, IHttpLogger httpLogger = null, ILoggerHandlerOptions options = null) { - this.loggerFactory = loggerFactory; + _loggerFactory = loggerFactory; + _httpLogger = httpLogger; + _handlerOptions = options; } public DelegatingHandler Create() { - return new LoggerHandler(loggerFactory); + return new LoggerHandler(_loggerFactory, _httpLogger, _handlerOptions); } } } \ No newline at end of file diff --git a/commercetools.Sdk/commercetools.Base.Client/LoggerHandlerOptions.cs b/commercetools.Sdk/commercetools.Base.Client/LoggerHandlerOptions.cs new file mode 100644 index 00000000000..2d2488e13fc --- /dev/null +++ b/commercetools.Sdk/commercetools.Base.Client/LoggerHandlerOptions.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using commercetools.Base.Client.Error; +using Microsoft.Extensions.Logging; + +namespace commercetools.Base.Client +{ + public class LoggerHandlerOptions : ILoggerHandlerOptions + { + public LogLevel ResponseLogEvent { get; set; } = LogLevel.Information; + public LogLevel DefaultExceptionLogEvent { get; set; } = LogLevel.Error; + public Dictionary ExceptionLogEvents { get; set; } = new Dictionary() + { + { typeof(NotFoundException), LogLevel.Information } + }; + } +} \ No newline at end of file diff --git a/commercetools.Sdk/commercetools.Base.Client/Middlewares/NotFoundMiddleware.cs b/commercetools.Sdk/commercetools.Base.Client/Middlewares/NotFoundMiddleware.cs new file mode 100644 index 00000000000..d49b923ee96 --- /dev/null +++ b/commercetools.Sdk/commercetools.Base.Client/Middlewares/NotFoundMiddleware.cs @@ -0,0 +1,28 @@ +using System.Net; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using commercetools.Base.Client.Error; + +namespace commercetools.Base.Client.Middlewares; + +public class NotFoundMiddleware: DelegatingMiddleware +{ + protected internal override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + try + { + return await base.SendAsync(request, cancellationToken).ConfigureAwait(false); + } + catch (NotFoundException exception) + { + return new HttpResponseMessage() + { + StatusCode = HttpStatusCode.NotFound, + Content = new EmptyContent(), + ReasonPhrase = exception.Message, + RequestMessage = request, + }; + } + } +} \ No newline at end of file diff --git a/commercetools.Sdk/commercetools.Base.Client/StreamCtpClient.cs b/commercetools.Sdk/commercetools.Base.Client/StreamCtpClient.cs index a7b1b2dc2f8..fc494e50bff 100644 --- a/commercetools.Sdk/commercetools.Base.Client/StreamCtpClient.cs +++ b/commercetools.Sdk/commercetools.Base.Client/StreamCtpClient.cs @@ -47,6 +47,11 @@ public async Task ExecuteAsJsonAsync(HttpRequestMessage requestMessage, public async Task> SendAsync(HttpRequestMessage requestMessage, CancellationToken cancellationToken = default) { using var result = await SendAsAsync(requestMessage, cancellationToken).ConfigureAwait(false); + if (result.Content is EmptyContent) + { + return new ApiResponse(result.StatusCode, result.ReasonPhrase, result.Headers, default); + } + await using var contentStream = await result.Content.ReadAsStreamAsync().ConfigureAwait(false); var content = _serializerService.Deserialize(contentStream); return new ApiResponse(result.StatusCode, result.ReasonPhrase, result.Headers, content); diff --git a/commercetools.Sdk/commercetools.Base.Client/commercetools.Base.Client.csproj b/commercetools.Sdk/commercetools.Base.Client/commercetools.Base.Client.csproj index b9ac765ac2f..bfa83f5e7bd 100644 --- a/commercetools.Sdk/commercetools.Base.Client/commercetools.Base.Client.csproj +++ b/commercetools.Sdk/commercetools.Base.Client/commercetools.Base.Client.csproj @@ -1,7 +1,7 @@ - netstandard2.1 + net6.0 0.1.0 https://github.com/commercetools/commercetools-dotnet-core-sdk-v2 The Composable Commerce SDK allows developers to work effectively by providing typesafe access to commercetools Composable Commerce in their .NET applications. diff --git a/commercetools.Sdk/commercetools.Base.Registration/commercetools.Base.Registration.csproj b/commercetools.Sdk/commercetools.Base.Registration/commercetools.Base.Registration.csproj index 13184c6655a..51f4492abc4 100644 --- a/commercetools.Sdk/commercetools.Base.Registration/commercetools.Base.Registration.csproj +++ b/commercetools.Sdk/commercetools.Base.Registration/commercetools.Base.Registration.csproj @@ -1,7 +1,7 @@ - netstandard2.1 + net6.0 0.1.0 https://github.com/commercetools/commercetools-dotnet-core-sdk-v2 The Composable Commerce SDK allows developers to work effectively by providing typesafe access to commercetools Composable Commerce in their .NET applications. diff --git a/commercetools.Sdk/commercetools.Base.Serialization/commercetools.Base.Serialization.csproj b/commercetools.Sdk/commercetools.Base.Serialization/commercetools.Base.Serialization.csproj index dce8c5d65a5..71f4c4c78b2 100644 --- a/commercetools.Sdk/commercetools.Base.Serialization/commercetools.Base.Serialization.csproj +++ b/commercetools.Sdk/commercetools.Base.Serialization/commercetools.Base.Serialization.csproj @@ -1,7 +1,7 @@ - netstandard2.1 + net6.0 0.1.0 https://github.com/commercetools/commercetools-dotnet-core-sdk-v2 The Composable Commerce SDK allows developers to work effectively by providing typesafe access to commercetools Composable Commerce in their .NET applications. diff --git a/commercetools.Sdk/commercetools.Sdk.Api/DependencyInjectionSetup.cs b/commercetools.Sdk/commercetools.Sdk.Api/DependencyInjectionSetup.cs index e25bd8394a1..c641f4b624d 100644 --- a/commercetools.Sdk/commercetools.Sdk.Api/DependencyInjectionSetup.cs +++ b/commercetools.Sdk/commercetools.Sdk.Api/DependencyInjectionSetup.cs @@ -8,6 +8,7 @@ using commercetools.Sdk.Api.Models.Errors; using commercetools.Sdk.Api.Models.Products; using commercetools.Base.Client; +using commercetools.Base.Client.Middlewares; using commercetools.Base.Client.Tokens; using commercetools.Base.Registration; using commercetools.Base.Serialization; @@ -25,7 +26,8 @@ public static IHttpClientBuilder UseCommercetoolsApi(this IServiceCollection ser string clientName = DefaultClientNames.Api, Func tokenProviderSupplier = null, ISerializationConfiguration serializationConfiguration = null, - ClientOptions options = null) + ClientOptions options = null, + IEnumerable middlewares = null) { var clients = new List() { @@ -38,14 +40,14 @@ public static IHttpClientBuilder UseCommercetoolsApi(this IServiceCollection ser services.AddSingleton(c => ApiFactory.Create(c.GetServices().Single(client => client.Name == clientName), clientConfiguration.ProjectKey)); } return services.UseCommercetoolsApi(configuration, clients, - tokenProviderSupplier ?? CreateDefaultTokenProvider, serializationConfiguration, options).Single().Value; + tokenProviderSupplier ?? CreateDefaultTokenProvider, serializationConfiguration, options, middlewares).Single().Value; } public static IDictionary UseCommercetoolsApi(this IServiceCollection services, IConfiguration configuration, IList clients, Func tokenProviderSupplier = null, ISerializationConfiguration serializationConfiguration = null, - ClientOptions options = null) + ClientOptions options = null, IEnumerable middlewares = null) { services.UseCommercetoolsApiSerialization(serializationConfiguration); @@ -60,7 +62,8 @@ public static IDictionary UseCommercetoolsApi(this I serviceProvider => serviceProvider.GetService(), message => typeof(ErrorResponse), tokenProviderSupplier ?? CreateDefaultTokenProvider, - options); + options, + middlewares); } public static void UseCommercetoolsApiSerialization(this IServiceCollection services, diff --git a/commercetools.Sdk/commercetools.Sdk.Api/commercetools.Sdk.Api.csproj b/commercetools.Sdk/commercetools.Sdk.Api/commercetools.Sdk.Api.csproj index da7f1051be6..ac64e18442f 100644 --- a/commercetools.Sdk/commercetools.Sdk.Api/commercetools.Sdk.Api.csproj +++ b/commercetools.Sdk/commercetools.Sdk.Api/commercetools.Sdk.Api.csproj @@ -1,7 +1,7 @@ - netstandard2.1 + net6.0 0.1.0 https://github.com/commercetools/commercetools-dotnet-core-sdk-v2 The Composable Commerce SDK allows developers to work effectively by providing typesafe access to commercetools Composable Commerce in their .NET applications. diff --git a/commercetools.Sdk/commercetools.Sdk.HistoryApi/commercetools.Sdk.HistoryApi.csproj b/commercetools.Sdk/commercetools.Sdk.HistoryApi/commercetools.Sdk.HistoryApi.csproj index 23ae2f9e721..f1f726a1162 100644 --- a/commercetools.Sdk/commercetools.Sdk.HistoryApi/commercetools.Sdk.HistoryApi.csproj +++ b/commercetools.Sdk/commercetools.Sdk.HistoryApi/commercetools.Sdk.HistoryApi.csproj @@ -1,7 +1,7 @@ - netstandard2.1 + net6.0 https://github.com/commercetools/commercetools-dotnet-core-sdk-v2 The Composable Commerce SDK allows developers to work effectively by providing typesafe access to commercetools Composable Commerce in their .NET applications. Copyright commercetools 2021 diff --git a/commercetools.Sdk/commercetools.Sdk.ImportApi/commercetools.Sdk.ImportApi.csproj b/commercetools.Sdk/commercetools.Sdk.ImportApi/commercetools.Sdk.ImportApi.csproj index 23ae2f9e721..f1f726a1162 100644 --- a/commercetools.Sdk/commercetools.Sdk.ImportApi/commercetools.Sdk.ImportApi.csproj +++ b/commercetools.Sdk/commercetools.Sdk.ImportApi/commercetools.Sdk.ImportApi.csproj @@ -1,7 +1,7 @@ - netstandard2.1 + net6.0 https://github.com/commercetools/commercetools-dotnet-core-sdk-v2 The Composable Commerce SDK allows developers to work effectively by providing typesafe access to commercetools Composable Commerce in their .NET applications. Copyright commercetools 2021 diff --git a/commercetools.Sdk/compat/commercetools.Sdk.V2Compat/commercetools.Sdk.V2Compat.csproj b/commercetools.Sdk/compat/commercetools.Sdk.V2Compat/commercetools.Sdk.V2Compat.csproj index 27284c67a1e..e6e3b7c2c51 100644 --- a/commercetools.Sdk/compat/commercetools.Sdk.V2Compat/commercetools.Sdk.V2Compat.csproj +++ b/commercetools.Sdk/compat/commercetools.Sdk.V2Compat/commercetools.Sdk.V2Compat.csproj @@ -1,7 +1,7 @@ - netstandard2.1 + net6.0 0.1.0 https://github.com/commercetools/commercetools-dotnet-core-sdk-v2 false