是否可以将 IHttpClientFactory 注入强类型客户端?

Vek*_*ksi 5 c# .net-core asp.net-core

正如标题所说。

假设我注册了一个强类型客户端,例如

var services = new ServiceCollection();

//A named client is another option that could be tried since MSDN documentation shows that being used when IHttpClientFactory is injected.
//However, it appears it gives the same exception.
//services.AddHttpClient("test", httpClient => 
services.AddHttpClient<TestClient>(httpClient =>
{
    httpClient.BaseAddress = new Uri("");                    
});
.AddHttpMessageHandler(_ => new TestMessageHandler());

//Registering IHttpClientFactory isn't needed, hence commented.
//services.AddSingleton(sp => sp.GetRequiredService<IHttpClientFactory>());
var servicesProvider = services.BuildServiceProvider(validateScopes: true);

public class TestClient
{
    private IHttpClientFactory ClientFactory { get; }
    public TestClient(IHttpClientFactory clientFactory)
    {
        ClientFactory = clientFactory;
    }

    public async Task<HttpResponseMessage> CallAsync(CancellationToken cancellation = default)
    {
        //using(var client = ClientFactory.CreateClient("test")) 
        using(var client = ClientFactory.CreateClient())
        {
            return await client.GetAsync("/", cancellation);
        }
    }
}

// This throws with "Message: System.InvalidOperationException : A suitable constructor
// for type 'Test.TestClient' could not be located. Ensure the type is concrete and services
// are registered for all parameters of a public constructor.

var client = servicesProvider.GetService<TestClient>();
Run Code Online (Sandbox Code Playgroud)

但正如评论中所指出的,将抛出异常。我是否错过了一些愚蠢的事情,或者这种安排是不可能的?

<编辑:如果IHttpClientFactory已注册,NullReferenceException则在尝试解析时抛出a client。奇怪,奇怪。

<编辑 2:我想避免的场景也在https://github.com/aspnet/Extensions/issues/924 上进行了描述和讨论,也许那里写的方式是一种可能不太令人满意的避免方式一些问题。

这发生在 XUnit 项目中,可能与问题没有任何关系,但谁知道呢。:)

<编辑3:显示问题的控制台程序。

using Microsoft.Extensions.DependencyInjection;
using System;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;

namespace TypedClientTest
{

public class TestClient
{
    private IHttpClientFactory ClientFactory { get; }

    public TestClient(IHttpClientFactory clientFactory)
    {
        ClientFactory = clientFactory;
    }

    public async Task<HttpResponseMessage> TestAsync(CancellationToken cancellation = default)
    {
        using (var client = ClientFactory.CreateClient())
        {
            return await client.GetAsync("/", cancellation);
        }
    }
}

class Program
{
    static void Main(string[] args)
    {
        var services = new ServiceCollection();

        services
            .AddHttpClient<TestClient>(httpClient => httpClient.BaseAddress = new Uri("https://www.github.com/"));

        var servicesProvider = services.BuildServiceProvider(validateScopes: true);

        //This throws that there is not a suitable constructor. Should it?
        var client = servicesProvider.GetService<TestClient>();
    }
}
}
Run Code Online (Sandbox Code Playgroud)

install-package Microsoft.Extensions.Httpinstall-package Microsoft.Extensions.DependencyInjection

同样在异常堆栈中有读取

在 System.Threading.LazyInitializer.EnsureInitializedCore[T](T& target, Boolean& initialized, Object& syncLock, Func 1 valueFactory) at Microsoft.Extensions.Http.DefaultTypedHttpClientFactory1.Cache.get_Activator() at ** Microsoft.Extensions.Http.DefaultTypedHttpClientFactory 1.CreateClient(HttpClient httpClient) ** at System.Threading.LazyInitializer.EnsureInitializedCore[T](T& target, Boolean& initialized, Object& syncLock, Func1 valueFactory) 在 Microsoft.Extensions.Http.DefaultTypedHttpClientFactory 1.Cache.get_Activator() at Microsoft.Extensions.Http.DefaultTypedHttpClientFactory1.CreateClient(HttpClient httpClient) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitTransient(TransientCallSite TransientCallSite, ServiceProviderEngineScope scope) at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetService[T](IServiceProviderTest provider) at TypeMaind String[] args) 在 C:\projektit\testit\TypedClientTest\TypedClientTest\Program.cs:line 40

