I have a third party api I am using for a number of my Services in .NET Core Web API project.
So, I have something like below:
services.AddHttpClient<IUserService, UserService>(client =>
{
client.BaseAddress = new Uri(Environment.GetEnvironmentVariable("EXTERNAL_SERVICE_BASE_URL") ??
throw new ArgumentNullException("EXTERNAL_SERVICE_BASE_URLcannot be null"));
client.DefaultRequestHeaders.Add("removed", "removed");
}).ConfigurePrimaryHttpMessageHandler(() =>
{
HttpClientHandler clientHandler = new HttpClientHandler
{
ClientCertificateOptions = ClientCertificateOption.Manual,
ServerCertificateCustomValidationCallback = (sender, cert, chain, sslPolicyErrors) => true
};
clientHandler.ClientCertificates.Add(new X509Certificate2(securitySession.CertificateData.Data, securitySession.CertificateData.Password));
return new HttpClientXRayTracingHandler(clientHandler);
})
.AddPolicyHandler(HttpPolicies.GetRetryPolicy())
.SetHandlerLifetime(TimeSpan.FromSeconds(1));
This is fine and I can access the HttpClient
in UserService
and call my External API fine. However what I am going to need now is more Services in my code - but they use the same EXTERNAL_API - so lets say and AccountService
and a CustomerService
- and i'll end up with this:
services.AddHttpClient<IAccountService, AccountService>(client =>
{
client.BaseAddress = new Uri(Environment.GetEnvironmentVariable("EXTERNAL_SERVICE_BASE_URL") ??
throw new ArgumentNullException("EXTERNAL_SERVICE_BASE_URLcannot be null"));
client.DefaultRequestHeaders.Add("removed", "removed");
}).ConfigurePrimaryHttpMessageHandler(() =>
{
HttpClientHandler clientHandler = new HttpClientHandler
{
ClientCertificateOptions = ClientCertificateOption.Manual,
ServerCertificateCustomValidationCallback = (sender, cert, chain, sslPolicyErrors) => true
};
clientHandler.ClientCertificates.Add(new X509Certificate2(securitySession.CertificateData.Data, securitySession.CertificateData.Password));
return new HttpClientXRayTracingHandler(clientHandler);
})
.AddPolicyHandler(HttpPolicies.GetRetryPolicy())
.SetHandlerLifetime(TimeSpan.FromSeconds(1));
services.AddHttpClient<ICustomerService, CustomerService>(client =>
{
client.BaseAddress = new Uri(Environment.GetEnvironmentVariable("EXTERNAL_SERVICE_BASE_URL") ??
throw new ArgumentNullException("EXTERNAL_SERVICE_BASE_URLcannot be null"));
client.DefaultRequestHeaders.Add("removed", "removed");
}).ConfigurePrimaryHttpMessageHandler(() =>
{
HttpClientHandler clientHandler = new HttpClientHandler
{
ClientCertificateOptions = ClientCertificateOption.Manual,
ServerCertificateCustomValidationCallback = (sender, cert, chain, sslPolicyErrors) => true
};
clientHandler.ClientCertificates.Add(new X509Certificate2(securitySession.CertificateData.Data, securitySession.CertificateData.Password));
return new HttpClientXRayTracingHandler(clientHandler);
})
.AddPolicyHandler(HttpPolicies.GetRetryPolicy())
.SetHandlerLifetime(TimeSpan.FromSeconds(1));
The only change being the Service in my App that is using the HttpClient
. Is there a way I can move all the common plumbing of the wire up to a private method that can be called when I am adding the http client to each of my services?
CodePudding user response:
I would suggest to have a single named client
services.AddHttpClient("commonClient", client =>
{
client.BaseAddress = new Uri(Environment.GetEnvironmentVariable("EXTERNAL_SERVICE_BASE_URL") ??
throw new ArgumentNullException("EXTERNAL_SERVICE_BASE_URL cannot be null"));
client.DefaultRequestHeaders.Add("removed", "removed");
}).ConfigurePrimaryHttpMessageHandler(() =>
{
HttpClientHandler clientHandler = new HttpClientHandler
{
ClientCertificateOptions = ClientCertificateOption.Manual,
ServerCertificateCustomValidationCallback = (sender, cert, chain, sslPolicyErrors) => true
};
clientHandler.ClientCertificates.Add(new X509Certificate2(securitySession.CertificateData.Data, securitySession.CertificateData.Password));
return new HttpClientXRayTracingHandler(clientHandler);
})
.AddPolicyHandler(HttpPolicies.GetRetryPolicy())
.SetHandlerLifetime(TimeSpan.FromSeconds(1));
and 3 typed clients
services.AddHttpClient<IUserService, UserService>();
services.AddHttpClient<IAccountService, AccountService>();
services.AddHttpClient<ICustomerService, CustomerService>();
The usage of these components are a bit different than using just a named or just a typed client
readonly IUserService client;
public XYZController(IHttpClientFactory namedClientFactory, ITypedHttpClientFactory<UserService> typedClientFactory)
{
var namedClient = namedClientFactory.CreateClient("commonClient");
client = typedClientFactory.CreateClient(namedClient);
}
- First via the
IHttpClientFactory
we retrieve the"commonClient"
- Then we create a new instance of
UserService
by passing the previously retrieved named client
NOTE: The type parameter of ITypedHttpClientFactory
must be the concrete type not the interface otherwise you would receive an InvalidOperationException
CodePudding user response:
You can create an IServiceCollection
extension method:
public static class ServiceCollectionExtensions
{
// Change securitySession parameter to the appropriate type
public static void AddCustomHttpClient<TClient, TImplementation>(this IServiceCollection services, SecuritySession securitySession)
where TClient : class
where TImplementation : class, TClient
{
services.AddHttpClient<TClient, TImplementation>(client =>
{
client.BaseAddress = new Uri(Environment.GetEnvironmentVariable("EXTERNAL_SERVICE_BASE_URL") ??
throw new ArgumentNullException("EXTERNAL_SERVICE_BASE_URLcannot be null"));
client.DefaultRequestHeaders.Add("removed", "removed");
}).ConfigurePrimaryHttpMessageHandler(() =>
{
HttpClientHandler clientHandler = new HttpClientHandler
{
ClientCertificateOptions = ClientCertificateOption.Manual,
ServerCertificateCustomValidationCallback = (sender, cert, chain, sslPolicyErrors) => true
};
clientHandler.ClientCertificates.Add(new X509Certificate2(securitySession.CertificateData.Data, securitySession.CertificateData.Password));
return new HttpClientXRayTracingHandler(clientHandler);
})
.AddPolicyHandler(HttpPolicies.GetRetryPolicy())
.SetHandlerLifetime(TimeSpan.FromSeconds(1));
}
}
And use it like this:
services.AddCustomHttpClient<IUserService, UserService>(securitySession);
services.AddCustomHttpClient<IAccountService, AccountService>(securitySession);
services.AddCustomHttpClient<ICustomerService, CustomerService>(securitySession);
CodePudding user response:
if you want the same httpclient to be reused then create one like
services.AddHttpClient<IHTTPservice, HTTPservice>(client =>
{
client.BaseAddress = new Uri(Environment.GetEnvironmentVariable("EXTERNAL_SERVICE_BASE_URL") ??
throw new
ArgumentNullException("EXTERNAL_SERVICE_BASE_URLcannot be null"));
client.DefaultRequestHeaders.Add("removed", "removed");
}).ConfigurePrimaryHttpMessageHandler(() =>
{
HttpClientHandler clientHandler = new HttpClientHandler
{
ClientCertificateOptions = ClientCertificateOption.Manual,
ServerCertificateCustomValidationCallback = (sender, cert, chain,
sslPolicyErrors) => true
};
clientHandler.ClientCertificates.Add(new
X509Certificate2(securitySession.CertificateData.Data,
securitySession.CertificateData.Password));
return new HttpClientXRayTracingHandler(clientHandler);
})
.AddPolicyHandler(HttpPolicies.GetRetryPolicy())
.SetHandlerLifetime(TimeSpan.FromSeconds(1));
and define is as for example
public interface IHTTPservice
{
Task<T> Get<T>(string uri, int timeOut = 100, CancellationToken token = default);
Task<T> Post<T>(string uri, HttpContent content, int timeOut = 100, CancellationToken token = default);
Task<T> Post<T>(string uri, object value, int timeOut = 100, CancellationToken token = default);
}
public class HttpService : IHttpService
{
private readonly HttpClient _httpClient;
public HttpService(HttpClient httpClient)
{
_httpClient = httpClient;
}
public async Task<T> Get<T>(string uri, int timeOut = 100, CancellationToken token = default)
{
var request = new HttpRequestMessage(HttpMethod.Get, uri);
return await sendRequest<T>(request,timeOut, token);
}
public async Task<T> Post<T>(string uri, HttpContent content, int timeOut = 100, CancellationToken token = default)
{
var request = new HttpRequestMessage(HttpMethod.Post, uri);
request.Content = content;
return await sendRequest<T>(request, timeOut, token);
}
public async Task<T> Post<T>(string uri, object value, int timeOut = 100, CancellationToken token = default)
{
var request = new HttpRequestMessage(HttpMethod.Post, uri);
request.Content = new StringContent(JsonSerializer.Serialize(value), Encoding.UTF8, "application/json");
return await sendRequest<T>(request, timeOut, token);
}
// helper methods
private async Task<T> sendRequest<T>(HttpRequestMessage request, int timeOut = 100, CancellationToken token = default)
{
try
{
using var requestCTS = new CancellationTokenSource(TimeSpan.FromSeconds(timeOut));
using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(requestCTS.Token, token);
using var response = await _httpClient.SendAsync(request, linkedCts.Token);
// throw exception on error response
if (!response.IsSuccessStatusCode)
{
var error = await response.Content.ReadAsStringAsync(token);
throw new Exception(error);
}
return (await response.Content.ReadFromJsonAsync<T>())!;
}
catch (AccessTokenNotAvailableException)
{
//what you want
}
and then You may inject this IHTTPservice
where You want and use this single generic HttpService in other services.
not sure if it is best practise - please comment of someone known but it works well.