diff --git a/Scrutor.Extensions.HttpClient/Extensions/HttpClientExtensions.cs b/Scrutor.Extensions.HttpClient/Extensions/HttpClientExtensions.cs
index d1aec71..073f9f1 100644
--- a/Scrutor.Extensions.HttpClient/Extensions/HttpClientExtensions.cs
+++ b/Scrutor.Extensions.HttpClient/Extensions/HttpClientExtensions.cs
@@ -1,116 +1,77 @@
-using System;
-using System.Collections.Generic;
+using System;
using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
+using System.Reflection;
using Scrutor;
namespace Microsoft.Extensions.DependencyInjection;
///
-/// Add DI capabilities for Scrutor to work with HttpClient
+/// Scrutor selector extensions that register each scanned class as a typed
+/// (through )
+/// instead of a plain transient/scoped/singleton service.
///
-/// https://github.com/khellang/Scrutor/issues/180
+/// Bridges Scrutor assembly scanning with Microsoft.Extensions.Http.
+/// See https://github.com/khellang/Scrutor/issues/180.
public static class HttpClientExtensions
{
///
- /// Add as Named
+ /// Registers each scanned service as a typed bound to
+ /// the named client (so they share that client's configuration).
///
- ///
- ///
- ///
+ /// The Scrutor service-type selector.
+ /// The name of a client registered via AddHttpClient(name, ...).
public static IServiceTypeSelector AsHttpClient(this IServiceTypeSelector selector, string name = "")
- {
- var strategy = new NamedHttpClientRegistrationStrategy(name);
- return selector.UsingRegistrationStrategy(strategy);
- }
+ => selector.UsingRegistrationStrategy(new HttpClientRegistrationStrategy(name));
///
- /// Add as
+ /// Registers each scanned service as a typed , each with
+ /// its own default client named after the service type.
///
- ///
- ///
+ /// The Scrutor service-type selector.
public static IServiceTypeSelector AsHttpClient(this IServiceTypeSelector selector)
- {
- return selector.UsingRegistrationStrategy(HttpClientRegistrationStrategy.Instance);
- }
+ => selector.UsingRegistrationStrategy(new HttpClientRegistrationStrategy(name: null));
///
- /// for Named
+ /// A Scrutor that registers each scanned
+ /// (service, implementation) pair as a typed HttpClient. 's
+ /// AddHttpClient<TClient,TImplementation> has no non-generic overload, so the closed
+ /// generic method is resolved once and invoked per scanned type.
///
- private class NamedHttpClientRegistrationStrategy(string httpClientName) : RegistrationStrategy
+ private sealed class HttpClientRegistrationStrategy(string? name) : RegistrationStrategy
{
- public string HttpClientName { get; } = httpClientName;
- public static RegistrationStrategy Instance { get; private set; }
- public static RegistrationStrategy GetInstance(string httpClientName)
- {
- Instance ??= new NamedHttpClientRegistrationStrategy(httpClientName);
- return Instance;
- }
+ // Resolved lazily and cached: the AddHttpClient<,> generic definition matching whether a
+ // client name was supplied. Named → (IServiceCollection, string); unnamed → (IServiceCollection).
+ private static readonly MethodInfo NamedAddHttpClient = ResolveAddHttpClient(named: true);
+ private static readonly MethodInfo UnnamedAddHttpClient = ResolveAddHttpClient(named: false);
public override void Apply(IServiceCollection services, ServiceDescriptor descriptor)
{
- Type TInterface = descriptor.ServiceType;
- Type TClass = descriptor.ImplementationType!;
- Type TImplementingClass = typeof(HttpClientFactoryServiceCollectionExtensions);
-
-
- /* Get all methods named "AddHttpClient" */
- var methods = TImplementingClass.GetMethods()
- .Where(m => m.Name == "AddHttpClient" && m.IsGenericMethodDefinition)
- .ToArray();
+ var serviceType = descriptor.ServiceType;
+ var implementationType = descriptor.ImplementationType
+ ?? throw new InvalidOperationException(
+ $"AsHttpClient() requires a concrete implementation type, but the descriptor for " +
+ $"'{serviceType}' has none. Pair it with a class-based Scrutor selector such as " +
+ $"AddClasses(...).AsMatchingInterface() / .AsSelf().");
- /* Then get the one that matches the amount of generic arguments we want to provide,
- * in our case 2: Interface + Implementation AddHttpClient
- * Can be modified to add more parameters, but minimum is two: AddHttpClient(services, name)
- */
- var method = methods.FirstOrDefault(m =>
- {
- var parameters = m.GetParameters();
- var genericArguments = m.GetGenericArguments();
- return
- parameters.Length == 2 && genericArguments.Length == 2 &&
- parameters[0].ParameterType == typeof(IServiceCollection) && parameters[1].ParameterType == typeof(string)
- ;
- });
+ var closed = (name is null ? UnnamedAddHttpClient : NamedAddHttpClient)
+ .MakeGenericMethod(serviceType, implementationType);
- var genericMethod = method.MakeGenericMethod(TInterface, TClass);
- genericMethod.Invoke(services, [services, HttpClientName]);
+ var arguments = name is null ? new object[] { services } : new object[] { services, name };
+ closed.Invoke(obj: null, arguments);
}
- }
- ///
- /// for
- ///
- private class HttpClientRegistrationStrategy : RegistrationStrategy
- {
- public static readonly RegistrationStrategy Instance = new HttpClientRegistrationStrategy();
-
- public override void Apply(IServiceCollection services, ServiceDescriptor descriptor)
- {
- Type TInterface = descriptor.ServiceType;
- Type TClass = descriptor.ImplementationType!;
- Type TImplementingClass = typeof(HttpClientFactoryServiceCollectionExtensions);
+ private static MethodInfo ResolveAddHttpClient(bool named) =>
+ typeof(HttpClientFactoryServiceCollectionExtensions).GetMethods()
+ .SingleOrDefault(method =>
+ method is { Name: nameof(HttpClientFactoryServiceCollectionExtensions.AddHttpClient), IsGenericMethodDefinition: true }
+ && method.GetGenericArguments().Length == 2
+ && HasShape(method.GetParameters(), named))
+ ?? throw new InvalidOperationException(
+ $"Could not locate the AddHttpClient({(named ? "IServiceCollection, string" : "IServiceCollection")}) " +
+ $"overload on {nameof(HttpClientFactoryServiceCollectionExtensions)}. The Microsoft.Extensions.Http API may have changed.");
-
- /* Get all methods named "AddHttpClient" */
- var methods = TImplementingClass.GetMethods()
- .Where(m => m.Name == "AddHttpClient" && m.IsGenericMethodDefinition)
- .ToArray();
-
- /* Then get the one that matches the amount of generic arguments we want to provide,
- * in our case 2: Interface + Implementation AddHttpClient
- * Can be modified to add more parameters, but minimum is one AddHttpClient(services)
- */
- var method = methods.FirstOrDefault(m =>
- {
- var parameters = m.GetParameters();
- var genericArguments = m.GetGenericArguments();
- return parameters.Length == 1 && genericArguments.Length == 2 && parameters[0].ParameterType == typeof(IServiceCollection);
- });
-
- var genericMethod = method.MakeGenericMethod(TInterface, TClass);
- genericMethod.Invoke(services, [services]);
- }
+ private static bool HasShape(ParameterInfo[] parameters, bool named) => named
+ ? parameters.Length == 2 && parameters[0].ParameterType == typeof(IServiceCollection) && parameters[1].ParameterType == typeof(string)
+ : parameters.Length == 1 && parameters[0].ParameterType == typeof(IServiceCollection);
}
-}
\ No newline at end of file
+}
diff --git a/Scrutor.Extensions.HttpClient/Scrutor.Extensions.HttpClient.csproj b/Scrutor.Extensions.HttpClient/Scrutor.Extensions.HttpClient.csproj
index 25aad83..109eef2 100644
--- a/Scrutor.Extensions.HttpClient/Scrutor.Extensions.HttpClient.csproj
+++ b/Scrutor.Extensions.HttpClient/Scrutor.Extensions.HttpClient.csproj
@@ -1,5 +1,15 @@
+
+
+ netstandard2.0;net8.0
+
+ latest
+
+
Scrutor HttpClient Extension
Chrison Simtian