简体   繁体   English

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

[英]How to register typed httpClient service with autofac?

I'm creating MVC web application which calls an api using .net core 2.2 using separate HttpClient s to call each controller (same api). I'm creating MVC web application which calls an api using .net core 2.2 using separate HttpClient s to call each controller (same api).

Ex:前任:

  • For user controller actions: UserService (httpclient)对于用户 controller 操作:UserService (httpclient)
  • For post controller actions: PostService (httpclient)对于发布 controller 操作:PostService (httpclient)

In startup.cs I use DI as:startup.cs我使用 DI 作为:

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

In my handler:在我的处理程序中:

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();
    }
}

But when invoking command handler I get this error:但是当调用命令处理程序时,我得到了这个错误:

None of the constructors found with 'Autofac.Core.Activators.Reflection.DefaultConstructorFinder' on type 'xxx.Application.Services.Users.UserService' can be invoked with the available services and parameters: Cannot resolve parameter 'System.Net.Http.HttpClient httpClient' of constructor 'Void.ctor(System.Net.Http.HttpClient, xxx.Application.Configurations.IApplicationConfigurations, Microsoft.Extensions.Logging.ILogger`1[xxx.Application.Services.Users.UserService])'.无法使用可用的服务和参数调用类型为“xxx.Application.Services.Users.UserService”的“Autofac.Core.Activators.Reflection.DefaultConstructorFinder”的构造函数:无法解析参数“System.Net.Http”。构造函数 'Void.ctor(System.Net.Http.HttpClient, xxx.Application.Configurations.IApplicationConfigurations, Microsoft.Extensions.Logging.ILogger`1[xxx.Application.Services.Users.UserService])'的 HttpClient httpClient'。

But I've registered services in autofac module:但是我已经在 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();
    }
}

Here is my UserService class constructor:这是我的UserService class 构造函数:

public UserService (HttpClient httpClient, IApplicationConfigurations applicationConfig, ILogger<UserService> logger)
{
    _httpClient = httpClient;
    _applicationConfig = applicationConfig;
    _logger = logger;

    _remoteServiceBaseUrl = $"{_applicationConfig.WebApiBaseUrl}";
}

I have two questions:我有两个问题:

  1. What does the above error mean?上面的错误是什么意思?
  2. Is it good practice to use separate httpclients for different controllers in api?为 api 中的不同控制器使用单独的 httpclients 是一种好习惯吗?

By doing通过做

services.AddHttpClient<IUserService, UserService>();  

You will configure the native .net core dependency injection to inject HttpClient to UserService when a IUserService is requested.您将配置本机 .net 核心依赖注入,以便在请求IUserService时将HttpClient注入UserService

Then you do然后你做

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

which will erase the native dependency injection configuration for IUserService .这将删除IUserService的本机依赖注入配置。 The IUserService is now registered with UserService without any HttpClient in mind. IUserService现在注册到UserService时没有考虑任何HttpClient

The simplest way to add HttpClient would be to register it like this:添加HttpClient的最简单方法是像这样注册它:

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

or或者

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

If you want to configure your httpclient for a specific service you can create an autofac module which add parameters like this:如果你想为特定服务配置你的 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;
                        }
                    )
                  });
            };
        }
    }
}

Then然后

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");
}));

Cyril's implementation of using an Autofac module works wonderfully, but unfortunately is not compatible with Autofac 6.0+. Cyril 使用 Autofac 模块的实现效果很好,但不幸的是与 Autofac 6.0+ 不兼容。

In order to configure an HttpClient in Autofac 6.0+ for a specific service, an Autofac middleware needs to be implemented:为了在 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);
    }
}

Then the service can be registered, utilizing the middleware:然后可以使用中间件注册服务:

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");
        }));
    });

You can register any type with httpclient as follows extension method.您可以使用 httpclient 注册任何类型,如下扩展方法。

public static ContainerBuilder RegisterWithHttpClient<TInterface, TClass>(this ContainerBuilder builder, Action<IComponentContext, HttpClient> config)
        where TClass: class
    {
        builder
            .RegisterType<TClass>()
            .AsSelf()
            .As<TInterface>()
            .WithParameter(new ResolvedParameter(
                (info, context) => info.ParameterType.IsAssignableFrom(typeof(HttpClient)),
                (info, context) =>
                {
                    var httpClient = context.Resolve<IHttpClientFactory>().CreateClient();
                    config.Invoke(context, httpClient);

                    return httpClient;
                }
            ))
            .InstancePerLifetimeScope();

        return builder;
    }

and register your type.并注册您的类型。

    //in startup.cs or autofac module.
    public void ConfigureContainer(ContainerBuilder container)
    {
        container.RegisterWithHttpClient<IEmailSender, MyEmailSender>((context, client) =>
        {
            var settings = context.Resolve<IOptionsSnapshot<EmailSenderSettings>>().Value;
            client.BaseAddress = new Uri($"{settings.ApiBaseUrl.TrimEnd('/')}/");
            client.Timeout = TimeSpan.FromSeconds(settings.TimeoutSeconds);
        });
    }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM