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

问题描述 投票:0回答:4

我正在创建 MVC Web 应用程序,它使用 .net core 2.2 调用 api,使用单独的

HttpClient
来调用每个控制器(相同的 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}";
}

我有两个问题:

  1. 上面的错误是什么意思?
  2. 为 api 中的不同控制器使用单独的 httpclient 是一个好习惯吗?
c# dependency-injection .net-core httpclient autofac
4个回答
25
投票

通过做

services.AddHttpClient<IUserService, UserService>();  

您将配置本机 .net core 依赖项注入,以便在请求

HttpClient
时将
UserService
注入到
IUserService

然后你就可以了

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

6
投票

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

3
投票

您可以按照以下扩展方法向 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;
    }

并注册您的类型。

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

0
投票

根据https://autofac.readthedocs.io/en/latest/integration/netcore.html#quick-start页面,最简单、最正确的方法是:

// Generate a ServiceCollection to directly add elements to.
var serviceCollection = new ServiceCollection();

// Add the HTTP Client as per the extension.
serviceCollection.AddHttpClient();
serviceCollection.AddHttpClient<IUserService, UserService>();
serviceCollection.AddHttpClient<IPostService, PostService>();

// Build the ContainerBuilder.
var containerBuilder = new ContainerBuilder();

// Populate the builder from the ServiceCollection.
containerBuilder.Populate(serviceCollection);

// Continue with the AutoFac registrations after this point.
© www.soinside.com 2019 - 2024. All rights reserved.