From f204560678855834414669584ca2841f9c0d4d4a Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Thu, 22 Jun 2023 16:14:40 +0800 Subject: [PATCH 01/14] Add AddHttpClientDefaults for all --- .../ref/Microsoft.Extensions.Http.cs | 1 + .../DefaultHttpClientBuilderTracker.cs | 10 +++ .../HttpClientBuilderExtensions.cs | 5 ++ ...lientFactoryServiceCollectionExtensions.cs | 82 ++++++++++++++++++ .../AddHttpClientDefaultsTest.cs | 84 +++++++++++++++++++ ...tFactoryServiceCollectionExtensionsTest.cs | 21 +++++ 6 files changed, 203 insertions(+) create mode 100644 src/libraries/Microsoft.Extensions.Http/src/DependencyInjection/DefaultHttpClientBuilderTracker.cs create mode 100644 src/libraries/Microsoft.Extensions.Http/tests/Microsoft.Extensions.Http.Tests/DependencyInjection/AddHttpClientDefaultsTest.cs diff --git a/src/libraries/Microsoft.Extensions.Http/ref/Microsoft.Extensions.Http.cs b/src/libraries/Microsoft.Extensions.Http/ref/Microsoft.Extensions.Http.cs index ac217fa34612e..943592980c31a 100644 --- a/src/libraries/Microsoft.Extensions.Http/ref/Microsoft.Extensions.Http.cs +++ b/src/libraries/Microsoft.Extensions.Http/ref/Microsoft.Extensions.Http.cs @@ -25,6 +25,7 @@ public static partial class HttpClientBuilderExtensions public static partial class HttpClientFactoryServiceCollectionExtensions { public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddHttpClient(this Microsoft.Extensions.DependencyInjection.IServiceCollection services) { throw null; } + public static Microsoft.Extensions.DependencyInjection.IHttpClientBuilder AddHttpClientDefaults(Microsoft.Extensions.DependencyInjection.IServiceCollection services) { throw null; } public static Microsoft.Extensions.DependencyInjection.IHttpClientBuilder AddHttpClient(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, string name) { throw null; } public static Microsoft.Extensions.DependencyInjection.IHttpClientBuilder AddHttpClient(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, string name, System.Action configureClient) { throw null; } public static Microsoft.Extensions.DependencyInjection.IHttpClientBuilder AddHttpClient(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, string name, System.Action configureClient) { throw null; } diff --git a/src/libraries/Microsoft.Extensions.Http/src/DependencyInjection/DefaultHttpClientBuilderTracker.cs b/src/libraries/Microsoft.Extensions.Http/src/DependencyInjection/DefaultHttpClientBuilderTracker.cs new file mode 100644 index 0000000000000..3200ed722e38d --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Http/src/DependencyInjection/DefaultHttpClientBuilderTracker.cs @@ -0,0 +1,10 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.Extensions.DependencyInjection +{ + internal sealed class DefaultHttpClientBuilderTracker + { + public IHttpClientBuilder? Instance { get; set; } + } +} diff --git a/src/libraries/Microsoft.Extensions.Http/src/DependencyInjection/HttpClientBuilderExtensions.cs b/src/libraries/Microsoft.Extensions.Http/src/DependencyInjection/HttpClientBuilderExtensions.cs index e97b7f4aa2b10..011645b3613d6 100644 --- a/src/libraries/Microsoft.Extensions.Http/src/DependencyInjection/HttpClientBuilderExtensions.cs +++ b/src/libraries/Microsoft.Extensions.Http/src/DependencyInjection/HttpClientBuilderExtensions.cs @@ -275,6 +275,11 @@ public static IHttpClientBuilder ConfigureHttpMessageHandlerBuilder(this IHttpCl this IHttpClientBuilder builder, bool validateSingleType) where TClient : class { + if (builder.Name is null) + { + throw new InvalidOperationException("AddTypedClient isn't supported with AddHttpClientDefaults."); + } + ReserveClient(builder, typeof(TClient), builder.Name, validateSingleType); builder.Services.AddTransient(s => AddTransientHelper(s, builder)); diff --git a/src/libraries/Microsoft.Extensions.Http/src/DependencyInjection/HttpClientFactoryServiceCollectionExtensions.cs b/src/libraries/Microsoft.Extensions.Http/src/DependencyInjection/HttpClientFactoryServiceCollectionExtensions.cs index 08072f0b43987..d33f778e8755a 100644 --- a/src/libraries/Microsoft.Extensions.Http/src/DependencyInjection/HttpClientFactoryServiceCollectionExtensions.cs +++ b/src/libraries/Microsoft.Extensions.Http/src/DependencyInjection/HttpClientFactoryServiceCollectionExtensions.cs @@ -2,11 +2,16 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using System.Linq; using System.Net.Http; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Http; using Microsoft.Extensions.Internal; +using Microsoft.Extensions.Options; namespace Microsoft.Extensions.DependencyInjection { @@ -50,6 +55,9 @@ public static IServiceCollection AddHttpClient(this IServiceCollection services) // because we access it by reaching into the service collection. services.TryAddSingleton(new HttpClientMappingRegistry()); + // This is used to track the default builder. + services.TryAddSingleton(new DefaultHttpClientBuilderTracker()); + // Register default client as HttpClient services.TryAddTransient(s => { @@ -59,6 +67,80 @@ public static IServiceCollection AddHttpClient(this IServiceCollection services) return services; } + /// + /// Adds the and related services to the and configures + /// defaults for all s. + /// + /// The . + /// An that can be used to configure the client defaults. + /// + /// + /// The defaults will be applied to all instances for all configurations, before name-specific congfiguration is applied. + /// + /// + public static IHttpClientBuilder AddHttpClientDefaults(this IServiceCollection services) + { + ThrowHelper.ThrowIfNull(services); + + AddHttpClient(services); + + var tracker = (DefaultHttpClientBuilderTracker?)services.Single(sd => sd.ServiceType == typeof(DefaultHttpClientBuilderTracker)).ImplementationInstance; + Debug.Assert(tracker != null); + + // Create builder if it doesn't already exist. + tracker.Instance ??= new DefaultHttpClientBuilder(new DefaultServiceCollection(services), name: null!); + + return tracker.Instance; + } + + private sealed class DefaultServiceCollection : IServiceCollection + { + private readonly IServiceCollection _services; + private ServiceDescriptor? _lastAdded; + + public DefaultServiceCollection(IServiceCollection services) + { + _services = services; + } + + public ServiceDescriptor this[int index] + { + get => _services[index]; + set => _services[index] = value; + } + + public int Count => _services.Count; + public bool IsReadOnly => _services.IsReadOnly; + + public void Add(ServiceDescriptor item) + { + // Insert configuration definitions into the collect before other definitions so they run first. + if (item.ServiceType.IsGenericType && item.ServiceType.GetGenericTypeDefinition() == typeof(IConfigureOptions<>)) + { + var insertIndex = 0; + if (_lastAdded is not null && _services.IndexOf(_lastAdded) is var index && index != -1) + { + insertIndex = index + 1; + } + + _services.Insert(insertIndex, item); + _lastAdded = item; + return; + } + + _services.Add(item); + } + public void Clear() => _services.Clear(); + public bool Contains(ServiceDescriptor item) => _services.Contains(item); + public void CopyTo(ServiceDescriptor[] array, int arrayIndex) => _services.CopyTo(array, arrayIndex); + public IEnumerator GetEnumerator() => _services.GetEnumerator(); + public int IndexOf(ServiceDescriptor item) => _services.IndexOf(item); + public void Insert(int index, ServiceDescriptor item) => _services.Insert(index, item); + public bool Remove(ServiceDescriptor item) => _services.Remove(item); + public void RemoveAt(int index) => _services.RemoveAt(index); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } + /// /// Adds the and related services to the and configures /// a named . diff --git a/src/libraries/Microsoft.Extensions.Http/tests/Microsoft.Extensions.Http.Tests/DependencyInjection/AddHttpClientDefaultsTest.cs b/src/libraries/Microsoft.Extensions.Http/tests/Microsoft.Extensions.Http.Tests/DependencyInjection/AddHttpClientDefaultsTest.cs new file mode 100644 index 0000000000000..d000c2e185bb4 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Http/tests/Microsoft.Extensions.Http.Tests/DependencyInjection/AddHttpClientDefaultsTest.cs @@ -0,0 +1,84 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Net.Http; +using Microsoft.Extensions.Http; +using Xunit; + +namespace Microsoft.Extensions.DependencyInjection +{ + // These are mostly integration tests that verify the configuration experience. + public class AddHttpClientDefaultsTest + { + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + public void AddHttpClientDefaults_MultipleCalls_SameInstance() + { + // Arrange + var serviceCollection = new ServiceCollection(); + + // Act + var d1 = serviceCollection.AddHttpClientDefaults(); + var d2 = serviceCollection.AddHttpClientDefaults(); + + // Assert + Assert.Same(d1, d2); + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + public void AddHttpClientDefaults_WithNameConfig_NameConfigUsed() + { + // Arrange + var serviceCollection = new ServiceCollection(); + + // Act1 + serviceCollection.AddHttpClient("example.com", c => c.BaseAddress = new Uri("http://example.com/")); + serviceCollection.AddHttpClientDefaults().ConfigureHttpClient(c => c.BaseAddress = new Uri("http://default.com/")); + + var services = serviceCollection.BuildServiceProvider(); + var factory = services.GetRequiredService(); + + // Act2 + var client = factory.CreateClient("example.com"); + + // Assert + Assert.NotNull(client); + Assert.Equal("http://example.com/", client.BaseAddress.AbsoluteUri); + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + public void AddHttpClientDefaults_MultipleConfig_LastWins() + { + // Arrange + var serviceCollection = new ServiceCollection(); + + // Act1 + serviceCollection.AddHttpClientDefaults() + .ConfigureHttpClient(c => c.BaseAddress = new Uri("http://default1.com/")) + .ConfigureHttpClient(c => c.BaseAddress = new Uri("http://default2.com/")); + + var services = serviceCollection.BuildServiceProvider(); + var factory = services.GetRequiredService(); + + // Act2 + var client = factory.CreateClient(); + + // Assert + Assert.NotNull(client); + Assert.Equal("http://default2.com/", client.BaseAddress.AbsoluteUri); + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + public void AddTypedClient_MultipleTimes_Error() + { + // Arrange + var serviceCollection = new ServiceCollection(); + + // Act + var ex = Assert.ThrowsAny(() => serviceCollection.AddHttpClientDefaults().AddTypedClient()); + + // Assert + Assert.Equal("AddTypedClient isn't supported with AddHttpClientDefaults.", ex.Message); + } + } +} diff --git a/src/libraries/Microsoft.Extensions.Http/tests/Microsoft.Extensions.Http.Tests/DependencyInjection/HttpClientFactoryServiceCollectionExtensionsTest.cs b/src/libraries/Microsoft.Extensions.Http/tests/Microsoft.Extensions.Http.Tests/DependencyInjection/HttpClientFactoryServiceCollectionExtensionsTest.cs index 35e24be3161c1..64ebd34992730 100644 --- a/src/libraries/Microsoft.Extensions.Http/tests/Microsoft.Extensions.Http.Tests/DependencyInjection/HttpClientFactoryServiceCollectionExtensionsTest.cs +++ b/src/libraries/Microsoft.Extensions.Http/tests/Microsoft.Extensions.Http.Tests/DependencyInjection/HttpClientFactoryServiceCollectionExtensionsTest.cs @@ -143,6 +143,27 @@ public void AddHttpClient_WithName_ConfiguresNamedClient() Assert.Equal("http://example.com/", client.BaseAddress.AbsoluteUri); } + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + public void AddHttpClient_WithDefaults_ConfiguresClient() + { + // Arrange + var serviceCollection = new ServiceCollection(); + + // Act1 + serviceCollection.AddHttpClient("example.com", c => c.BaseAddress = new Uri("http://example.com/")); + serviceCollection.AddHttpClientDefaults().ConfigureHttpClient(c => c.BaseAddress = new Uri("http://default.com/")); + + var services = serviceCollection.BuildServiceProvider(); + var factory = services.GetRequiredService(); + + // Act2 + var client = factory.CreateClient("example.com"); + + // Assert + Assert.NotNull(client); + Assert.Equal("http://example.com/", client.BaseAddress.AbsoluteUri); + } + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] public void AddHttpClient_WithTypedClient_ConfiguresNamedClient() { From f559ba78ca435ef85c5d978cc092b88d71f1e86d Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Thu, 22 Jun 2023 16:26:29 +0800 Subject: [PATCH 02/14] Clean up --- ...faultHttpClientBuilderServiceCollection.cs | 60 +++++++++++++++++++ ...lientFactoryServiceCollectionExtensions.cs | 52 +--------------- 2 files changed, 63 insertions(+), 49 deletions(-) create mode 100644 src/libraries/Microsoft.Extensions.Http/src/DependencyInjection/DefaultHttpClientBuilderServiceCollection.cs diff --git a/src/libraries/Microsoft.Extensions.Http/src/DependencyInjection/DefaultHttpClientBuilderServiceCollection.cs b/src/libraries/Microsoft.Extensions.Http/src/DependencyInjection/DefaultHttpClientBuilderServiceCollection.cs new file mode 100644 index 0000000000000..58b4685489bef --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Http/src/DependencyInjection/DefaultHttpClientBuilderServiceCollection.cs @@ -0,0 +1,60 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections; +using System.Collections.Generic; +using Microsoft.Extensions.Options; + +namespace Microsoft.Extensions.DependencyInjection +{ + internal sealed class DefaultHttpClientBuilderServiceCollection : IServiceCollection + { + private readonly IServiceCollection _services; + private ServiceDescriptor? _lastAdded; + + public DefaultHttpClientBuilderServiceCollection(IServiceCollection services) + { + _services = services; + } + + public void Add(ServiceDescriptor item) + { + // Insert IConfigureOptions services into the collection before other definitions. + // This ensures they run first, apply configuration, then named clients run afterwards. + if (item.ServiceType.IsGenericType && item.ServiceType.GetGenericTypeDefinition() == typeof(IConfigureOptions<>)) + { + var insertIndex = 0; + + // If configuration has already been added, additional configuration should come after. + // This is done to preserve the order that default configuration is run. + if (_lastAdded is not null && _services.IndexOf(_lastAdded) is var index && index != -1) + { + insertIndex = index + 1; + } + + _services.Insert(insertIndex, item); + _lastAdded = item; + return; + } + + _services.Add(item); + } + + public ServiceDescriptor this[int index] + { + get => _services[index]; + set => _services[index] = value; + } + public int Count => _services.Count; + public bool IsReadOnly => _services.IsReadOnly; + public void Clear() => _services.Clear(); + public bool Contains(ServiceDescriptor item) => _services.Contains(item); + public void CopyTo(ServiceDescriptor[] array, int arrayIndex) => _services.CopyTo(array, arrayIndex); + public IEnumerator GetEnumerator() => _services.GetEnumerator(); + public int IndexOf(ServiceDescriptor item) => _services.IndexOf(item); + public void Insert(int index, ServiceDescriptor item) => _services.Insert(index, item); + public bool Remove(ServiceDescriptor item) => _services.Remove(item); + public void RemoveAt(int index) => _services.RemoveAt(index); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } +} diff --git a/src/libraries/Microsoft.Extensions.Http/src/DependencyInjection/HttpClientFactoryServiceCollectionExtensions.cs b/src/libraries/Microsoft.Extensions.Http/src/DependencyInjection/HttpClientFactoryServiceCollectionExtensions.cs index d33f778e8755a..adad23c013c2c 100644 --- a/src/libraries/Microsoft.Extensions.Http/src/DependencyInjection/HttpClientFactoryServiceCollectionExtensions.cs +++ b/src/libraries/Microsoft.Extensions.Http/src/DependencyInjection/HttpClientFactoryServiceCollectionExtensions.cs @@ -84,63 +84,17 @@ public static IHttpClientBuilder AddHttpClientDefaults(this IServiceCollection s AddHttpClient(services); + // We want to return the same builder instance for multiple calls. + // This is required because the service collection wrapper has state (last added position) that we want to maintain. var tracker = (DefaultHttpClientBuilderTracker?)services.Single(sd => sd.ServiceType == typeof(DefaultHttpClientBuilderTracker)).ImplementationInstance; Debug.Assert(tracker != null); // Create builder if it doesn't already exist. - tracker.Instance ??= new DefaultHttpClientBuilder(new DefaultServiceCollection(services), name: null!); + tracker.Instance ??= new DefaultHttpClientBuilder(new DefaultHttpClientBuilderServiceCollection(services), name: null!); return tracker.Instance; } - private sealed class DefaultServiceCollection : IServiceCollection - { - private readonly IServiceCollection _services; - private ServiceDescriptor? _lastAdded; - - public DefaultServiceCollection(IServiceCollection services) - { - _services = services; - } - - public ServiceDescriptor this[int index] - { - get => _services[index]; - set => _services[index] = value; - } - - public int Count => _services.Count; - public bool IsReadOnly => _services.IsReadOnly; - - public void Add(ServiceDescriptor item) - { - // Insert configuration definitions into the collect before other definitions so they run first. - if (item.ServiceType.IsGenericType && item.ServiceType.GetGenericTypeDefinition() == typeof(IConfigureOptions<>)) - { - var insertIndex = 0; - if (_lastAdded is not null && _services.IndexOf(_lastAdded) is var index && index != -1) - { - insertIndex = index + 1; - } - - _services.Insert(insertIndex, item); - _lastAdded = item; - return; - } - - _services.Add(item); - } - public void Clear() => _services.Clear(); - public bool Contains(ServiceDescriptor item) => _services.Contains(item); - public void CopyTo(ServiceDescriptor[] array, int arrayIndex) => _services.CopyTo(array, arrayIndex); - public IEnumerator GetEnumerator() => _services.GetEnumerator(); - public int IndexOf(ServiceDescriptor item) => _services.IndexOf(item); - public void Insert(int index, ServiceDescriptor item) => _services.Insert(index, item); - public bool Remove(ServiceDescriptor item) => _services.Remove(item); - public void RemoveAt(int index) => _services.RemoveAt(index); - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - } - /// /// Adds the and related services to the and configures /// a named . From 55c75d58b89a60f3077a856cbf01dfd27f1210eb Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Thu, 22 Jun 2023 16:31:16 +0800 Subject: [PATCH 03/14] Clean up --- .../DefaultHttpClientBuilderServiceCollection.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libraries/Microsoft.Extensions.Http/src/DependencyInjection/DefaultHttpClientBuilderServiceCollection.cs b/src/libraries/Microsoft.Extensions.Http/src/DependencyInjection/DefaultHttpClientBuilderServiceCollection.cs index 58b4685489bef..9d4a86df94c6d 100644 --- a/src/libraries/Microsoft.Extensions.Http/src/DependencyInjection/DefaultHttpClientBuilderServiceCollection.cs +++ b/src/libraries/Microsoft.Extensions.Http/src/DependencyInjection/DefaultHttpClientBuilderServiceCollection.cs @@ -19,13 +19,13 @@ public DefaultHttpClientBuilderServiceCollection(IServiceCollection services) public void Add(ServiceDescriptor item) { - // Insert IConfigureOptions services into the collection before other definitions. - // This ensures they run first, apply configuration, then named clients run afterwards. + // Insert IConfigureOptions services into the collection before other descriptors. + // This ensures they run and apply configuration first. Configuration for named clients run afterwards. if (item.ServiceType.IsGenericType && item.ServiceType.GetGenericTypeDefinition() == typeof(IConfigureOptions<>)) { var insertIndex = 0; - // If configuration has already been added, additional configuration should come after. + // If default configuration has already been added, additional default configuration should come after. // This is done to preserve the order that default configuration is run. if (_lastAdded is not null && _services.IndexOf(_lastAdded) is var index && index != -1) { From d7802843bf3c12c1e31819aede307569ae2e6fd5 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Thu, 22 Jun 2023 16:34:43 +0800 Subject: [PATCH 04/14] Clean up --- .../HttpClientFactoryServiceCollectionExtensions.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/libraries/Microsoft.Extensions.Http/src/DependencyInjection/HttpClientFactoryServiceCollectionExtensions.cs b/src/libraries/Microsoft.Extensions.Http/src/DependencyInjection/HttpClientFactoryServiceCollectionExtensions.cs index adad23c013c2c..f0d3d20dc2128 100644 --- a/src/libraries/Microsoft.Extensions.Http/src/DependencyInjection/HttpClientFactoryServiceCollectionExtensions.cs +++ b/src/libraries/Microsoft.Extensions.Http/src/DependencyInjection/HttpClientFactoryServiceCollectionExtensions.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; @@ -11,7 +10,6 @@ using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Http; using Microsoft.Extensions.Internal; -using Microsoft.Extensions.Options; namespace Microsoft.Extensions.DependencyInjection { From e1efa380988384e3074a85eee2cf672b0e3251c7 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Thu, 22 Jun 2023 16:35:26 +0800 Subject: [PATCH 05/14] Clean up --- .../HttpClientFactoryServiceCollectionExtensions.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libraries/Microsoft.Extensions.Http/src/DependencyInjection/HttpClientFactoryServiceCollectionExtensions.cs b/src/libraries/Microsoft.Extensions.Http/src/DependencyInjection/HttpClientFactoryServiceCollectionExtensions.cs index f0d3d20dc2128..b662e422f384a 100644 --- a/src/libraries/Microsoft.Extensions.Http/src/DependencyInjection/HttpClientFactoryServiceCollectionExtensions.cs +++ b/src/libraries/Microsoft.Extensions.Http/src/DependencyInjection/HttpClientFactoryServiceCollectionExtensions.cs @@ -82,12 +82,12 @@ public static IHttpClientBuilder AddHttpClientDefaults(this IServiceCollection s AddHttpClient(services); - // We want to return the same builder instance for multiple calls. + // We want to return the same default builder instance for each call. // This is required because the service collection wrapper has state (last added position) that we want to maintain. var tracker = (DefaultHttpClientBuilderTracker?)services.Single(sd => sd.ServiceType == typeof(DefaultHttpClientBuilderTracker)).ImplementationInstance; Debug.Assert(tracker != null); - // Create builder if it doesn't already exist. + // Create default builder if it doesn't already exist. tracker.Instance ??= new DefaultHttpClientBuilder(new DefaultHttpClientBuilderServiceCollection(services), name: null!); return tracker.Instance; From c3e802cffc68301de2d306bd8cf2f62ae8bcfc8c Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Fri, 23 Jun 2023 10:41:52 +0800 Subject: [PATCH 06/14] Update --- .../DefaultHttpClientBuilder.cs | 14 ++++-- ...faultHttpClientBuilderServiceCollection.cs | 47 +++++++++++++------ ... DefaultHttpClientConfigurationTracker.cs} | 4 +- ...lientFactoryServiceCollectionExtensions.cs | 14 ++---- .../AddHttpClientDefaultsTest.cs | 14 ------ 5 files changed, 48 insertions(+), 45 deletions(-) rename src/libraries/Microsoft.Extensions.Http/src/DependencyInjection/{DefaultHttpClientBuilderTracker.cs => DefaultHttpClientConfigurationTracker.cs} (59%) diff --git a/src/libraries/Microsoft.Extensions.Http/src/DependencyInjection/DefaultHttpClientBuilder.cs b/src/libraries/Microsoft.Extensions.Http/src/DependencyInjection/DefaultHttpClientBuilder.cs index 8e1e3daf9096b..05810038230f6 100644 --- a/src/libraries/Microsoft.Extensions.Http/src/DependencyInjection/DefaultHttpClientBuilder.cs +++ b/src/libraries/Microsoft.Extensions.Http/src/DependencyInjection/DefaultHttpClientBuilder.cs @@ -1,14 +1,22 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Diagnostics; +using System.Linq; + namespace Microsoft.Extensions.DependencyInjection { internal sealed class DefaultHttpClientBuilder : IHttpClientBuilder { - public DefaultHttpClientBuilder(IServiceCollection services, string name) + public DefaultHttpClientBuilder(IServiceCollection services, string? name) { - Services = services; - Name = name; + // We want to return the same default builder instance for each call. + // This is required because the service collection wrapper has state (last added position) that we want to maintain. + var tracker = (DefaultHttpClientConfigurationTracker?)services.Single(sd => sd.ServiceType == typeof(DefaultHttpClientConfigurationTracker)).ImplementationInstance; + Debug.Assert(tracker != null); + + Services = new DefaultHttpClientBuilderServiceCollection(services, name == null, tracker); + Name = name!; } public string Name { get; } diff --git a/src/libraries/Microsoft.Extensions.Http/src/DependencyInjection/DefaultHttpClientBuilderServiceCollection.cs b/src/libraries/Microsoft.Extensions.Http/src/DependencyInjection/DefaultHttpClientBuilderServiceCollection.cs index 9d4a86df94c6d..158bdd56b5c63 100644 --- a/src/libraries/Microsoft.Extensions.Http/src/DependencyInjection/DefaultHttpClientBuilderServiceCollection.cs +++ b/src/libraries/Microsoft.Extensions.Http/src/DependencyInjection/DefaultHttpClientBuilderServiceCollection.cs @@ -3,6 +3,7 @@ using System.Collections; using System.Collections.Generic; +using System.Linq; using Microsoft.Extensions.Options; namespace Microsoft.Extensions.DependencyInjection @@ -10,36 +11,52 @@ namespace Microsoft.Extensions.DependencyInjection internal sealed class DefaultHttpClientBuilderServiceCollection : IServiceCollection { private readonly IServiceCollection _services; - private ServiceDescriptor? _lastAdded; + private readonly bool _isDefault; + private readonly DefaultHttpClientConfigurationTracker _tracker; - public DefaultHttpClientBuilderServiceCollection(IServiceCollection services) + public DefaultHttpClientBuilderServiceCollection(IServiceCollection services, bool isDefault, DefaultHttpClientConfigurationTracker tracker) { _services = services; + _isDefault = isDefault; + _tracker = tracker; } public void Add(ServiceDescriptor item) { - // Insert IConfigureOptions services into the collection before other descriptors. - // This ensures they run and apply configuration first. Configuration for named clients run afterwards. - if (item.ServiceType.IsGenericType && item.ServiceType.GetGenericTypeDefinition() == typeof(IConfigureOptions<>)) + if (_isDefault) { - var insertIndex = 0; + // Insert IConfigureOptions services into the collection before other descriptors. + // This ensures they run and apply configuration first. Configuration for named clients run afterwards. + if (IsConfigurationOptions(item)) + { + if (_tracker.InsertDefaultsAfterDescriptor != null && + _services.IndexOf(_tracker.InsertDefaultsAfterDescriptor) is var index && index != -1) + { + index++; + _services.Insert(index, item); + } + else + { + _services.Add(item); + } - // If default configuration has already been added, additional default configuration should come after. - // This is done to preserve the order that default configuration is run. - if (_lastAdded is not null && _services.IndexOf(_lastAdded) is var index && index != -1) + _tracker.InsertDefaultsAfterDescriptor = item; + return; + } + } + else + { + if (_tracker.InsertDefaultsAfterDescriptor == null && IsConfigurationOptions(item)) { - insertIndex = index + 1; + _tracker.InsertDefaultsAfterDescriptor = _services.Last(); } - _services.Insert(insertIndex, item); - _lastAdded = item; - return; + _services.Add(item); } - - _services.Add(item); } + private static bool IsConfigurationOptions(ServiceDescriptor item) => item.ServiceType.IsGenericType && item.ServiceType.GetGenericTypeDefinition() == typeof(IConfigureOptions<>); + public ServiceDescriptor this[int index] { get => _services[index]; diff --git a/src/libraries/Microsoft.Extensions.Http/src/DependencyInjection/DefaultHttpClientBuilderTracker.cs b/src/libraries/Microsoft.Extensions.Http/src/DependencyInjection/DefaultHttpClientConfigurationTracker.cs similarity index 59% rename from src/libraries/Microsoft.Extensions.Http/src/DependencyInjection/DefaultHttpClientBuilderTracker.cs rename to src/libraries/Microsoft.Extensions.Http/src/DependencyInjection/DefaultHttpClientConfigurationTracker.cs index 3200ed722e38d..010551be0780c 100644 --- a/src/libraries/Microsoft.Extensions.Http/src/DependencyInjection/DefaultHttpClientBuilderTracker.cs +++ b/src/libraries/Microsoft.Extensions.Http/src/DependencyInjection/DefaultHttpClientConfigurationTracker.cs @@ -3,8 +3,8 @@ namespace Microsoft.Extensions.DependencyInjection { - internal sealed class DefaultHttpClientBuilderTracker + internal sealed class DefaultHttpClientConfigurationTracker { - public IHttpClientBuilder? Instance { get; set; } + public ServiceDescriptor? InsertDefaultsAfterDescriptor { get; set; } } } diff --git a/src/libraries/Microsoft.Extensions.Http/src/DependencyInjection/HttpClientFactoryServiceCollectionExtensions.cs b/src/libraries/Microsoft.Extensions.Http/src/DependencyInjection/HttpClientFactoryServiceCollectionExtensions.cs index b662e422f384a..a1dbfeef6faa2 100644 --- a/src/libraries/Microsoft.Extensions.Http/src/DependencyInjection/HttpClientFactoryServiceCollectionExtensions.cs +++ b/src/libraries/Microsoft.Extensions.Http/src/DependencyInjection/HttpClientFactoryServiceCollectionExtensions.cs @@ -53,8 +53,8 @@ public static IServiceCollection AddHttpClient(this IServiceCollection services) // because we access it by reaching into the service collection. services.TryAddSingleton(new HttpClientMappingRegistry()); - // This is used to track the default builder. - services.TryAddSingleton(new DefaultHttpClientBuilderTracker()); + // This is used to store configuration for the default builder. + services.TryAddSingleton(new DefaultHttpClientConfigurationTracker()); // Register default client as HttpClient services.TryAddTransient(s => @@ -82,15 +82,7 @@ public static IHttpClientBuilder AddHttpClientDefaults(this IServiceCollection s AddHttpClient(services); - // We want to return the same default builder instance for each call. - // This is required because the service collection wrapper has state (last added position) that we want to maintain. - var tracker = (DefaultHttpClientBuilderTracker?)services.Single(sd => sd.ServiceType == typeof(DefaultHttpClientBuilderTracker)).ImplementationInstance; - Debug.Assert(tracker != null); - - // Create default builder if it doesn't already exist. - tracker.Instance ??= new DefaultHttpClientBuilder(new DefaultHttpClientBuilderServiceCollection(services), name: null!); - - return tracker.Instance; + return new DefaultHttpClientBuilder(services, name: null!); } /// diff --git a/src/libraries/Microsoft.Extensions.Http/tests/Microsoft.Extensions.Http.Tests/DependencyInjection/AddHttpClientDefaultsTest.cs b/src/libraries/Microsoft.Extensions.Http/tests/Microsoft.Extensions.Http.Tests/DependencyInjection/AddHttpClientDefaultsTest.cs index d000c2e185bb4..1b442e058f4f9 100644 --- a/src/libraries/Microsoft.Extensions.Http/tests/Microsoft.Extensions.Http.Tests/DependencyInjection/AddHttpClientDefaultsTest.cs +++ b/src/libraries/Microsoft.Extensions.Http/tests/Microsoft.Extensions.Http.Tests/DependencyInjection/AddHttpClientDefaultsTest.cs @@ -11,20 +11,6 @@ namespace Microsoft.Extensions.DependencyInjection // These are mostly integration tests that verify the configuration experience. public class AddHttpClientDefaultsTest { - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] - public void AddHttpClientDefaults_MultipleCalls_SameInstance() - { - // Arrange - var serviceCollection = new ServiceCollection(); - - // Act - var d1 = serviceCollection.AddHttpClientDefaults(); - var d2 = serviceCollection.AddHttpClientDefaults(); - - // Assert - Assert.Same(d1, d2); - } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] public void AddHttpClientDefaults_WithNameConfig_NameConfigUsed() { From 0ee1f7a4d0010f757ed12f9670720d6b8f60288c Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Fri, 23 Jun 2023 12:56:28 +0800 Subject: [PATCH 07/14] Limit to only HttpClientFactoryOptions --- ...faultHttpClientBuilderServiceCollection.cs | 40 +++++++++---------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/src/libraries/Microsoft.Extensions.Http/src/DependencyInjection/DefaultHttpClientBuilderServiceCollection.cs b/src/libraries/Microsoft.Extensions.Http/src/DependencyInjection/DefaultHttpClientBuilderServiceCollection.cs index 158bdd56b5c63..398aa7c1c9aa5 100644 --- a/src/libraries/Microsoft.Extensions.Http/src/DependencyInjection/DefaultHttpClientBuilderServiceCollection.cs +++ b/src/libraries/Microsoft.Extensions.Http/src/DependencyInjection/DefaultHttpClientBuilderServiceCollection.cs @@ -4,6 +4,7 @@ using System.Collections; using System.Collections.Generic; using System.Linq; +using Microsoft.Extensions.Http; using Microsoft.Extensions.Options; namespace Microsoft.Extensions.DependencyInjection @@ -23,40 +24,37 @@ public DefaultHttpClientBuilderServiceCollection(IServiceCollection services, bo public void Add(ServiceDescriptor item) { + if (item.ServiceType != typeof(IConfigureOptions)) + { + _services.Add(item); + } + if (_isDefault) { - // Insert IConfigureOptions services into the collection before other descriptors. + // Insert IConfigureOptions services into the collection before named config descriptors. // This ensures they run and apply configuration first. Configuration for named clients run afterwards. - if (IsConfigurationOptions(item)) + if (_tracker.InsertDefaultsAfterDescriptor != null && + _services.IndexOf(_tracker.InsertDefaultsAfterDescriptor) is var index && index != -1) { - if (_tracker.InsertDefaultsAfterDescriptor != null && - _services.IndexOf(_tracker.InsertDefaultsAfterDescriptor) is var index && index != -1) - { - index++; - _services.Insert(index, item); - } - else - { - _services.Add(item); - } - - _tracker.InsertDefaultsAfterDescriptor = item; - return; + index++; + _services.Insert(index, item); } + else + { + _services.Add(item); + } + + _tracker.InsertDefaultsAfterDescriptor = item; } else { - if (_tracker.InsertDefaultsAfterDescriptor == null && IsConfigurationOptions(item)) - { - _tracker.InsertDefaultsAfterDescriptor = _services.Last(); - } + // Track the location of where the first named config descriptor was added. + _tracker.InsertDefaultsAfterDescriptor ??= _services.Last(); _services.Add(item); } } - private static bool IsConfigurationOptions(ServiceDescriptor item) => item.ServiceType.IsGenericType && item.ServiceType.GetGenericTypeDefinition() == typeof(IConfigureOptions<>); - public ServiceDescriptor this[int index] { get => _services[index]; From 9282fd63f24efa0b86450d620d7cd6ae5e0bed7a Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Fri, 23 Jun 2023 13:14:21 +0800 Subject: [PATCH 08/14] Fix comment --- .../src/DependencyInjection/DefaultHttpClientBuilder.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/libraries/Microsoft.Extensions.Http/src/DependencyInjection/DefaultHttpClientBuilder.cs b/src/libraries/Microsoft.Extensions.Http/src/DependencyInjection/DefaultHttpClientBuilder.cs index 05810038230f6..c4913edbd09be 100644 --- a/src/libraries/Microsoft.Extensions.Http/src/DependencyInjection/DefaultHttpClientBuilder.cs +++ b/src/libraries/Microsoft.Extensions.Http/src/DependencyInjection/DefaultHttpClientBuilder.cs @@ -8,10 +8,9 @@ namespace Microsoft.Extensions.DependencyInjection { internal sealed class DefaultHttpClientBuilder : IHttpClientBuilder { - public DefaultHttpClientBuilder(IServiceCollection services, string? name) + public DefaultHttpClientBuilder(IServiceCollection services, string name) { - // We want to return the same default builder instance for each call. - // This is required because the service collection wrapper has state (last added position) that we want to maintain. + // The tracker references a descriptor. It marks the position of where default services are added to the collection. var tracker = (DefaultHttpClientConfigurationTracker?)services.Single(sd => sd.ServiceType == typeof(DefaultHttpClientConfigurationTracker)).ImplementationInstance; Debug.Assert(tracker != null); From 06124e5055ac3039101f4ab6bf4e2249253b1ad5 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Fri, 23 Jun 2023 13:44:02 +0800 Subject: [PATCH 09/14] Fix test name --- .../DependencyInjection/AddHttpClientDefaultsTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/Microsoft.Extensions.Http/tests/Microsoft.Extensions.Http.Tests/DependencyInjection/AddHttpClientDefaultsTest.cs b/src/libraries/Microsoft.Extensions.Http/tests/Microsoft.Extensions.Http.Tests/DependencyInjection/AddHttpClientDefaultsTest.cs index 1b442e058f4f9..643a296d97f6f 100644 --- a/src/libraries/Microsoft.Extensions.Http/tests/Microsoft.Extensions.Http.Tests/DependencyInjection/AddHttpClientDefaultsTest.cs +++ b/src/libraries/Microsoft.Extensions.Http/tests/Microsoft.Extensions.Http.Tests/DependencyInjection/AddHttpClientDefaultsTest.cs @@ -55,7 +55,7 @@ public void AddHttpClientDefaults_MultipleConfig_LastWins() } [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] - public void AddTypedClient_MultipleTimes_Error() + public void AddTypedClient_Error() { // Arrange var serviceCollection = new ServiceCollection(); From e2ab3162f40329aa188606b883e63566766f6175 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Fri, 23 Jun 2023 19:54:02 +0800 Subject: [PATCH 10/14] Fix test --- .../DefaultHttpClientBuilderServiceCollection.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libraries/Microsoft.Extensions.Http/src/DependencyInjection/DefaultHttpClientBuilderServiceCollection.cs b/src/libraries/Microsoft.Extensions.Http/src/DependencyInjection/DefaultHttpClientBuilderServiceCollection.cs index 398aa7c1c9aa5..e4ca5659db15e 100644 --- a/src/libraries/Microsoft.Extensions.Http/src/DependencyInjection/DefaultHttpClientBuilderServiceCollection.cs +++ b/src/libraries/Microsoft.Extensions.Http/src/DependencyInjection/DefaultHttpClientBuilderServiceCollection.cs @@ -27,6 +27,7 @@ public void Add(ServiceDescriptor item) if (item.ServiceType != typeof(IConfigureOptions)) { _services.Add(item); + return; } if (_isDefault) From a13c14a968eeab38e86825a7f77d562e64a718dd Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Wed, 28 Jun 2023 12:18:35 +0800 Subject: [PATCH 11/14] Refactor to ConfigureHttpClientDefaults --- .../ref/Microsoft.Extensions.Http.cs | 2 +- ...lientFactoryServiceCollectionExtensions.cs | 18 ++++--- .../AddHttpClientDefaultsTest.cs | 48 +++++++++++++++++-- ...tFactoryServiceCollectionExtensionsTest.cs | 5 +- 4 files changed, 56 insertions(+), 17 deletions(-) diff --git a/src/libraries/Microsoft.Extensions.Http/ref/Microsoft.Extensions.Http.cs b/src/libraries/Microsoft.Extensions.Http/ref/Microsoft.Extensions.Http.cs index 943592980c31a..bf22a1a19e145 100644 --- a/src/libraries/Microsoft.Extensions.Http/ref/Microsoft.Extensions.Http.cs +++ b/src/libraries/Microsoft.Extensions.Http/ref/Microsoft.Extensions.Http.cs @@ -25,7 +25,7 @@ public static partial class HttpClientBuilderExtensions public static partial class HttpClientFactoryServiceCollectionExtensions { public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddHttpClient(this Microsoft.Extensions.DependencyInjection.IServiceCollection services) { throw null; } - public static Microsoft.Extensions.DependencyInjection.IHttpClientBuilder AddHttpClientDefaults(Microsoft.Extensions.DependencyInjection.IServiceCollection services) { throw null; } + public static Microsoft.Extensions.DependencyInjection.IServiceCollection ConfigureHttpClientDefaults(Microsoft.Extensions.DependencyInjection.IServiceCollection services, System.Action configure) { throw null; } public static Microsoft.Extensions.DependencyInjection.IHttpClientBuilder AddHttpClient(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, string name) { throw null; } public static Microsoft.Extensions.DependencyInjection.IHttpClientBuilder AddHttpClient(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, string name, System.Action configureClient) { throw null; } public static Microsoft.Extensions.DependencyInjection.IHttpClientBuilder AddHttpClient(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, string name, System.Action configureClient) { throw null; } diff --git a/src/libraries/Microsoft.Extensions.Http/src/DependencyInjection/HttpClientFactoryServiceCollectionExtensions.cs b/src/libraries/Microsoft.Extensions.Http/src/DependencyInjection/HttpClientFactoryServiceCollectionExtensions.cs index a1dbfeef6faa2..e5dffcfe25340 100644 --- a/src/libraries/Microsoft.Extensions.Http/src/DependencyInjection/HttpClientFactoryServiceCollectionExtensions.cs +++ b/src/libraries/Microsoft.Extensions.Http/src/DependencyInjection/HttpClientFactoryServiceCollectionExtensions.cs @@ -66,23 +66,21 @@ public static IServiceCollection AddHttpClient(this IServiceCollection services) } /// - /// Adds the and related services to the and configures - /// defaults for all s. + /// Adds a delegate that will be used to configure all instances. /// /// The . - /// An that can be used to configure the client defaults. - /// - /// - /// The defaults will be applied to all instances for all configurations, before name-specific congfiguration is applied. - /// - /// - public static IHttpClientBuilder AddHttpClientDefaults(this IServiceCollection services) + /// A delegate that is used to configure an . + /// The . + public static IServiceCollection ConfigureHttpClientDefaults(this IServiceCollection services, Action configure) { ThrowHelper.ThrowIfNull(services); + ThrowHelper.ThrowIfNull(configure); AddHttpClient(services); - return new DefaultHttpClientBuilder(services, name: null!); + configure(new DefaultHttpClientBuilder(services, name: null!)); + + return services; } /// diff --git a/src/libraries/Microsoft.Extensions.Http/tests/Microsoft.Extensions.Http.Tests/DependencyInjection/AddHttpClientDefaultsTest.cs b/src/libraries/Microsoft.Extensions.Http/tests/Microsoft.Extensions.Http.Tests/DependencyInjection/AddHttpClientDefaultsTest.cs index 643a296d97f6f..92a551af59732 100644 --- a/src/libraries/Microsoft.Extensions.Http/tests/Microsoft.Extensions.Http.Tests/DependencyInjection/AddHttpClientDefaultsTest.cs +++ b/src/libraries/Microsoft.Extensions.Http/tests/Microsoft.Extensions.Http.Tests/DependencyInjection/AddHttpClientDefaultsTest.cs @@ -19,7 +19,10 @@ public void AddHttpClientDefaults_WithNameConfig_NameConfigUsed() // Act1 serviceCollection.AddHttpClient("example.com", c => c.BaseAddress = new Uri("http://example.com/")); - serviceCollection.AddHttpClientDefaults().ConfigureHttpClient(c => c.BaseAddress = new Uri("http://default.com/")); + serviceCollection.ConfigureHttpClientDefaults(builder => + { + builder.ConfigureHttpClient(c => c.BaseAddress = new Uri("http://default.com/")); + }); var services = serviceCollection.BuildServiceProvider(); var factory = services.GetRequiredService(); @@ -39,9 +42,38 @@ public void AddHttpClientDefaults_MultipleConfig_LastWins() var serviceCollection = new ServiceCollection(); // Act1 - serviceCollection.AddHttpClientDefaults() - .ConfigureHttpClient(c => c.BaseAddress = new Uri("http://default1.com/")) - .ConfigureHttpClient(c => c.BaseAddress = new Uri("http://default2.com/")); + serviceCollection.ConfigureHttpClientDefaults(builder => + { + builder.ConfigureHttpClient(c => c.BaseAddress = new Uri("http://default1.com/")); + }); + serviceCollection.ConfigureHttpClientDefaults(builder => + { + builder.ConfigureHttpClient(c => c.BaseAddress = new Uri("http://default2.com/")); + }); + + var services = serviceCollection.BuildServiceProvider(); + var factory = services.GetRequiredService(); + + // Act2 + var client = factory.CreateClient(); + + // Assert + Assert.NotNull(client); + Assert.Equal("http://default2.com/", client.BaseAddress.AbsoluteUri); + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + public void AddHttpClientDefaults_MultipleConfigInOneDefault_LastWins() + { + // Arrange + var serviceCollection = new ServiceCollection(); + + // Act1 + serviceCollection.ConfigureHttpClientDefaults(builder => + { + builder.ConfigureHttpClient(c => c.BaseAddress = new Uri("http://default1.com/")); + builder.ConfigureHttpClient(c => c.BaseAddress = new Uri("http://default2.com/")); + }); var services = serviceCollection.BuildServiceProvider(); var factory = services.GetRequiredService(); @@ -61,7 +93,13 @@ public void AddTypedClient_Error() var serviceCollection = new ServiceCollection(); // Act - var ex = Assert.ThrowsAny(() => serviceCollection.AddHttpClientDefaults().AddTypedClient()); + var ex = Assert.ThrowsAny(() => + { + serviceCollection.ConfigureHttpClientDefaults(builder => + { + builder.AddTypedClient(); + }); + }); // Assert Assert.Equal("AddTypedClient isn't supported with AddHttpClientDefaults.", ex.Message); diff --git a/src/libraries/Microsoft.Extensions.Http/tests/Microsoft.Extensions.Http.Tests/DependencyInjection/HttpClientFactoryServiceCollectionExtensionsTest.cs b/src/libraries/Microsoft.Extensions.Http/tests/Microsoft.Extensions.Http.Tests/DependencyInjection/HttpClientFactoryServiceCollectionExtensionsTest.cs index 64ebd34992730..d5f6596496d31 100644 --- a/src/libraries/Microsoft.Extensions.Http/tests/Microsoft.Extensions.Http.Tests/DependencyInjection/HttpClientFactoryServiceCollectionExtensionsTest.cs +++ b/src/libraries/Microsoft.Extensions.Http/tests/Microsoft.Extensions.Http.Tests/DependencyInjection/HttpClientFactoryServiceCollectionExtensionsTest.cs @@ -151,7 +151,10 @@ public void AddHttpClient_WithDefaults_ConfiguresClient() // Act1 serviceCollection.AddHttpClient("example.com", c => c.BaseAddress = new Uri("http://example.com/")); - serviceCollection.AddHttpClientDefaults().ConfigureHttpClient(c => c.BaseAddress = new Uri("http://default.com/")); + serviceCollection.ConfigureHttpClientDefaults(builder => + { + builder.ConfigureHttpClient(c => c.BaseAddress = new Uri("http://default.com/")); + }); var services = serviceCollection.BuildServiceProvider(); var factory = services.GetRequiredService(); From ec57e0aa970a89ebb876677ff8d5ee356b932ed8 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Wed, 28 Jun 2023 13:37:27 +0800 Subject: [PATCH 12/14] Add extra configure methods --- .../ref/Microsoft.Extensions.Http.cs | 4 +- .../HttpClientBuilderExtensions.cs | 45 ++++++++++ ...tFactoryServiceCollectionExtensionsTest.cs | 88 +++++++++++++++++++ 3 files changed, 136 insertions(+), 1 deletion(-) diff --git a/src/libraries/Microsoft.Extensions.Http/ref/Microsoft.Extensions.Http.cs b/src/libraries/Microsoft.Extensions.Http/ref/Microsoft.Extensions.Http.cs index bf22a1a19e145..b307868c31980 100644 --- a/src/libraries/Microsoft.Extensions.Http/ref/Microsoft.Extensions.Http.cs +++ b/src/libraries/Microsoft.Extensions.Http/ref/Microsoft.Extensions.Http.cs @@ -12,12 +12,14 @@ public static partial class HttpClientBuilderExtensions public static Microsoft.Extensions.DependencyInjection.IHttpClientBuilder AddTypedClient(this Microsoft.Extensions.DependencyInjection.IHttpClientBuilder builder, System.Func factory) where TClient : class { throw null; } public static Microsoft.Extensions.DependencyInjection.IHttpClientBuilder AddTypedClient(this Microsoft.Extensions.DependencyInjection.IHttpClientBuilder builder, System.Func factory) where TClient : class { throw null; } public static Microsoft.Extensions.DependencyInjection.IHttpClientBuilder AddTypedClient(this Microsoft.Extensions.DependencyInjection.IHttpClientBuilder builder) where TClient : class where TImplementation : class, TClient { throw null; } + public static Microsoft.Extensions.DependencyInjection.IHttpClientBuilder ConfigureAdditionalHttpMessageHandlers(this Microsoft.Extensions.DependencyInjection.IHttpClientBuilder builder, System.Action, System.IServiceProvider> configureAdditionalHandlers) { throw null; } public static Microsoft.Extensions.DependencyInjection.IHttpClientBuilder ConfigureHttpClient(this Microsoft.Extensions.DependencyInjection.IHttpClientBuilder builder, System.Action configureClient) { throw null; } public static Microsoft.Extensions.DependencyInjection.IHttpClientBuilder ConfigureHttpClient(this Microsoft.Extensions.DependencyInjection.IHttpClientBuilder builder, System.Action configureClient) { throw null; } public static Microsoft.Extensions.DependencyInjection.IHttpClientBuilder ConfigureHttpMessageHandlerBuilder(this Microsoft.Extensions.DependencyInjection.IHttpClientBuilder builder, System.Action configureBuilder) { throw null; } public static Microsoft.Extensions.DependencyInjection.IHttpClientBuilder ConfigurePrimaryHttpMessageHandler(this Microsoft.Extensions.DependencyInjection.IHttpClientBuilder builder, System.Func configureHandler) { throw null; } public static Microsoft.Extensions.DependencyInjection.IHttpClientBuilder ConfigurePrimaryHttpMessageHandler(this Microsoft.Extensions.DependencyInjection.IHttpClientBuilder builder, System.Func configureHandler) { throw null; } public static Microsoft.Extensions.DependencyInjection.IHttpClientBuilder ConfigurePrimaryHttpMessageHandler(this Microsoft.Extensions.DependencyInjection.IHttpClientBuilder builder) where THandler : System.Net.Http.HttpMessageHandler { throw null; } + public static Microsoft.Extensions.DependencyInjection.IHttpClientBuilder ConfigurePrimaryHttpMessageHandler(this Microsoft.Extensions.DependencyInjection.IHttpClientBuilder builder, System.Action configureHandler) { throw null; } public static Microsoft.Extensions.DependencyInjection.IHttpClientBuilder RedactLoggedHeaders(this Microsoft.Extensions.DependencyInjection.IHttpClientBuilder builder, System.Collections.Generic.IEnumerable redactedLoggedHeaderNames) { throw null; } public static Microsoft.Extensions.DependencyInjection.IHttpClientBuilder RedactLoggedHeaders(this Microsoft.Extensions.DependencyInjection.IHttpClientBuilder builder, System.Func shouldRedactHeaderValue) { throw null; } public static Microsoft.Extensions.DependencyInjection.IHttpClientBuilder SetHandlerLifetime(this Microsoft.Extensions.DependencyInjection.IHttpClientBuilder builder, System.TimeSpan handlerLifetime) { throw null; } @@ -25,7 +27,6 @@ public static partial class HttpClientBuilderExtensions public static partial class HttpClientFactoryServiceCollectionExtensions { public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddHttpClient(this Microsoft.Extensions.DependencyInjection.IServiceCollection services) { throw null; } - public static Microsoft.Extensions.DependencyInjection.IServiceCollection ConfigureHttpClientDefaults(Microsoft.Extensions.DependencyInjection.IServiceCollection services, System.Action configure) { throw null; } public static Microsoft.Extensions.DependencyInjection.IHttpClientBuilder AddHttpClient(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, string name) { throw null; } public static Microsoft.Extensions.DependencyInjection.IHttpClientBuilder AddHttpClient(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, string name, System.Action configureClient) { throw null; } public static Microsoft.Extensions.DependencyInjection.IHttpClientBuilder AddHttpClient(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, string name, System.Action configureClient) { throw null; } @@ -45,6 +46,7 @@ public static partial class HttpClientFactoryServiceCollectionExtensions public static Microsoft.Extensions.DependencyInjection.IHttpClientBuilder AddHttpClient(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, string name, System.Action configureClient) where TClient : class where TImplementation : class, TClient { throw null; } public static Microsoft.Extensions.DependencyInjection.IHttpClientBuilder AddHttpClient(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, string name, System.Func factory) where TClient : class where TImplementation : class, TClient { throw null; } public static Microsoft.Extensions.DependencyInjection.IHttpClientBuilder AddHttpClient(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, string name, System.Func factory) where TClient : class where TImplementation : class, TClient { throw null; } + public static Microsoft.Extensions.DependencyInjection.IServiceCollection ConfigureHttpClientDefaults(Microsoft.Extensions.DependencyInjection.IServiceCollection services, System.Action configure) { throw null; } } public partial interface IHttpClientBuilder { diff --git a/src/libraries/Microsoft.Extensions.Http/src/DependencyInjection/HttpClientBuilderExtensions.cs b/src/libraries/Microsoft.Extensions.Http/src/DependencyInjection/HttpClientBuilderExtensions.cs index 011645b3613d6..b74de485d8fc4 100644 --- a/src/libraries/Microsoft.Extensions.Http/src/DependencyInjection/HttpClientBuilderExtensions.cs +++ b/src/libraries/Microsoft.Extensions.Http/src/DependencyInjection/HttpClientBuilderExtensions.cs @@ -220,6 +220,31 @@ public static IHttpClientBuilder ConfigurePrimaryHttpMessageHandler(th return builder; } + /// + /// Adds a delegate that will be used to configure the primary for a + /// named . + /// + /// The . + /// A delegate that is used to configure a previously set or default primary . + /// An that can be used to configure the client. + /// + /// + /// The argument provided to will be + /// a reference to a scoped service provider that shares the lifetime of the handler being constructed. + /// + /// + public static IHttpClientBuilder ConfigurePrimaryHttpMessageHandler(this IHttpClientBuilder builder, Action configureHandler) + { + ThrowHelper.ThrowIfNull(builder); + + builder.Services.Configure(builder.Name, options => + { + options.HttpMessageHandlerBuilderActions.Add(b => configureHandler(b.PrimaryHandler, b.Services)); + }); + + return builder; + } + /// /// Adds a delegate that will be used to configure message handlers using /// for a named . @@ -536,6 +561,26 @@ public static IHttpClientBuilder SetHandlerLifetime(this IHttpClientBuilder buil return builder; } + /// + /// Adds a delegate that will be used to configure additional message handlers using + /// for a named . + /// + /// The . + /// A delegate that is used to configure a collection of s. + /// An that can be used to configure the client. + public static IHttpClientBuilder ConfigureAdditionalHttpMessageHandlers(this IHttpClientBuilder builder, Action, IServiceProvider> configureAdditionalHandlers) + { + ThrowHelper.ThrowIfNull(builder); + ThrowHelper.ThrowIfNull(configureAdditionalHandlers); + + builder.Services.Configure(builder.Name, options => + { + options.HttpMessageHandlerBuilderActions.Add(b => configureAdditionalHandlers(b.AdditionalHandlers, b.Services)); + }); + + return builder; + } + // See comments on HttpClientMappingRegistry. private static void ReserveClient(IHttpClientBuilder builder, Type type, string name, bool validateSingleType) { diff --git a/src/libraries/Microsoft.Extensions.Http/tests/Microsoft.Extensions.Http.Tests/DependencyInjection/HttpClientFactoryServiceCollectionExtensionsTest.cs b/src/libraries/Microsoft.Extensions.Http/tests/Microsoft.Extensions.Http.Tests/DependencyInjection/HttpClientFactoryServiceCollectionExtensionsTest.cs index d5f6596496d31..73d8518eb93d3 100644 --- a/src/libraries/Microsoft.Extensions.Http/tests/Microsoft.Extensions.Http.Tests/DependencyInjection/HttpClientFactoryServiceCollectionExtensionsTest.cs +++ b/src/libraries/Microsoft.Extensions.Http/tests/Microsoft.Extensions.Http.Tests/DependencyInjection/HttpClientFactoryServiceCollectionExtensionsTest.cs @@ -1366,6 +1366,94 @@ public void SuppressScope_True_InScope_DoesNotCreateScope() } } + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + public void AddHttpClient_ConfigurePrimaryHttpMessageHandler_ApplyChangesPrimaryHandler() + { + // Arrange + var testCredentials = new TestCredentials(); + var testBuilder = new TestHttpMessageHandlerBuilder(); + + var serviceCollection = new ServiceCollection(); + serviceCollection.AddSingleton(testBuilder); + serviceCollection + .AddHttpClient("test") + .ConfigurePrimaryHttpMessageHandler((primaryHandler, _) => + { + ((HttpClientHandler)primaryHandler).Credentials = testCredentials; + }); + + var services = serviceCollection.BuildServiceProvider(); + + // Act & Assert + _ = services.GetRequiredService(); + + Assert.Same(testCredentials, ((HttpClientHandler)testBuilder.PrimaryHandler).Credentials); + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + public void AddHttpClient_ConfigureAdditionalHttpMessageHandlers_ModifyAdditionalHandlers() + { + // Arrange + var testCredentials = new TestCredentials(); + var testBuilder = new TestHttpMessageHandlerBuilder(); + + var serviceCollection = new ServiceCollection(); + serviceCollection.AddSingleton(testBuilder); + serviceCollection + .AddHttpClient("test") + .AddHttpMessageHandler(() => Mock.Of()) + .ConfigureAdditionalHttpMessageHandlers((additionalHandlers, _) => + { + additionalHandlers.Clear(); + }); + + var services = serviceCollection.BuildServiceProvider(); + + // Act & Assert + _ = services.GetRequiredService(); + + // Contains two logging handlers added by the filter. + Assert.Equal(2, testBuilder.AdditionalHandlers.Count); + } + + private sealed class TestHttpMessageHandlerBuilder : HttpMessageHandlerBuilder + { + public override string? Name { get; set; } + public override HttpMessageHandler PrimaryHandler { get; set; } = new HttpClientHandler(); + public override IList AdditionalHandlers { get; } = new List(); + + public override HttpMessageHandler Build() => PrimaryHandler; + } + + private sealed class TestCredentials : ICredentials + { + public NetworkCredential GetCredential(Uri uri, string authType) => throw new NotImplementedException(); + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + public void AddHttpClientDefaults_MultipleConfigInOneDefault_LastWins() + { + // Arrange + var serviceCollection = new ServiceCollection(); + + // Act1 + serviceCollection.ConfigureHttpClientDefaults(builder => + { + builder.ConfigureHttpClient(c => c.BaseAddress = new Uri("http://default1.com/")); + builder.ConfigureHttpClient(c => c.BaseAddress = new Uri("http://default2.com/")); + }); + + var services = serviceCollection.BuildServiceProvider(); + var factory = services.GetRequiredService(); + + // Act2 + var client = factory.CreateClient(); + + // Assert + Assert.NotNull(client); + Assert.Equal("http://default2.com/", client.BaseAddress.AbsoluteUri); + } + private class TestGenericTypedClient : TestTypedClient { public TestGenericTypedClient(HttpClient httpClient) From c2fbbbb5c859e2454328d0e94a127651e48c6f9e Mon Sep 17 00:00:00 2001 From: Natalia Kondratyeva Date: Wed, 12 Jul 2023 22:38:08 +0100 Subject: [PATCH 13/14] Add obsoletion --- .../ref/Microsoft.Extensions.Http.cs | 1 + .../HttpClientBuilderExtensions.cs | 2 ++ ...tFactoryServiceCollectionExtensionsTest.cs | 22 +++++++++---------- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/libraries/Microsoft.Extensions.Http/ref/Microsoft.Extensions.Http.cs b/src/libraries/Microsoft.Extensions.Http/ref/Microsoft.Extensions.Http.cs index b307868c31980..5c6c9dbf63777 100644 --- a/src/libraries/Microsoft.Extensions.Http/ref/Microsoft.Extensions.Http.cs +++ b/src/libraries/Microsoft.Extensions.Http/ref/Microsoft.Extensions.Http.cs @@ -15,6 +15,7 @@ public static partial class HttpClientBuilderExtensions public static Microsoft.Extensions.DependencyInjection.IHttpClientBuilder ConfigureAdditionalHttpMessageHandlers(this Microsoft.Extensions.DependencyInjection.IHttpClientBuilder builder, System.Action, System.IServiceProvider> configureAdditionalHandlers) { throw null; } public static Microsoft.Extensions.DependencyInjection.IHttpClientBuilder ConfigureHttpClient(this Microsoft.Extensions.DependencyInjection.IHttpClientBuilder builder, System.Action configureClient) { throw null; } public static Microsoft.Extensions.DependencyInjection.IHttpClientBuilder ConfigureHttpClient(this Microsoft.Extensions.DependencyInjection.IHttpClientBuilder builder, System.Action configureClient) { throw null; } + [System.Obsolete("This method has been deprecated. Use ConfigurePrimaryHttpMessageHandler or ConfigureAdditionalHttpMessageHandlers instead.")] public static Microsoft.Extensions.DependencyInjection.IHttpClientBuilder ConfigureHttpMessageHandlerBuilder(this Microsoft.Extensions.DependencyInjection.IHttpClientBuilder builder, System.Action configureBuilder) { throw null; } public static Microsoft.Extensions.DependencyInjection.IHttpClientBuilder ConfigurePrimaryHttpMessageHandler(this Microsoft.Extensions.DependencyInjection.IHttpClientBuilder builder, System.Func configureHandler) { throw null; } public static Microsoft.Extensions.DependencyInjection.IHttpClientBuilder ConfigurePrimaryHttpMessageHandler(this Microsoft.Extensions.DependencyInjection.IHttpClientBuilder builder, System.Func configureHandler) { throw null; } diff --git a/src/libraries/Microsoft.Extensions.Http/src/DependencyInjection/HttpClientBuilderExtensions.cs b/src/libraries/Microsoft.Extensions.Http/src/DependencyInjection/HttpClientBuilderExtensions.cs index b74de485d8fc4..82ae528f012ab 100644 --- a/src/libraries/Microsoft.Extensions.Http/src/DependencyInjection/HttpClientBuilderExtensions.cs +++ b/src/libraries/Microsoft.Extensions.Http/src/DependencyInjection/HttpClientBuilderExtensions.cs @@ -236,6 +236,7 @@ public static IHttpClientBuilder ConfigurePrimaryHttpMessageHandler(th public static IHttpClientBuilder ConfigurePrimaryHttpMessageHandler(this IHttpClientBuilder builder, Action configureHandler) { ThrowHelper.ThrowIfNull(builder); + ThrowHelper.ThrowIfNull(configureHandler); builder.Services.Configure(builder.Name, options => { @@ -252,6 +253,7 @@ public static IHttpClientBuilder ConfigurePrimaryHttpMessageHandler(this IHttpCl /// The . /// A delegate that is used to configure an . /// An that can be used to configure the client. + [Obsolete("This method has been deprecated. Use ConfigurePrimaryHttpMessageHandler or ConfigureAdditionalHttpMessageHandlers instead.")] public static IHttpClientBuilder ConfigureHttpMessageHandlerBuilder(this IHttpClientBuilder builder, Action configureBuilder) { ThrowHelper.ThrowIfNull(builder); diff --git a/src/libraries/Microsoft.Extensions.Http/tests/Microsoft.Extensions.Http.Tests/DependencyInjection/HttpClientFactoryServiceCollectionExtensionsTest.cs b/src/libraries/Microsoft.Extensions.Http/tests/Microsoft.Extensions.Http.Tests/DependencyInjection/HttpClientFactoryServiceCollectionExtensionsTest.cs index 73d8518eb93d3..7b954cdba8ee6 100644 --- a/src/libraries/Microsoft.Extensions.Http/tests/Microsoft.Extensions.Http.Tests/DependencyInjection/HttpClientFactoryServiceCollectionExtensionsTest.cs +++ b/src/libraries/Microsoft.Extensions.Http/tests/Microsoft.Extensions.Http.Tests/DependencyInjection/HttpClientFactoryServiceCollectionExtensionsTest.cs @@ -766,14 +766,14 @@ public void AddHttpMessageHandler_WithName_NewHandlerIsSurroundedByLogging_ForHt // Arrange var serviceCollection = new ServiceCollection(); - HttpMessageHandlerBuilder builder = null; + IList additionalHandlers = null; // Act1 - serviceCollection.AddHttpClient("example.com").ConfigureHttpMessageHandlerBuilder(b => + serviceCollection.AddHttpClient("example.com").ConfigureAdditionalHttpMessageHandlers((handlers, _) => { - builder = b; + additionalHandlers = handlers; - b.AdditionalHandlers.Add(Mock.Of()); + handlers.Add(Mock.Of()); }); var services = serviceCollection.BuildServiceProvider(); @@ -788,7 +788,7 @@ public void AddHttpMessageHandler_WithName_NewHandlerIsSurroundedByLogging_ForHt Assert.NotNull(client); Assert.Collection( - builder.AdditionalHandlers, + additionalHandlers, h => Assert.IsType(h), h => Assert.NotNull(h), h => Assert.IsType(h)); @@ -907,14 +907,14 @@ public void AddHttpMessageHandler_WithName_NewHandlerIsSurroundedByLogging_ForHt { var serviceCollection = new ServiceCollection(); - HttpMessageHandlerBuilder builder = null; + IList additionalHandlers = null; // Act1 - serviceCollection.AddHttpClient("example.com").ConfigureHttpMessageHandlerBuilder(b => + serviceCollection.AddHttpClient("example.com").ConfigureAdditionalHttpMessageHandlers((handlers, _) => { - builder = b; + additionalHandlers = handlers; - b.AdditionalHandlers.Add(Mock.Of()); + handlers.Add(Mock.Of()); }); var services = serviceCollection.BuildServiceProvider(); @@ -929,7 +929,7 @@ public void AddHttpMessageHandler_WithName_NewHandlerIsSurroundedByLogging_ForHt Assert.IsNotType(handler); Assert.Collection( - builder.AdditionalHandlers, + additionalHandlers, h => Assert.IsType(h), h => Assert.NotNull(h), h => Assert.IsType(h)); @@ -1507,7 +1507,7 @@ protected override Task SendAsync(HttpRequestMessage reques { #if NETFRAMEWORK request.Properties[nameof(ScopedService)] = Service; -#else +#else request.Options.Set(new HttpRequestOptionsKey(nameof(ScopedService)), Service); #endif return Task.FromResult(new HttpResponseMessage()); From 4ea43f6e6c594025cea227fc95c9dc1e36e94c3f Mon Sep 17 00:00:00 2001 From: Natalia Kondratyeva Date: Wed, 12 Jul 2023 22:46:52 +0100 Subject: [PATCH 14/14] Rename test and fix message --- .../DependencyInjection/HttpClientBuilderExtensions.cs | 2 +- ...aultsTest.cs => ConfigureHttpClientDefaultsTest.cs} | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) rename src/libraries/Microsoft.Extensions.Http/tests/Microsoft.Extensions.Http.Tests/DependencyInjection/{AddHttpClientDefaultsTest.cs => ConfigureHttpClientDefaultsTest.cs} (90%) diff --git a/src/libraries/Microsoft.Extensions.Http/src/DependencyInjection/HttpClientBuilderExtensions.cs b/src/libraries/Microsoft.Extensions.Http/src/DependencyInjection/HttpClientBuilderExtensions.cs index 82ae528f012ab..5414c5ae945bb 100644 --- a/src/libraries/Microsoft.Extensions.Http/src/DependencyInjection/HttpClientBuilderExtensions.cs +++ b/src/libraries/Microsoft.Extensions.Http/src/DependencyInjection/HttpClientBuilderExtensions.cs @@ -304,7 +304,7 @@ public static IHttpClientBuilder ConfigureHttpMessageHandlerBuilder(this IHttpCl { if (builder.Name is null) { - throw new InvalidOperationException("AddTypedClient isn't supported with AddHttpClientDefaults."); + throw new InvalidOperationException($"{nameof(HttpClientBuilderExtensions.AddTypedClient)} isn't supported with {nameof(HttpClientFactoryServiceCollectionExtensions.ConfigureHttpClientDefaults)}."); } ReserveClient(builder, typeof(TClient), builder.Name, validateSingleType); diff --git a/src/libraries/Microsoft.Extensions.Http/tests/Microsoft.Extensions.Http.Tests/DependencyInjection/AddHttpClientDefaultsTest.cs b/src/libraries/Microsoft.Extensions.Http/tests/Microsoft.Extensions.Http.Tests/DependencyInjection/ConfigureHttpClientDefaultsTest.cs similarity index 90% rename from src/libraries/Microsoft.Extensions.Http/tests/Microsoft.Extensions.Http.Tests/DependencyInjection/AddHttpClientDefaultsTest.cs rename to src/libraries/Microsoft.Extensions.Http/tests/Microsoft.Extensions.Http.Tests/DependencyInjection/ConfigureHttpClientDefaultsTest.cs index 92a551af59732..ce3c8929d3a87 100644 --- a/src/libraries/Microsoft.Extensions.Http/tests/Microsoft.Extensions.Http.Tests/DependencyInjection/AddHttpClientDefaultsTest.cs +++ b/src/libraries/Microsoft.Extensions.Http/tests/Microsoft.Extensions.Http.Tests/DependencyInjection/ConfigureHttpClientDefaultsTest.cs @@ -9,10 +9,10 @@ namespace Microsoft.Extensions.DependencyInjection { // These are mostly integration tests that verify the configuration experience. - public class AddHttpClientDefaultsTest + public class ConfigureHttpClientDefaultsTest { [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] - public void AddHttpClientDefaults_WithNameConfig_NameConfigUsed() + public void ConfigureHttpClientDefaults_WithNameConfig_NameConfigUsed() { // Arrange var serviceCollection = new ServiceCollection(); @@ -36,7 +36,7 @@ public void AddHttpClientDefaults_WithNameConfig_NameConfigUsed() } [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] - public void AddHttpClientDefaults_MultipleConfig_LastWins() + public void ConfigureHttpClientDefaults_MultipleConfig_LastWins() { // Arrange var serviceCollection = new ServiceCollection(); @@ -63,7 +63,7 @@ public void AddHttpClientDefaults_MultipleConfig_LastWins() } [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] - public void AddHttpClientDefaults_MultipleConfigInOneDefault_LastWins() + public void ConfigureHttpClientDefaults_MultipleConfigInOneDefault_LastWins() { // Arrange var serviceCollection = new ServiceCollection(); @@ -102,7 +102,7 @@ public void AddTypedClient_Error() }); // Assert - Assert.Equal("AddTypedClient isn't supported with AddHttpClientDefaults.", ex.Message); + Assert.Equal("AddTypedClient isn't supported with ConfigureHttpClientDefaults.", ex.Message); } } }