如何使用 autofac 注册类型化的 httpClient 服务?

Posted

技术标签:

【中文标题】如何使用 autofac 注册类型化的 httpClient 服务?【英文标题】:How to register typed httpClient service with autofac? 【发布时间】:2019-11-10 17:34:39 【问题描述】:

我正在创建 MVC Web 应用程序,该应用程序使用 .net core 2.2 调用 api,使用单独的 HttpClients 来调用每个控制器(相同的 api)。

例如:

对于用户控制器操作:UserService (httpclient) 对于后期控制器操作:PostService (httpclient)

startup.cs 我使用 DI 作为:

services.AddHttpClient<IUserService, UserService>();
services.AddHttpClient<IPostService, PostService>();

在我的处理程序中:

public class CommandHandler : IRequestHandler<Command, BaseResponse>

    private readonly IUserService _userService;

    public CommandHandler(IUserService userService)
    
        _userService = userService;
    

    public Task<BaseResponse> Handle(Command request, CancellationToken cancellationToken)
    
        throw new System.NotImplementedException();
    

但是在调用命令处理程序时出现此错误:

没有找到任何构造函数 'Autofac.Core.Activators.Reflection.DefaultConstructorFinder' 类型 'xxx.Application.Services.Users.UserService' 可以被调用 可用的服务和参数:无法解析参数 构造函数'Void'的'System.Net.Http.HttpClient httpClient' .ctor(System.Net.Http.HttpClient, xxx.Application.Configurations.IApplicationConfigurations, Microsoft.Extensions.Logging.ILogger`1[xxx.Application.Services.Users.UserService])'。

但我已经在 autofac 模块中注册了服务:

public class ServiceModule : Module

    protected override void Load(ContainerBuilder builder)
    
        builder.RegisterAssemblyTypes(typeof(ServiceModule).Assembly)
                .Where(t => t.Namespace.StartsWith("xxx.Application.Services"))
                .AsImplementedInterfaces().InstancePerLifetimeScope();
    

这是我的UserService 类构造函数:

public UserService (HttpClient httpClient, IApplicationConfigurations applicationConfig, ILogger<UserService> logger)

    _httpClient = httpClient;
    _applicationConfig = applicationConfig;
    _logger = logger;

    _remoteServiceBaseUrl = $"_applicationConfig.WebApiBaseUrl";

我有两个问题:

    以上错误是什么意思? 为 api 中的不同控制器使用单独的 httpclients 是一种好习惯吗?

【问题讨论】:

我认为你误解了这个错误。 Autofac 无法实例化UserService,因为构造函数参数之一是您尚未注册的HttpClient。该错误与CommandHandler 无关。您应该包含 UserService 的构造函数代码。 谢谢。我已将其包含在问题中(已更新) 这能回答你的问题吗? Autofac Exception: Cannot resolve parameter of constructor 'Void .ctor 注册顺序很重要。见:github.com/autofac/Autofac/issues/934 您能分享更多来自您的 startup.cs 文件的代码吗? 【参考方案1】:

通过做

services.AddHttpClient<IUserService, UserService>();  

当请求IUserService 时,您将配置本机.net 核心依赖注入以将HttpClient 注入UserService

那你就做吧

builder.RegisterAssemblyTypes(typeof(ServiceModule).Assembly)
       .Where(t => t.Namespace.StartsWith("xxx.Application.Services"))
       .AsImplementedInterfaces().InstancePerLifetimeScope();

这将删除IUserService 的本机依赖注入配置。 IUserService 现在注册到 UserService 没有任何 HttpClient

添加HttpClient 的最简单方法是像这样注册它:

builder.Register(c => new HttpClient())
       .As<HttpClient>();

services.AddHttpClient(); // register the .net core IHttpClientFactory 
builder.Register(c => c.Resolve<IHttpClientFactory>().CreateClient())
       .As<HttpClient>(); 

如果你想为一个特定的服务配置你的 httpclient,你可以创建一个 autofac 模块来添加这样的参数:

public class HttpClientModule<TService> : Module

    public HttpClientModule(Action<HttpClient> clientConfigurator)
    
        this._clientConfigurator = clientConfigurator;
    

    private readonly Action<HttpClient> _clientConfigurator;

    protected override void AttachToComponentRegistration(IComponentRegistry componentRegistry, IComponentRegistration registration)
    
        base.AttachToComponentRegistration(componentRegistry, registration);

        if (registration.Activator.LimitType == typeof(TService))
        
            registration.Preparing += (sender, e) =>
            
                e.Parameters = e.Parameters.Union(
                  new[]
                  
                    new ResolvedParameter(
                        (p, i) => p.ParameterType == typeof(HttpClient),
                        (p, i) => 
                            HttpClient client = i.Resolve<IHttpClientFactory>().CreateClient();
                            this._clientConfigurator(client);
                            return client;
                        
                    )
                  );
            ;
        
    

然后

builder.RegisterModule(new HttpClientModule<UserService>(client =>

    client.BaseAddress = new Uri("https://api.XXX.com/");
    client.DefaultRequestHeaders.Add("Accept", "application/vnd.XXX.v3+json");
    client.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-XXX");
));

【讨论】:

很棒的代码,但与 Autofac 6 不兼容。【参考方案2】:

Cyril 使用 Autofac 模块的实现非常出色,但不幸的是与 Autofac 6.0+ 不兼容。

为了在 Autofac 6.0+ 中为特定服务配置 HttpClient,需要实现 Autofac 中间件:

public class HttpClientMiddleware<TService> : IResolveMiddleware

    private readonly Action<HttpClient> _clientConfigurator;

    public HttpClientMiddleware(Action<HttpClient> clientConfigurator)
    
        _clientConfigurator = clientConfigurator;
    

    public PipelinePhase Phase => PipelinePhase.ParameterSelection;

    public void Execute(ResolveRequestContext context, Action<ResolveRequestContext> next)
    
        if (context.Registration.Activator.LimitType == typeof(TService))
        
            context.ChangeParameters(context.Parameters.Union(
                new[]
                
                    new ResolvedParameter(
                        (p, _) => p.ParameterType == typeof(HttpClient),
                        (_, i) => 
                            var client = i.Resolve<IHttpClientFactory>().CreateClient();
                            _clientConfigurator(client);
                            return client;
                        
                    )
                ));
        

        next(context);
    

然后可以使用中间件注册服务:

builder.RegisterType<UserService>()
    .As<IUserService>()
    .ConfigurePipeline(p =>
    
        p.Use(new HttpClientMiddleware<UserService>(client =>
        
            client.BaseAddress = new Uri("https://api.XXX.com/");
            client.DefaultRequestHeaders.Add("Accept", "application/vnd.XXX.v3+json");
            client.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-XXX");
        ));
    );

【讨论】:

以上是关于如何使用 autofac 注册类型化的 httpClient 服务?的主要内容,如果未能解决你的问题,请参考以下文章

Autofac官方文档翻译--注册组件--4组件扫描

autofac与unity注册类型的几个小区别

autoFac——茴字的三种写法

Autofac注册多个容器

具有类型参数的Autofac DynamicProxy

在 Autofac 中,如何更改调用 Build 后注册的实例?