I'm creating MVC web application which calls an api using .net core 2.2 using separate HttpClients to call each controller (same api).
Ex:
In startup.cs I use DI as:
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])'.
But I've registered services in autofac module:
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:
public UserService (HttpClient httpClient, IApplicationConfigurations applicationConfig, ILogger<UserService> logger)
{
_httpClient = httpClient;
_applicationConfig = applicationConfig;
_logger = logger;
_remoteServiceBaseUrl = $"{_applicationConfig.WebApiBaseUrl}";
}
I have two questions:
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.
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. The IUserService is now registered with UserService without any HttpClient in mind.
The simplest way to add HttpClient would be to register it like this :
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 :
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+.
In order to configure an HttpClient in Autofac 6.0+ for a specific service, an Autofac middleware needs to be implemented:
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.
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);
});
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With