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:
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