From 0a4982b914aa6c33fedbaef50d8d075a687d957f Mon Sep 17 00:00:00 2001 From: David Driscoll Date: Wed, 10 Mar 2021 15:19:14 -0500 Subject: [PATCH] Added methods to support creating RefitSettings using the service provider --- README.md | 14 +- .../HttpClientFactoryExtensions.cs | 144 ++++++++++-------- Refit.HttpClientFactory/SettingsFor.cs | 13 ++ .../HttpClientFactoryExtensionsTests.cs | 102 ++++++++++++- 4 files changed, 207 insertions(+), 66 deletions(-) create mode 100644 Refit.HttpClientFactory/SettingsFor.cs diff --git a/README.md b/README.md index f50c3c453..cdd8effb8 100644 --- a/README.md +++ b/README.md @@ -790,17 +790,17 @@ There may be times when you want to know what the target interface type is of th have a derived interface that implements a common base like this: ```csharp -public interface IGetAPI +public interface IGetAPI { [Get("/{key}")] Task Get(long key); } -public interface IUsersAPI : IGetAPI +public interface IUsersAPI : IGetAPI { } -public interface IOrdersAPI : IGetAPI +public interface IOrdersAPI : IGetAPI { } ``` @@ -1099,6 +1099,14 @@ services.AddRefitClient(settings) // Add additional IHttpClientBuilder chained methods as required here: // .AddHttpMessageHandler() // .SetHandlerLifetime(TimeSpan.FromMinutes(2)); + +// or injected from the container +services.AddRefitClient(provider => new RefitSettings() { /* configure settings */ }) + .ConfigureHttpClient(c => c.BaseAddress = new Uri("https://api.example.com")); + // Add additional IHttpClientBuilder chained methods as required here: + // .AddHttpMessageHandler() + // .SetHandlerLifetime(TimeSpan.FromMinutes(2)); + ``` Note that some of the properties of `RefitSettings` will be ignored because the `HttpClient` and `HttpClientHandlers` will be managed by the `HttpClientFactory` instead of Refit. diff --git a/Refit.HttpClientFactory/HttpClientFactoryExtensions.cs b/Refit.HttpClientFactory/HttpClientFactoryExtensions.cs index 2703f2519..5d185638b 100644 --- a/Refit.HttpClientFactory/HttpClientFactoryExtensions.cs +++ b/Refit.HttpClientFactory/HttpClientFactoryExtensions.cs @@ -1,7 +1,10 @@ using System; +using System.Linq; using System.Net.Http; +using System.Reflection; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Http; namespace Refit { @@ -16,37 +19,7 @@ public static class HttpClientFactoryExtensions /// public static IHttpClientBuilder AddRefitClient(this IServiceCollection services, RefitSettings? settings = null) where T : class { - services.AddSingleton(provider => RequestBuilder.ForType(settings)); - - return services.AddHttpClient(UniqueName.ForType()) - .ConfigureHttpMessageHandlerBuilder(builder => - { - // check to see if user provided custom auth token - HttpMessageHandler? innerHandler = null; - if (settings != null) - { - if (settings.HttpMessageHandlerFactory != null) - { - innerHandler = settings.HttpMessageHandlerFactory(); - } - - if (settings.AuthorizationHeaderValueGetter != null) - { - innerHandler = new AuthenticatedHttpClientHandler(settings.AuthorizationHeaderValueGetter, innerHandler); - } - else if (settings.AuthorizationHeaderValueWithParamGetter != null) - { - innerHandler = new AuthenticatedParameterizedHttpClientHandler(settings.AuthorizationHeaderValueWithParamGetter, innerHandler); - } - } - - if(innerHandler != null) - { - builder.PrimaryHandler = innerHandler; - } - - }) - .AddTypedClient((client, serviceProvider) => RestService.For(client, serviceProvider.GetService>()!)); + return AddRefitClient(services, _ => settings); } /// @@ -58,35 +31,86 @@ public static IHttpClientBuilder AddRefitClient(this IServiceCollection servi /// public static IHttpClientBuilder AddRefitClient(this IServiceCollection services, Type refitInterfaceType, RefitSettings? settings = null) { - return services.AddHttpClient(UniqueName.ForType(refitInterfaceType)) - .ConfigureHttpMessageHandlerBuilder(builder => - { - // check to see if user provided custom auth token - HttpMessageHandler? innerHandler = null; - if (settings != null) - { - if (settings.HttpMessageHandlerFactory != null) - { - innerHandler = settings.HttpMessageHandlerFactory(); - } - - if (settings.AuthorizationHeaderValueGetter != null) - { - innerHandler = new AuthenticatedHttpClientHandler(settings.AuthorizationHeaderValueGetter, innerHandler); - } - else if (settings.AuthorizationHeaderValueWithParamGetter != null) - { - innerHandler = new AuthenticatedParameterizedHttpClientHandler(settings.AuthorizationHeaderValueWithParamGetter, innerHandler); - } - } - - if (innerHandler != null) - { - builder.PrimaryHandler = innerHandler; - } - - }) - .AddTypedClient(refitInterfaceType, (client, serviceProvider) => RestService.For(refitInterfaceType, client, settings)); + return AddRefitClient(services, refitInterfaceType, _ => settings); + } + + /// + /// Adds a Refit client to the DI container + /// + /// Type of the Refit interface + /// container + /// Optional. Action to configure refit settings. This method is called once and only once, avoid using any scoped dependencies that maybe be disposed automatically. + /// + public static IHttpClientBuilder AddRefitClient(this IServiceCollection services, Func? settingsAction) where T : class + { + services.AddSingleton(provider => new SettingsFor(settingsAction?.Invoke(provider))); + services.AddSingleton(provider => RequestBuilder.ForType(provider.GetRequiredService>().Settings)); + + return services + .AddHttpClient(UniqueName.ForType()) + .ConfigureHttpMessageHandlerBuilder(builder => + { + // check to see if user provided custom auth token + if (CreateInnerHandlerIfProvided(builder.Services.GetRequiredService>().Settings) is {} innerHandler) + { + builder.PrimaryHandler = innerHandler; + } + }) + .AddTypedClient((client, serviceProvider) => RestService.For(client, serviceProvider.GetService>()!)); + } + + /// + /// Adds a Refit client to the DI container + /// + /// container + /// Type of the Refit interface + /// Optional. Action to configure refit settings. This method is called once and only once, avoid using any scoped dependencies that maybe be disposed automatically. + /// + public static IHttpClientBuilder AddRefitClient(this IServiceCollection services, Type refitInterfaceType, Func? settingsAction) + { + var settingsType = typeof(SettingsFor<>).MakeGenericType(refitInterfaceType); + var requestBuilderType = typeof(IRequestBuilder<>).MakeGenericType(refitInterfaceType); + services.AddSingleton(settingsType, provider => Activator.CreateInstance(typeof(SettingsFor<>).MakeGenericType(refitInterfaceType)!, settingsAction?.Invoke(provider))!); + services.AddSingleton(requestBuilderType, provider => RequestBuilderGenericForTypeMethod.MakeGenericMethod(refitInterfaceType).Invoke(null, new object?[] { ((ISettingsFor)provider.GetRequiredService(settingsType)).Settings })!); + + return services + .AddHttpClient(UniqueName.ForType(refitInterfaceType)) + .ConfigureHttpMessageHandlerBuilder(builder => + { + // check to see if user provided custom auth token + if (CreateInnerHandlerIfProvided(((ISettingsFor)builder.Services.GetRequiredService(settingsType)).Settings) is { } innerHandler) + { + builder.PrimaryHandler = innerHandler; + } + }) + .AddTypedClient(refitInterfaceType, (client, serviceProvider) => RestService.For(refitInterfaceType, client, (IRequestBuilder)serviceProvider.GetRequiredService(requestBuilderType))); + } + + private static readonly MethodInfo RequestBuilderGenericForTypeMethod = typeof(RequestBuilder) + .GetMethods(BindingFlags.Public | BindingFlags.Static) + .Single(z => z.IsGenericMethodDefinition && z.GetParameters().Length == 1); + + static HttpMessageHandler? CreateInnerHandlerIfProvided(RefitSettings? settings) + { + HttpMessageHandler? innerHandler = null; + if (settings != null) + { + if (settings.HttpMessageHandlerFactory != null) + { + innerHandler = settings.HttpMessageHandlerFactory(); + } + + if (settings.AuthorizationHeaderValueGetter != null) + { + innerHandler = new AuthenticatedHttpClientHandler(settings.AuthorizationHeaderValueGetter, innerHandler); + } + else if (settings.AuthorizationHeaderValueWithParamGetter != null) + { + innerHandler = new AuthenticatedParameterizedHttpClientHandler(settings.AuthorizationHeaderValueWithParamGetter, innerHandler); + } + } + + return innerHandler; } static IHttpClientBuilder AddTypedClient(this IHttpClientBuilder builder, Type type, Func factory) diff --git a/Refit.HttpClientFactory/SettingsFor.cs b/Refit.HttpClientFactory/SettingsFor.cs new file mode 100644 index 000000000..cf8e6a4f9 --- /dev/null +++ b/Refit.HttpClientFactory/SettingsFor.cs @@ -0,0 +1,13 @@ +namespace Refit +{ + public interface ISettingsFor + { + RefitSettings? Settings { get; } + } + + public class SettingsFor : ISettingsFor + { + public SettingsFor(RefitSettings? settings) => Settings = settings; + public RefitSettings? Settings { get; } + } +} diff --git a/Refit.Tests/HttpClientFactoryExtensionsTests.cs b/Refit.Tests/HttpClientFactoryExtensionsTests.cs index 2149a2f4e..0a3bfd90c 100644 --- a/Refit.Tests/HttpClientFactoryExtensionsTests.cs +++ b/Refit.Tests/HttpClientFactoryExtensionsTests.cs @@ -1,18 +1,21 @@ -namespace Refit.Tests + +using Microsoft.Extensions.Options; + +namespace Refit.Tests { using Microsoft.Extensions.DependencyInjection; + + using System.Text.Json; using Xunit; public class HttpClientFactoryExtensionsTests { class User { - } class Role { - } [Fact] @@ -25,5 +28,98 @@ public void GenericHttpClientsAreAssignedUniqueNames() Assert.NotEqual(userClientName, roleClientName); } + + [Fact] + public void HttpClientServicesAreAddedCorrectlyGivenGenericArgument() + { + var serviceCollection = new ServiceCollection(); + serviceCollection.AddRefitClient(); + Assert.Contains(serviceCollection, z => z.ServiceType == typeof(SettingsFor)); + Assert.Contains(serviceCollection, z => z.ServiceType == typeof(IRequestBuilder)); + } + + [Fact] + public void HttpClientServicesAreAddedCorrectlyGivenTypeArgument() + { + var serviceCollection = new ServiceCollection(); + serviceCollection.AddRefitClient(typeof(IFooWithOtherAttribute)); + Assert.Contains(serviceCollection, z => z.ServiceType == typeof(SettingsFor)); + Assert.Contains(serviceCollection, z => z.ServiceType == typeof(IRequestBuilder)); + } + + [Fact] + public void HttpClientReturnsClientGivenGenericArgument() + { + var serviceCollection = new ServiceCollection(); + serviceCollection.AddRefitClient(); + var serviceProvider = serviceCollection.BuildServiceProvider(); + Assert.NotNull(serviceProvider.GetService()); + } + + [Fact] + public void HttpClientReturnsClientGivenTypeArgument() + { + var serviceCollection = new ServiceCollection(); + serviceCollection.AddRefitClient(typeof(IFooWithOtherAttribute)); + var serviceProvider = serviceCollection.BuildServiceProvider(); + Assert.NotNull(serviceProvider.GetService()); + } + + [Fact] + public void HttpClientSettingsAreInjectableGivenGenericArgument() + { + var serviceCollection = new ServiceCollection() + .Configure(o => o.Serializer = new SystemTextJsonContentSerializer(new JsonSerializerOptions())); + serviceCollection.AddRefitClient(_ => new RefitSettings() {ContentSerializer = _.GetRequiredService>().Value.Serializer}); + var serviceProvider = serviceCollection.BuildServiceProvider(); + Assert.Same( + serviceProvider.GetRequiredService>().Value.Serializer, + serviceProvider.GetRequiredService>().Settings!.ContentSerializer + ); + } + + [Fact] + public void HttpClientSettingsAreInjectableGivenTypeArgument() + { + var serviceCollection = new ServiceCollection() + .Configure(o => o.Serializer = new SystemTextJsonContentSerializer(new JsonSerializerOptions())); + serviceCollection.AddRefitClient(typeof(IFooWithOtherAttribute), _ => new RefitSettings() {ContentSerializer = _.GetRequiredService>().Value.Serializer}); + var serviceProvider = serviceCollection.BuildServiceProvider(); + Assert.Same( + serviceProvider.GetRequiredService>().Value.Serializer, + serviceProvider.GetRequiredService>().Settings!.ContentSerializer + ); + } + + [Fact] + public void HttpClientSettingsCanBeProvidedStaticallyGivenGenericArgument() + { + var contentSerializer = new SystemTextJsonContentSerializer(new JsonSerializerOptions()); + var serviceCollection = new ServiceCollection(); + serviceCollection.AddRefitClient(new RefitSettings() {ContentSerializer = contentSerializer }); + var serviceProvider = serviceCollection.BuildServiceProvider(); + Assert.Same( + contentSerializer, + serviceProvider.GetRequiredService>().Settings!.ContentSerializer + ); + } + + [Fact] + public void HttpClientSettingsCanBeProvidedStaticallyGivenTypeArgument() + { + var contentSerializer = new SystemTextJsonContentSerializer(new JsonSerializerOptions()); + var serviceCollection = new ServiceCollection(); + serviceCollection.AddRefitClient(new RefitSettings() {ContentSerializer = contentSerializer }); + var serviceProvider = serviceCollection.BuildServiceProvider(); + Assert.Same( + contentSerializer, + serviceProvider.GetRequiredService>().Settings!.ContentSerializer + ); + } + + class ClientOptions + { + public SystemTextJsonContentSerializer Serializer { get; set; } + } } }