这当然指向了问题。但可能需要进一步调试。

<编辑4:所以转到源代码,问题在https://github.com/aspnet/Extensions/blob/557995ec322f1175d6d8a72a41713eec2d194871/src/HttpClientFactory/Http/src/DefaultTypedHttpClientFactory.cs#L47https:// github.com/aspnet/Extensions/blob/11cf90103841c35cbefe9afb8e5bf9fee696dd17/src/HttpClientFactory/Http/src/DependencyInjection/HttpClientFactoryServiceCollectionExtensions.cs一般。

现在可能有一些方法可以解决这个问题。:)

<编辑5:

因此,它似乎需要.AddHttpClient一个类型化的客户端,该客户端IHttpClientFactory最终会出现在“奇怪的地方”。事实上,它不可能用于IHttpClientFactory创建自己类型的类型化客户端。

使用命名客户端进行此操作的一种方法可能是

public static class CustomServicesCollectionExtensions
{
    public static IHttpClientBuilder AddTypedHttpClient<TClient>(this IServiceCollection serviceCollection, Action<HttpClient> configureClient)  where TClient: class
    {
        //return serviceCollection.Add(new ServiceDescriptor(typeof(TClient).Name, f => new ...,*/ ServiceLifetime.Singleton));
        servicesCollection.AddTransient<TClient>();
        return serviceCollection.AddHttpClient(typeof(TType).Name, configureClient);
    }
}

public static class HttpClientFactoryExtensions
{
    public static HttpClient CreateClient<TClient>(this IHttpClientFactory clientFactory)
    {
        return clientFactory.CreateClient(typeof(TClient).Name);
    }
}


public class TestClient
{
    private IHttpClientFactory ClientFactory { get; }

    public TestClient(IHttpClientFactory clientFactory)
    {
        ClientFactory = clientFactory;
    }

    public async Task<HttpResponseMessage> Test(CancellationToken cancellation = default)
    {
        using(var client = ClientFactory.CreateClient<TestClient>())
        {
            return await client.GetAsync("/", cancellation);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

这模仿了扩展方法已经做的事情。当然,现在也可以更好地公开终身服务。

des*_*dgc 7

请阅读有关类型化客户端的信息:

类型化客户端在其构造函数中接受 HttpClient 参数

相反,IHttpClientFactory您的类应该在其构造函数中接受 an HttpClient,该构造函数将由 DI 提供(通过AddHttpClient扩展启用)。

public class TestClient
{
    private HttpClient Client { get; }
    public TestClient(HttpClient client)
    {
        Client = client;
    }

    public Task<HttpResponseMessage> CallAsync(CancellationToken cancellation = default)
    {
        return client.GetAsync("/", cancellation);
    }
}
Run Code Online (Sandbox Code Playgroud)

编辑

(基于上述编辑)

如果您想覆盖AddHttpClient扩展方法的默认行为,那么您应该直接注册您的实现:

var services = new ServiceCollection();
services.AddHttpClient("test", httpClient =>
{
    httpClient.BaseAddress = new Uri("https://localhost");                    
});

services.AddScoped<TestClient>();

var servicesProvider = services.BuildServiceProvider(validateScopes: true);
using (var scope = servicesProvider.CreateScope())
{
    var client = scope.ServiceProvider.GetRequiredService<TestClient>();
}
Run Code Online (Sandbox Code Playgroud)
public class TestClient
{
    private IHttpClientFactory ClientFactory { get; }

    public TestClient(IHttpClientFactory clientFactory)
    {
        ClientFactory = clientFactory;
    }

    public Task<HttpResponseMessage> CallAsync(CancellationToken cancellation = default)
    {
        using (var client = ClientFactory.CreateClient("test"))
        {
            return client.GetAsync("/", cancellation);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)