From c0f9d46a256e787ec252868e9408ba83b8be67ca Mon Sep 17 00:00:00 2001 From: "1622989459@qq.com" <1622989459@qq.com> Date: Wed, 7 Jan 2026 05:50:14 +0800 Subject: [PATCH] Refactor HttpClientFactoryExtensions to simplify code structure - Extract duplicated extension method implementations into 4 core methods - Use T4 template to auto-generate all 16 overload variants Note: This change is API-compatible but binary-incompatible: - All method parameters now use optional parameter syntax - No code changes required for existing usage - But applications referencing this library need to be recompiled --- .../HttpClientFactoryCore.cs | 291 ++++++++++ .../HttpClientFactoryExtensions.cs | 525 ++++++++---------- .../HttpClientFactoryExtensions.tt | 181 ++++++ .../Refit.HttpClientFactory.csproj | 19 + .../HttpClientFactoryExtensionsTests.cs | 5 +- 5 files changed, 724 insertions(+), 297 deletions(-) create mode 100644 Refit.HttpClientFactory/HttpClientFactoryCore.cs create mode 100644 Refit.HttpClientFactory/HttpClientFactoryExtensions.tt diff --git a/Refit.HttpClientFactory/HttpClientFactoryCore.cs b/Refit.HttpClientFactory/HttpClientFactoryCore.cs new file mode 100644 index 000000000..5684070da --- /dev/null +++ b/Refit.HttpClientFactory/HttpClientFactoryCore.cs @@ -0,0 +1,291 @@ + +using System; +using System.Linq; +using System.Net.Http; +using System.Reflection; +using System.Runtime.CompilerServices; + +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Http; + +namespace Refit +{ + /// + /// HttpClientFactoryExtensions + /// + internal static class HttpClientFactoryCore + { + + internal static IHttpClientBuilder AddRefitClientCore( + IServiceCollection services, + Type refitInterfaceType, + Func? settings, + string? httpClientName + ) + { + if (services == null) throw new ArgumentNullException(nameof(services)); + if (refitInterfaceType == null) throw new ArgumentNullException(nameof(refitInterfaceType)); + + // register settings + var settingsType = typeof(SettingsFor<>).MakeGenericType(refitInterfaceType); + services.AddSingleton( + settingsType, + provider => Activator.CreateInstance( + typeof(SettingsFor<>).MakeGenericType(refitInterfaceType)!, + settings?.Invoke(provider) + )! + ); + + // register RequestBuilder + var requestBuilderType = typeof(IRequestBuilder<>).MakeGenericType(refitInterfaceType); + services.AddSingleton( + requestBuilderType, + provider => RequestBuilderGenericForTypeMethod + .MakeGenericMethod(refitInterfaceType) + .Invoke( + null, + [((ISettingsFor)provider.GetRequiredService(settingsType)).Settings] + )! + ); + + // create HttpClientBuilder + var builder = services.AddHttpClient(httpClientName ?? UniqueName.ForType(refitInterfaceType)); + + // configure message handler + builder.ConfigureHttpMessageHandlerBuilder(builderConfig => + { + var handler = CreateInnerHandlerIfProvided( + ((ISettingsFor)builderConfig.Services.GetRequiredService(settingsType)).Settings + ); + if (handler != null) + { + builderConfig.PrimaryHandler = handler; + } + }); + + // add typed client (register transient that resolves HttpClient from IHttpClientFactory and creates Refit client) + builder.Services.AddTransient( + refitInterfaceType, + s => + { + var httpClientFactory = s.GetRequiredService(); + var httpClient = httpClientFactory.CreateClient(builder.Name); + return RestService.For( + refitInterfaceType, + httpClient, + (IRequestBuilder)s.GetRequiredService(requestBuilderType) + ); + } + ); + + return builder; + } + + internal static IHttpClientBuilder AddKeyedRefitClientCore( + IServiceCollection services, + Type refitInterfaceType, + object? serviceKey, + Func? settings, + string? httpClientName + ) + { + if (services == null) throw new ArgumentNullException(nameof(services)); + if (refitInterfaceType == null) throw new ArgumentNullException(nameof(refitInterfaceType)); + if (serviceKey == null) throw new ArgumentNullException(nameof(serviceKey)); + + // register settings + var settingsType = typeof(SettingsFor<>).MakeGenericType(refitInterfaceType); + services.AddKeyedSingleton( + settingsType, + serviceKey, + (provider, _) => Activator.CreateInstance( + typeof(SettingsFor<>).MakeGenericType(refitInterfaceType)!, + settings?.Invoke(provider) + )! + ); + + // register RequestBuilder + var requestBuilderType = typeof(IRequestBuilder<>).MakeGenericType(refitInterfaceType); + services.AddKeyedSingleton( + requestBuilderType, + serviceKey, + (provider, _) => RequestBuilderGenericForTypeMethod + .MakeGenericMethod(refitInterfaceType) + .Invoke( + null, + [((ISettingsFor)provider.GetRequiredKeyedService(settingsType, serviceKey)).Settings] + )! + ); + + // create HttpClientBuilder + var builder = services.AddHttpClient(httpClientName ?? UniqueName.ForType(refitInterfaceType, serviceKey)); + + // configure primary handler + builder.ConfigurePrimaryHttpMessageHandler(serviceProvider => + { + var settingsInstance = (ISettingsFor)serviceProvider.GetRequiredKeyedService(settingsType, serviceKey); + return settingsInstance.Settings?.HttpMessageHandlerFactory?.Invoke() ?? new HttpClientHandler(); + }); + + // configure additional handlers + builder.ConfigureAdditionalHttpMessageHandlers((handlers, serviceProvider) => + { + var settingsInstance = (ISettingsFor)serviceProvider.GetRequiredKeyedService(settingsType, serviceKey); + if (settingsInstance.Settings?.AuthorizationHeaderValueGetter is { } getToken) + { + handlers.Add(new AuthenticatedHttpClientHandler(null, getToken)); + } + }); + + // add keyed typed client (register keyed transient that resolves HttpClient and creates Refit client) + builder.Services.AddKeyedTransient( + refitInterfaceType, + serviceKey, + (s, _) => + { + var httpClientFactory = s.GetRequiredService(); + var httpClient = httpClientFactory.CreateClient(builder.Name); + return RestService.For( + refitInterfaceType, + httpClient, + (IRequestBuilder)s.GetRequiredKeyedService(requestBuilderType, serviceKey) + ); + } + ); + + return builder; + } + + internal static IHttpClientBuilder AddRefitClientCore( + IServiceCollection services, + Type refitInterfaceType, + Func? settings, + string? httpClientName + ) where T : class + { + if (services == null) throw new ArgumentNullException(nameof(services)); + + // register settings + services.AddSingleton(provider => new SettingsFor(settings?.Invoke(provider))); + + // register RequestBuilder + services.AddSingleton(provider => + RequestBuilder.ForType(provider.GetRequiredService>().Settings) + ); + + // create HttpClientBuilder + var builder = services.AddHttpClient(httpClientName ?? UniqueName.ForType()); + + // configure message handler + builder.ConfigureHttpMessageHandlerBuilder(builderConfig => + { + var handler = CreateInnerHandlerIfProvided( + builderConfig.Services.GetRequiredService>().Settings + ); + if (handler != null) + { + builderConfig.PrimaryHandler = handler; + } + }); + + // add typed client using framework AddTypedClient + return builder.AddTypedClient((client, serviceProvider) => + RestService.For( + client, + serviceProvider.GetRequiredService>() + ) + ); + } + + internal static IHttpClientBuilder AddKeyedRefitClientCore( + IServiceCollection services, + Type refitInterfaceType, + object? serviceKey, + Func? settings, + string? httpClientName + ) where T : class + { + if (services == null) throw new ArgumentNullException(nameof(services)); + if (serviceKey == null) throw new ArgumentNullException(nameof(serviceKey)); + + // register settings + services.AddKeyedSingleton( + serviceKey, + (provider, _) => new SettingsFor(settings?.Invoke(provider)) + ); + + // register RequestBuilder + services.AddKeyedSingleton( + serviceKey, + (provider, _) => + RequestBuilder.ForType( + provider.GetRequiredKeyedService>(serviceKey).Settings + ) + ); + + // create HttpClientBuilder + var builder = services.AddHttpClient(httpClientName ?? UniqueName.ForType(serviceKey)); + + // configure primary handler + builder.ConfigurePrimaryHttpMessageHandler(serviceProvider => + { + var settingsInstance = serviceProvider.GetRequiredKeyedService>(serviceKey).Settings; + return settingsInstance?.HttpMessageHandlerFactory?.Invoke() ?? new HttpClientHandler(); + }); + + // configure additional handlers + builder.ConfigureAdditionalHttpMessageHandlers((handlers, serviceProvider) => + { + var settingsInstance = serviceProvider.GetRequiredKeyedService>(serviceKey).Settings; + if (settingsInstance?.AuthorizationHeaderValueGetter is { } getToken) + { + handlers.Add(new AuthenticatedHttpClientHandler(null, getToken)); + } + }); + + // add keyed typed client (inline keyed registration) + builder.Services.AddKeyedTransient( + serviceKey, + (s, _) => + { + var httpClientFactory = s.GetRequiredService(); + var httpClient = httpClientFactory.CreateClient(builder.Name); + return RestService.For( + httpClient, + s.GetRequiredKeyedService>(serviceKey) + ); + } + ); + + return builder; + } + + // helper - used by AddRefitClientCore and AddRefitClientCore + private 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 + ); + } + } + + return innerHandler; + } + + private static readonly MethodInfo RequestBuilderGenericForTypeMethod = + typeof(RequestBuilder) + .GetMethods(BindingFlags.Public | BindingFlags.Static) + .Single(z => z.IsGenericMethodDefinition && z.GetParameters().Length == 1); + } +} diff --git a/Refit.HttpClientFactory/HttpClientFactoryExtensions.cs b/Refit.HttpClientFactory/HttpClientFactoryExtensions.cs index ff188bf9c..348e2d023 100644 --- a/Refit.HttpClientFactory/HttpClientFactoryExtensions.cs +++ b/Refit.HttpClientFactory/HttpClientFactoryExtensions.cs @@ -12,183 +12,185 @@ namespace Refit /// public static class HttpClientFactoryExtensions { + /// + /// Adds a Refit client to the DI container + /// + /// Type of the Refit interface + /// container + /// Optional. Settings to configure the instance with + /// Optional. Allows users to change the HttpClient name. + /// + public static IHttpClientBuilder AddRefitClient( + this IServiceCollection services, + Type refitInterfaceType, + RefitSettings? settings = null, + string? httpClientName = null + ) + { + if (services == null) throw new ArgumentNullException(nameof(services)); + if (refitInterfaceType == null) throw new ArgumentNullException(nameof(refitInterfaceType)); + + return HttpClientFactoryCore.AddRefitClientCore(services, refitInterfaceType, _ => settings, httpClientName); + } + + /// + /// Adds a Refit client to the DI container with a specified service key + /// + /// Type of the Refit interface + /// container + /// An optional key to associate with the specific Refit client instance + /// Optional. Settings to configure the instance with + /// Optional. Allows users to change the HttpClient name. + /// + public static IHttpClientBuilder AddKeyedRefitClient( + this IServiceCollection services, + Type refitInterfaceType, + object? serviceKey, + RefitSettings? settings = null, + string? httpClientName = null + ) + { + if (services == null) throw new ArgumentNullException(nameof(services)); + if (refitInterfaceType == null) throw new ArgumentNullException(nameof(refitInterfaceType)); + if (serviceKey == null) throw new ArgumentNullException(nameof(serviceKey)); + + return HttpClientFactoryCore.AddKeyedRefitClientCore(services, refitInterfaceType, serviceKey, _ => settings, httpClientName); + } + /// /// Adds a Refit client to the DI container /// /// Type of the Refit interface /// container /// Optional. Settings to configure the instance with + /// Optional. Allows users to change the HttpClient name. /// public static IHttpClientBuilder AddRefitClient( this IServiceCollection services, - RefitSettings? settings = null + RefitSettings? settings = null, + string? httpClientName = null ) where T : class { - return AddRefitClient(services, _ => settings); + if (services == null) throw new ArgumentNullException(nameof(services)); + + return HttpClientFactoryCore.AddRefitClientCore(services, typeof(T), _ => settings, httpClientName); } /// - /// Adds a Refit client to the DI container + /// Adds a Refit client to the DI container with a specified service key /// /// Type of the Refit interface /// container /// An optional key to associate with the specific Refit client instance /// Optional. Settings to configure the instance with + /// Optional. Allows users to change the HttpClient name. /// public static IHttpClientBuilder AddKeyedRefitClient( this IServiceCollection services, object? serviceKey, - RefitSettings? settings = null + RefitSettings? settings = null, + string? httpClientName = null ) where T : class { - return AddKeyedRefitClient(services, serviceKey, _ => settings); + if (services == null) throw new ArgumentNullException(nameof(services)); + if (serviceKey == null) throw new ArgumentNullException(nameof(serviceKey)); + + return HttpClientFactoryCore.AddKeyedRefitClientCore(services, typeof(T), serviceKey, _ => settings, httpClientName); } /// /// Adds a Refit client to the DI container /// - /// container /// Type of the Refit interface + /// The HTTP client builder /// Optional. Settings to configure the instance with /// public static IHttpClientBuilder AddRefitClient( - this IServiceCollection services, + this IHttpClientBuilder builder, Type refitInterfaceType, RefitSettings? settings = null ) { - return AddRefitClient(services, refitInterfaceType, _ => settings); + if (builder == null) throw new ArgumentNullException(nameof(builder)); + if (refitInterfaceType == null) throw new ArgumentNullException(nameof(refitInterfaceType)); + + return HttpClientFactoryCore.AddRefitClientCore(builder.Services, refitInterfaceType, _ => settings, builder.Name); } /// - /// Adds a Refit client to the DI container + /// Adds a Refit client to the DI container with a specified service key /// - /// container /// Type of the Refit interface + /// The HTTP client builder /// An optional key to associate with the specific Refit client instance /// Optional. Settings to configure the instance with /// public static IHttpClientBuilder AddKeyedRefitClient( - this IServiceCollection services, + this IHttpClientBuilder builder, Type refitInterfaceType, object? serviceKey, RefitSettings? settings = null ) { - return AddKeyedRefitClient(services, refitInterfaceType, serviceKey, _ => settings); + if (builder == null) throw new ArgumentNullException(nameof(builder)); + if (refitInterfaceType == null) throw new ArgumentNullException(nameof(refitInterfaceType)); + if (serviceKey == null) throw new ArgumentNullException(nameof(serviceKey)); + + return HttpClientFactoryCore.AddKeyedRefitClientCore(builder.Services, refitInterfaceType, serviceKey, _ => settings, builder.Name); } /// /// 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. - /// Optional. Allows users to change the HttpClient name as provided to IServiceCollection.AddHttpClient. Useful for logging scenarios. + /// The HTTP client builder + /// Optional. Settings to configure the instance with /// public static IHttpClientBuilder AddRefitClient( - this IServiceCollection services, - Func? settingsAction, - string? httpClientName = null + this IHttpClientBuilder builder, + RefitSettings? settings = null ) where T : class { - services.AddSingleton(provider => new SettingsFor(settingsAction?.Invoke(provider))); - services.AddSingleton( - provider => - RequestBuilder.ForType( - provider.GetRequiredService>().Settings - ) - ); - - return services - .AddHttpClient(httpClientName ?? 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>()! - ) - ); + if (builder == null) throw new ArgumentNullException(nameof(builder)); + + return HttpClientFactoryCore.AddRefitClientCore(builder.Services, typeof(T), _ => settings, builder.Name); } /// /// Adds a Refit client to the DI container with a specified service key /// /// Type of the Refit interface - /// container + /// The HTTP client builder /// An optional key to associate with the specific Refit client instance - /// Optional. Action to configure refit settings. This method is called once and only once, avoid using any scoped dependencies that maybe be disposed automatically. - /// Optional. Allows users to change the HttpClient name as provided to IServiceCollection.AddHttpClient. Useful for logging scenarios. + /// Optional. Settings to configure the instance with /// public static IHttpClientBuilder AddKeyedRefitClient( - this IServiceCollection services, + this IHttpClientBuilder builder, object? serviceKey, - Func? settingsAction, - string? httpClientName = null + RefitSettings? settings = null ) where T : class { - services.AddKeyedSingleton(serviceKey, - (provider, _) => new SettingsFor(settingsAction?.Invoke(provider))); - services.AddKeyedSingleton( - serviceKey, - (provider, _) => - RequestBuilder.ForType( - provider.GetRequiredKeyedService>(serviceKey).Settings - ) - ); - - return services - .AddHttpClient(httpClientName ?? UniqueName.ForType(serviceKey)) - .ConfigurePrimaryHttpMessageHandler(serviceProvider => - { - var settings = serviceProvider.GetRequiredKeyedService>(serviceKey).Settings; - return - settings?.HttpMessageHandlerFactory?.Invoke() - ?? new HttpClientHandler(); - }) - .ConfigureAdditionalHttpMessageHandlers((handlers, serviceProvider) => - { - var settings = serviceProvider.GetRequiredKeyedService>(serviceKey).Settings; - if (settings?.AuthorizationHeaderValueGetter is { } getToken) - { - handlers.Add(new AuthenticatedHttpClientHandler(null, getToken)); - } - }) - .AddKeyedTypedClient( - serviceKey, - (client, serviceProvider) => - RestService.For( - client, - serviceProvider.GetKeyedService>(serviceKey)! - ) - ); + if (builder == null) throw new ArgumentNullException(nameof(builder)); + if (serviceKey == null) throw new ArgumentNullException(nameof(serviceKey)); + + return HttpClientFactoryCore.AddKeyedRefitClientCore(builder.Services, typeof(T), serviceKey, _ => settings, builder.Name); } /// /// 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. - /// Optional. Allows users to change the HttpClient name as provided to IServiceCollection.AddHttpClient. Useful for logging scenarios. + /// container + /// Optional. Action to configure refit settings. + /// Optional. Allows users to change the HttpClient name. /// +#if NET9_0_OR_GREATER + [System.Runtime.CompilerServices.OverloadResolutionPriority(1)] +#endif public static IHttpClientBuilder AddRefitClient( this IServiceCollection services, Type refitInterfaceType, @@ -196,66 +198,24 @@ public static IHttpClientBuilder AddRefitClient( string? httpClientName = null ) { - 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, - [ - ((ISettingsFor)provider.GetRequiredService(settingsType)).Settings - ] - )! - ); - - return services - .AddHttpClient(httpClientName ?? 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) - ) - ); + if (services == null) throw new ArgumentNullException(nameof(services)); + if (refitInterfaceType == null) throw new ArgumentNullException(nameof(refitInterfaceType)); + + return HttpClientFactoryCore.AddRefitClientCore(services, refitInterfaceType, settingsAction, httpClientName); } /// /// Adds a Refit client to the DI container with a specified service key /// - /// container /// Type of the Refit interface + /// container /// An optional key to associate with the specific Refit client instance - /// Optional. Action to configure refit settings. This method is called once and only once, avoid using any scoped dependencies that maybe be disposed automatically. - /// Optional. Allows users to change the HttpClient name as provided to IServiceCollection.AddHttpClient. Useful for logging scenarios. + /// Optional. Action to configure refit settings. + /// Optional. Allows users to change the HttpClient name. /// +#if NET9_0_OR_GREATER + [System.Runtime.CompilerServices.OverloadResolutionPriority(1)] +#endif public static IHttpClientBuilder AddKeyedRefitClient( this IServiceCollection services, Type refitInterfaceType, @@ -264,178 +224,153 @@ public static IHttpClientBuilder AddKeyedRefitClient( string? httpClientName = null ) { - var settingsType = typeof(SettingsFor<>).MakeGenericType(refitInterfaceType); - var requestBuilderType = typeof(IRequestBuilder<>).MakeGenericType(refitInterfaceType); - services.AddKeyedSingleton( - settingsType, - serviceKey, - (provider, _) => - Activator.CreateInstance( - typeof(SettingsFor<>).MakeGenericType(refitInterfaceType)!, - settingsAction?.Invoke(provider) - )! - ); - services.AddKeyedSingleton( - requestBuilderType, - serviceKey, - (provider, _) => - RequestBuilderGenericForTypeMethod - .MakeGenericMethod(refitInterfaceType) - .Invoke( - null, - [ - ((ISettingsFor)provider.GetRequiredKeyedService(settingsType, serviceKey)).Settings - ] - )! - ); - - return services - .AddHttpClient(httpClientName ?? UniqueName.ForType(refitInterfaceType, serviceKey)) - .ConfigurePrimaryHttpMessageHandler(serviceProvider => - { - var settings = (ISettingsFor)serviceProvider.GetRequiredKeyedService(settingsType, serviceKey); - return - settings.Settings?.HttpMessageHandlerFactory?.Invoke() - ?? new HttpClientHandler(); - }) - .ConfigureAdditionalHttpMessageHandlers((handlers, serviceProvider) => - { - var settings = (ISettingsFor)serviceProvider.GetRequiredKeyedService(settingsType, serviceKey); - if (settings.Settings?.AuthorizationHeaderValueGetter is { } getToken) - { - handlers.Add(new AuthenticatedHttpClientHandler(null, getToken)); - } - }) - .AddKeyedTypedClient( - refitInterfaceType, - serviceKey, - (client, serviceProvider) => - RestService.For( - refitInterfaceType, - client, - (IRequestBuilder)serviceProvider.GetRequiredKeyedService(requestBuilderType, serviceKey) - ) - ); + if (services == null) throw new ArgumentNullException(nameof(services)); + if (refitInterfaceType == null) throw new ArgumentNullException(nameof(refitInterfaceType)); + if (serviceKey == null) throw new ArgumentNullException(nameof(serviceKey)); + + return HttpClientFactoryCore.AddKeyedRefitClientCore(services, refitInterfaceType, serviceKey, settingsAction, httpClientName); } - private static readonly MethodInfo RequestBuilderGenericForTypeMethod = - typeof(RequestBuilder) - .GetMethods(BindingFlags.Public | BindingFlags.Static) - .Single(z => z.IsGenericMethodDefinition && z.GetParameters().Length == 1); + /// + /// Adds a Refit client to the DI container + /// + /// Type of the Refit interface + /// container + /// Optional. Action to configure refit settings. + /// Optional. Allows users to change the HttpClient name. + /// +#if NET9_0_OR_GREATER + [System.Runtime.CompilerServices.OverloadResolutionPriority(1)] +#endif + public static IHttpClientBuilder AddRefitClient( + this IServiceCollection services, + Func? settingsAction, + string? httpClientName = null + ) + where T : class + { + if (services == null) throw new ArgumentNullException(nameof(services)); + + return HttpClientFactoryCore.AddRefitClientCore(services, typeof(T), settingsAction, httpClientName); + } - static HttpMessageHandler? CreateInnerHandlerIfProvided(RefitSettings? settings) + /// + /// Adds a Refit client to the DI container with a specified service key + /// + /// Type of the Refit interface + /// container + /// An optional key to associate with the specific Refit client instance + /// Optional. Action to configure refit settings. + /// Optional. Allows users to change the HttpClient name. + /// +#if NET9_0_OR_GREATER + [System.Runtime.CompilerServices.OverloadResolutionPriority(1)] +#endif + public static IHttpClientBuilder AddKeyedRefitClient( + this IServiceCollection services, + object? serviceKey, + Func? settingsAction, + string? httpClientName = null + ) + where T : class { - HttpMessageHandler? innerHandler = null; - if (settings != null) - { - if (settings.HttpMessageHandlerFactory != null) - { - innerHandler = settings.HttpMessageHandlerFactory(); - } - - if (settings.AuthorizationHeaderValueGetter != null) - { - innerHandler = new AuthenticatedHttpClientHandler( - settings.AuthorizationHeaderValueGetter, - innerHandler - ); - } - } - - return innerHandler; + if (services == null) throw new ArgumentNullException(nameof(services)); + if (serviceKey == null) throw new ArgumentNullException(nameof(serviceKey)); + + return HttpClientFactoryCore.AddKeyedRefitClientCore(services, typeof(T), serviceKey, settingsAction, httpClientName); } - static IHttpClientBuilder AddTypedClient( + /// + /// Adds a Refit client to the DI container + /// + /// Type of the Refit interface + /// The HTTP client builder + /// Optional. Action to configure refit settings. + /// +#if NET9_0_OR_GREATER + [System.Runtime.CompilerServices.OverloadResolutionPriority(1)] +#endif + public static IHttpClientBuilder AddRefitClient( this IHttpClientBuilder builder, - Type type, - Func factory + Type refitInterfaceType, + Func? settingsAction ) { - if (builder == null) - { - throw new ArgumentNullException(nameof(builder)); - } - - if (factory == null) - { - throw new ArgumentNullException(nameof(factory)); - } - - builder.Services.AddTransient( - type, - s => - { - var httpClientFactory = s.GetRequiredService(); - var httpClient = httpClientFactory.CreateClient(builder.Name); - - return factory(httpClient, s); - } - ); - - return builder; + if (builder == null) throw new ArgumentNullException(nameof(builder)); + if (refitInterfaceType == null) throw new ArgumentNullException(nameof(refitInterfaceType)); + + return HttpClientFactoryCore.AddRefitClientCore(builder.Services, refitInterfaceType, settingsAction, builder.Name); } - static IHttpClientBuilder AddKeyedTypedClient( + /// + /// Adds a Refit client to the DI container with a specified service key + /// + /// Type of the Refit interface + /// The HTTP client builder + /// An optional key to associate with the specific Refit client instance + /// Optional. Action to configure refit settings. + /// +#if NET9_0_OR_GREATER + [System.Runtime.CompilerServices.OverloadResolutionPriority(1)] +#endif + public static IHttpClientBuilder AddKeyedRefitClient( this IHttpClientBuilder builder, - Type type, + Type refitInterfaceType, object? serviceKey, - Func factory + Func? settingsAction ) { - if (builder == null) - { - throw new ArgumentNullException(nameof(builder)); - } - - if (factory == null) - { - throw new ArgumentNullException(nameof(factory)); - } - - builder.Services.AddKeyedTransient( - type, - serviceKey, - (s, _) => - { - var httpClientFactory = s.GetRequiredService(); - var httpClient = httpClientFactory.CreateClient(builder.Name); - - return factory(httpClient, s); - } - ); - - return builder; + if (builder == null) throw new ArgumentNullException(nameof(builder)); + if (refitInterfaceType == null) throw new ArgumentNullException(nameof(refitInterfaceType)); + if (serviceKey == null) throw new ArgumentNullException(nameof(serviceKey)); + + return HttpClientFactoryCore.AddKeyedRefitClientCore(builder.Services, refitInterfaceType, serviceKey, settingsAction, builder.Name); } - static IHttpClientBuilder AddKeyedTypedClient( + /// + /// Adds a Refit client to the DI container + /// + /// Type of the Refit interface + /// The HTTP client builder + /// Optional. Action to configure refit settings. + /// +#if NET9_0_OR_GREATER + [System.Runtime.CompilerServices.OverloadResolutionPriority(1)] +#endif + public static IHttpClientBuilder AddRefitClient( + this IHttpClientBuilder builder, + Func? settingsAction + ) + where T : class + { + if (builder == null) throw new ArgumentNullException(nameof(builder)); + + return HttpClientFactoryCore.AddRefitClientCore(builder.Services, typeof(T), settingsAction, builder.Name); + } + + /// + /// Adds a Refit client to the DI container with a specified service key + /// + /// Type of the Refit interface + /// The HTTP client builder + /// An optional key to associate with the specific Refit client instance + /// Optional. Action to configure refit settings. + /// +#if NET9_0_OR_GREATER + [System.Runtime.CompilerServices.OverloadResolutionPriority(1)] +#endif + public static IHttpClientBuilder AddKeyedRefitClient( this IHttpClientBuilder builder, object? serviceKey, - Func factory + Func? settingsAction ) where T : class { - if (builder == null) - { - throw new ArgumentNullException(nameof(builder)); - } - - if (factory == null) - { - throw new ArgumentNullException(nameof(factory)); - } - - builder.Services.AddKeyedTransient( - serviceKey, - (s, _) => - { - var httpClientFactory = s.GetRequiredService(); - var httpClient = httpClientFactory.CreateClient(builder.Name); - - return factory(httpClient, s); - } - ); - - return builder; + if (builder == null) throw new ArgumentNullException(nameof(builder)); + if (serviceKey == null) throw new ArgumentNullException(nameof(serviceKey)); + + return HttpClientFactoryCore.AddKeyedRefitClientCore(builder.Services, typeof(T), serviceKey, settingsAction, builder.Name); } + } -} +} \ No newline at end of file diff --git a/Refit.HttpClientFactory/HttpClientFactoryExtensions.tt b/Refit.HttpClientFactory/HttpClientFactoryExtensions.tt new file mode 100644 index 000000000..b4992f3c4 --- /dev/null +++ b/Refit.HttpClientFactory/HttpClientFactoryExtensions.tt @@ -0,0 +1,181 @@ +<#@ template debug="false" hostspecific="false" language="C#" #> +<#@ output extension=".cs" #> +using System; +using System.Linq; +using System.Net.Http; +using System.Reflection; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Http; + +namespace Refit +{ + /// + /// HttpClientFactoryExtensions. + /// + public static class HttpClientFactoryExtensions + { +<# + // 位运算控制组合 + for (int bits = 0; bits < 16; bits++) + { + bool hasKeyed = (bits & 1) != 0; // 0001: 是否有Keyed + bool hasGeneric = (bits & 2) != 0; // 0010: 是否泛型 + bool isBuilder = (bits & 4) != 0; // 0100: 是否builder扩展 + bool hasAction = (bits & 8) != 0; // 1000: 是否有settingsAction + + // 生成XML注释 +#> + /// +<# if (hasKeyed) { #> + /// Adds a Refit client to the DI container with a specified service key +<# } else { #> + /// Adds a Refit client to the DI container +<# } #> + /// +<# if (hasGeneric) { #> + /// Type of the Refit interface +<# } else { #> + /// Type of the Refit interface +<# } #> +<# if (isBuilder) { #> + /// The HTTP client builder +<# } else { #> + /// container +<# } #> +<# if (hasKeyed) { #> + /// An optional key to associate with the specific Refit client instance +<# } #> +<# if (hasAction) { #> + /// Optional. Action to configure refit settings. +<# } else { #> + /// Optional. Settings to configure the instance with +<# } #> +<# if (!isBuilder) { #> + /// Optional. Allows users to change the HttpClient name. +<# } #> + /// +<# if(hasAction) { #> +#if NET9_0_OR_GREATER + [System.Runtime.CompilerServices.OverloadResolutionPriority(1)] +#endif +<# } #> +<# + // 生成方法签名 + string methodName = (hasKeyed ? "AddKeyedRefitClient" : "AddRefitClient"); +#> + public static IHttpClientBuilder <#= methodName #><#= hasGeneric ? "" : "" #>( + this <#= isBuilder ? "IHttpClientBuilder" : "IServiceCollection" #> <#= isBuilder ? "builder" : "services" #>, +<# if (!hasGeneric) { #> + Type refitInterfaceType, +<# } #> +<# if (hasKeyed) { #> + object? serviceKey, +<# } #> +<# if (hasAction) { #> + Func? settingsAction<#= !isBuilder ? "," : "" #> +<# } else { #> + RefitSettings? settings = null<#= !isBuilder ? "," : "" #> +<# } #> +<# if (!isBuilder) { #> + string? httpClientName = null +<# } #> + ) +<# if (hasGeneric) { #> + where T : class +<# } #> + { +<# + // 生成参数验证 + if (isBuilder) + { +#> + if (builder == null) throw new ArgumentNullException(nameof(builder)); +<# + } + else + { +#> + if (services == null) throw new ArgumentNullException(nameof(services)); +<# + } + + if (!hasGeneric) + { +#> + if (refitInterfaceType == null) throw new ArgumentNullException(nameof(refitInterfaceType)); +<# + } + + if (hasKeyed) + { +#> + if (serviceKey == null) throw new ArgumentNullException(nameof(serviceKey)); +<# + } +#> + +<# + // 生成Core方法调用 + string coreMethod = "HttpClientFactoryCore."; + coreMethod += (hasKeyed ? "AddKeyedRefitClient" : "AddRefitClient") + "Core"; + coreMethod += hasGeneric ? "" : ""; + coreMethod += "("; + + // 第一个参数:services + if (isBuilder) + { + coreMethod += "builder.Services"; + } + else + { + coreMethod += "services"; + } + + // 第二个参数:Type + if (hasGeneric) + { + coreMethod += ", typeof(T)"; + } + else + { + coreMethod += ", refitInterfaceType"; + } + + // 第三个参数:serviceKey + if (hasKeyed) + { + coreMethod += ", serviceKey"; + } + + // 第四个参数:settingsAction + if (hasAction) + { + coreMethod += ", settingsAction"; + } + else + { + coreMethod += ", _ => settings"; + } + + // 第五个参数:httpClientName + if (isBuilder) + { + // IHttpClientBuilder 版本使用 builder.Name + coreMethod += ", builder.Name"; + } + else + { + // IServiceCollection 版本使用传入的 httpClientName + coreMethod += ", httpClientName"; + } + + coreMethod += ")"; +#> + return <#= coreMethod #>; + } + +<# + } +#> + } +} \ No newline at end of file diff --git a/Refit.HttpClientFactory/Refit.HttpClientFactory.csproj b/Refit.HttpClientFactory/Refit.HttpClientFactory.csproj index a6f6558f2..4920d7e0d 100644 --- a/Refit.HttpClientFactory/Refit.HttpClientFactory.csproj +++ b/Refit.HttpClientFactory/Refit.HttpClientFactory.csproj @@ -13,4 +13,23 @@ + + + TextTemplatingFileGenerator + HttpClientFactoryExtensions.cs + + + + + + + + + + True + True + HttpClientFactoryExtensions.tt + + + diff --git a/Refit.Tests/HttpClientFactoryExtensionsTests.cs b/Refit.Tests/HttpClientFactoryExtensionsTests.cs index ec64b65d0..0f903a2f9 100644 --- a/Refit.Tests/HttpClientFactoryExtensionsTests.cs +++ b/Refit.Tests/HttpClientFactoryExtensionsTests.cs @@ -190,11 +190,12 @@ public void ProvidedHttpClientIsUsedAsNamedClient() var baseUri = new Uri("https://0:1337"); var services = new ServiceCollection(); - services.AddHttpClient("MyHttpClient", client => { + services.AddHttpClient("MyHttpClient", client => + { client.BaseAddress = baseUri; client.DefaultRequestHeaders.Add("X-Powered-By", Environment.OSVersion.VersionString); }); - services.AddRefitClient(null, "MyHttpClient"); + services.AddRefitClient(settingsAction: null, "MyHttpClient"); var sp = services.BuildServiceProvider(); var httpClientFactory = sp.GetRequiredService();