Re-set the certificate of singleton httpclient (Reconfiguring IHttpclientFactory?) - c#

Using C#, .NET Core 3.1
I add a singleton httpclient via in startup.cs:
services.AddHttpClient<IClientLogic, ClientLogicA>().ConfigurePrimaryHttpMessageHandler(() =>
{
var handler = new HttpClientHandler();
var cert= GetCertFromX();
handler.ClientCertificates.Add(cert);
return handler;
});
But lets say, later in ClientLogicA class, I want to change the certificate, how do I go about doing this and will the change persist for future uses of the httpclient singleton?

So what you want to do is modify the certificate of a HttpClient that is being produced by an IHttpClientFactory. It looks as though Microsoft may be adding this type of functionality in .NET 5, but in the meantime, we need to come up with a way to do it now.
This solution will work with both Named HttpClient and Typed HttpClient objects.
So the issue here is to create the Named or Typed HttpClient where the certificate collection that is bound to the HttpClient can be updated at any time. The problem is we can only set the creation parameters for HttpClient once. After that, the IHttpClientFactory reuses those settings over and over.
So, let's start by looking at how we inject our services:
Named HttpClient Injection Routine
services.AddTransient<IMyService, MyService>();
services.AddSingleton<ICertificateService, CertificateService>();
services.AddHttpClient("MyCertBasedClient").
ConfigurePrimaryHttpMessageHandler(sp =>
new CertBasedHttpClientHandler(
sp.GetRequiredService<ICertificateService>()));
Typed HttpClient Injection Routine
services.AddSingleton<ICertificateService, CertificateService>();
services.AddHttpClient<IMyService, MyService>().
ConfigurePrimaryHttpMessageHandler(sp =>
new CertBasedHttpClientHandler(
sp.GetRequiredService<ICertificateService>()));
We inject a ICertificateService as singleton that holds our current certificate and allows other services to change it. IMyService is injected manually when using Named HttpClient, while when using a Typed HttpClient, IMyService will be automatically injected. When it comes time for the IHttpClientFactory to create our HttpClient, it will call the lambda and produce an extended HttpClientHandler which takes our ICertificateService from our service pipeline as a constructor parameter.
This next part is the source to the ICertificateService. This service maintains the certificate with an "id" (which is just a timestamp of when it was last updated).
CertificateService.cs
public interface ICertificateService
{
void UpdateCurrentCertificate(X509Certificate cert);
X509Certificate GetCurrentCertificate(out long certId);
bool HasCertificateChanged(long certId);
}
public sealed class CertificateService : ICertificateService
{
private readonly object _certLock = new object();
private X509Certificate _currentCert;
private long _certId;
private readonly Stopwatch _stopwatch = new Stopwatch();
public CertificateService()
{
_stopwatch.Start();
}
public bool HasCertificateChanged(long certId)
{
lock(_certLock)
{
return certId != _certId;
}
}
public X509Certificate GetCurrentCertificate(out long certId)
{
lock(_certLock)
{
certId = _certId;
return _currentCert;
}
}
public void UpdateCurrentCertificate(X509Certificate cert)
{
lock(_certLock)
{
_currentCert = cert;
_certId = _stopwatch.ElapsedTicks;
}
}
}
This final part is the class that implements a custom HttpClientHandler. With this we can hook in to all HTTP requests being made by the client. If the certificate has changed, we swap it out before the request is made.
CertBasedHttpClientHandler.cs
public sealed class CertBasedHttpClientHandler : HttpClientHandler
{
private readonly ICertificateService _certService;
private long _currentCertId;
public CertBasedHttpClientHandler(ICertificateService certificateService)
{
_certService = certificateService;
var cert = _certService.GetCurrentCertificate(out _currentCertId);
if(cert != null)
{
ClientCertificates.Add(cert);
}
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
CancellationToken cancellationToken)
{
if(_certService.HasCertificateChanged(_currentCertId))
{
ClientCertificates.Clear();
var cert = _certService.GetCurrentCertificate(out _currentCertId);
if(cert != null)
{
ClientCertificates.Add(cert);
}
}
return base.SendAsync(request, cancellationToken);
}
}
Now I think the biggest down-side to this is if the HttpClient is in the middle of a request on another thread, we could run in to a race condition. You could alleviate that by guarding the code in the SendAsync with a SemaphoreSlim or any other asynchronous thread synchronizing pattern, but that could cause a bottle-neck so I didn't bother doing that. If you want to see that added, I will update this answer.

Related

HttpClient configured in Program.cs is not being passed to MediatR RequestHandler by dependency injection container

I'm working on a Blazor WebAssembly application in .NET 6.0.
I'm using MediatR requests and handlers.
public class DummyRequest : IRequest<string>
{
public Guid Test { get; } = new Guid("e9f41a5d-5da6-4aad-b118-83476b7f40f4");
}
public class DummyHandler : IRequestHandler<DummyRequest, string>
{
private readonly HttpClient _httpClient;
public DummyHandler(HttpClient httpClient)
{
_httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
}
public async Task<string> Handle(DummyRequest request, CancellationToken cancellationToken)
{
// This should be the value configured in Program.cs
string baseAddress = _httpClient.BaseAddress?.AbsoluteUri ?? string.Empty;
// But it's always blank, so we can't make any calls with the HttpClient
await Task.CompletedTask;
return "foobar";
}
}
I'm configuring a different HttpClient for each request handler in Program.cs, then I'm adding MediatR:
builder.Services.AddHttpClient<DummyHandler>((client) => { client.BaseAddress = new Uri("https://api.somewhere.com"); });
builder.Services.AddMediatR(Assembly.GetExecutingAssembly());
I have also tried reversing those calls, so that I add MediatR first, and register the HttpClient for the DummyHandler type afterwards.
At runtime, after that Handler has been instantiated, it should have an _httpClient with a BaseAddress property set to "https://api.somewhere.com".
However, it always gets an HttpClient with a null BaseUri, so the Handler can't use the HttpClient in any operations.
Can anybody see what's gone wrong please?
It seems that MediatR registers interface-implemetation pair so you need to follow the same pattern for the typed client registration. Try the following:
services.AddHttpClient<IRequestHandler<DummyRequest, string>, DummyHandler>((client) => { client.BaseAddress = new Uri("https://api.somewhere.com"); });
Gist with full test code.
Instead of a typed httpclient, you could use a named httpclient.
Thus register as
builder.Services.AddHttpClient("somename", client => { client.BaseAddress = new Uri("https://api.somewhere.com"); });
And in the constructor, inject the httpclientfactory instead:
public DummyHandler(HttpClientFactory httpClientFactory)
{
_httpClient = httpClientFactory.CreateClient("somename");
}
I suggest you to create the wrapper class around your Http client and register it instead.It hides implementation of your connection type and can be extended by other logic or other realization if you need.
Example:
class ApiConnection : IConnection
{
private readonly HttpClient _client;
public ApiConnection(...)
{
_client = new HttpClient();
}
// some other logic
}
Add this class to your Handler (IConnection connection) and use it in handler.
Register as: services.AddSingleton<IConnection, APIConnection>();
In case you are using the IRequestHandler<SomeCommand> alternative where there is no response, MediatR internally converts it to to IRequestHandler<SomeCommand, Unit>, which is what you will need to use to properly inject the HTTP client in your DI:
serviceCollection
.AddHttpClient<IRequestHandler<SomeCommand, Unit>, SomeCommandHandler>((httpClient) =>
{
...
});

ASP .NET Core DI: Provide async BaseAddress to HTTP client

Whenever a client makes a request to my ASP .NET Core 3.1 API, I want to use a HttpClient to do some actions in other services.
I registered HttpClient with Dependency Injection:
services.AddHttpClient<MyHttp>();
Unfortunately the BaseAddressof this HttpClient is not static. Instead the client sends a custom header including a reference to a database-entry.
So in order to determine the BaseAddress for my HttpClient i need to make an async call to my Database.
Currently I do something like the following:
public class SomeController
{
public SomeController(MyHttp http, AddressRepository db)
{
_http = http;
_db = db;
}
public async Task<dynamic> SomeAction([FromRoute] string id)
{
var address = await _db.Get(id);
_http.BaseAddress = new Uri(address);
var res = await _http.GetAsync("some-path");
//Handle response and do some business-logic
return new { };
}
}
This works, but whenever i use MyHttp in any service I need to make sure that this services sets the BaseAddress property.
I'd rather provide the Address as a constructor parameter.
First i thought of a factory implementation. But services.AddScoped(serviceProvider => {}) does not support async operations (And I understand that object initialization should be fast and reliable).
But I feel like my current solution is also a bad practice. Is there any better way of doing this?
That is right that service collection registration does not support async factory - because service resolving should be always fast.
But there is nothing wrong with initializing baseAddress after client creation. So if your wanna make sure that httpClient is initialized with correct base address how about extracting that logic into separate class that will build your http client configuration?
public YourHttpClientFactory {
private IHttpClientFactory _factory;
public YourHttpClientFactory(IHttpClientFactory factory)
{
_factory = factory;
}
public Task<HttpClient> Create(Guid id) {
var client = factory.createClient();
// do your async client initialization
return client;
}
}

How to unit test/dependency inject a class reliant on HttpClient with a custom HttpClientHandler configuration

I'm looking for suggestions on how to improve on my current design for testing a class (example below) that depends on HttpClient with a custom HttpClientHandler configuration. I normally use constructor injection to inject a HttpClient that is consistent across the application, however because this is in a class library I can't rely on the consumers of the library to set up the HttpClientHandler correctly.
For testing I follow the standard approach of replacing HttpClientHandler in the HttpClient constructor. Because I can't rely on the consumer of the library to inject a valid HttpClient I'm not putting this in a public constructor, instead I'm using a private constructor with an internal static method (CreateWithCustomHttpClient()) to create it. The intent behind this is:
Private constructor should not be called by a dependency injection library automatically. I'm aware that if I made it public/internal then some DI libraries that had a HttpClient already registered would call that constructor.
Internal static method can be called by a unit testing library using InternalsVisibleToAttribute
This setup seems quite complex to me and I'm hoping someone might be able to suggest an improvement, I am however aware that this could be quite subjective so if there are any established patterns or design rules to follow in this case I would really appreciate hearing about them.
I've included the DownloadSomethingAsync() method just to demonstrate why the non-standard configuration is required for HttpClientHandler. The default is for redirect responses to automatically redirect internally without returning the response, I need the redirect response so that I can wrap it in a class that report progress on the download (the functionality of that is not relevant to this question).
public class DemoClass
{
private static readonly HttpClient defaultHttpClient = new HttpClient(
new HttpClientHandler
{
AllowAutoRedirect = false
});
private readonly ILogger<DemoClass> logger;
private readonly HttpClient httpClient;
public DemoClass(ILogger<DemoClass> logger) : this(logger, defaultHttpClient) { }
private DemoClass(ILogger<DemoClass> logger, HttpClient httpClient)
{
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
this.httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
}
[Obsolete("This is only provided for testing and should not be used in calling code")]
internal static DemoClass CreateWithCustomHttpClient(ILogger<DemoClass> logger, HttpClient httpClient)
=> new DemoClass(logger, httpClient);
public async Task<FileSystemInfo> DownloadSomethingAsync(CancellationToken ct = default)
{
// Build the request
logger.LogInformation("Sending request for download");
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, "http://example.com/downloadredirect");
// Send the request
HttpResponseMessage response = await httpClient.SendAsync(request, ct);
// Analyse the result
switch (response.StatusCode)
{
case HttpStatusCode.Redirect:
break;
case HttpStatusCode.NoContent:
return null;
default: throw new InvalidOperationException();
}
// Get the redirect location
Uri redirect = response.Headers.Location;
if (redirect == null)
throw new InvalidOperationException("Redirect response did not contain a redirect URI");
// Create a class to handle the download with progress tracking
logger.LogDebug("Wrapping release download request");
IDownloadController controller = new HttpDownloadController(redirect);
// Begin the download
logger.LogDebug("Beginning release download");
return await controller.DownloadAsync();
}
}
In my opinion, I'd use IHttpClientFactory in Microsoft.Extensions.Http, and create a custom dependency injection extension for consumers of the class library to use:
public static class DemoClassServiceCollectionExtensions
{
public static IServiceCollection AddDemoClass(
this IServiceCollection services,
Func<HttpMessageHandler> configureHandler = null)
{
// Configure named HTTP client with primary message handler
var builder= services.AddHttpClient(nameof(DemoClass));
if (configureHandler == null)
{
builder = builder.ConfigurePrimaryHttpMessageHandler(
() => new HttpClientHandler
{
AllowAutoRedirect = false
});
}
else
{
builder = builder.ConfigurePrimaryHttpMessageHandler(configureHandler);
}
services.AddTransient<DemoClass>();
return services;
}
}
In DemoClass, use IHttpClientFactory to create named HTTP client:
class DemoClass
{
private readonly HttpClient _client;
public DemoClass(IHttpClientFactory httpClientFactory)
{
// This named client will have pre-configured message handler
_client = httpClientFactory.CreateClient(nameof(DemoClass));
}
public async Task DownloadSomethingAsync()
{
// omitted
}
}
You could require consumers to must call AddDemoClass in order to use DemoClass:
var services = new ServiceCollection();
services.AddDemoClass();
In this way, you could hide details of HTTP client construction.
Meanwhile, in tests, you could mock IHttpClientFactory to return HttpClient for testing purpose.

Share HttpClient between services

I am working on a Blazor project, and to make the question I have easier to understand, we can say that I am using two different services that handles the Authentication part. Those are registered in the configureservices startup method together with a named httpclient.
services.AddHttpClient("XBOWServicesApi", c =>
{
c.BaseAddress = new Uri(XBOWServicesApi);
});
services.AddSingleton<IService1, Service1>();
services.AddSingleton<IService2, Service2>();
Service 1: Wraps all functionality available in a REST Api. It uses an http client which is set in the constructor via an instanciated httpclientfactory. This needs to be set with a baseurl and an Auth-header to work.
public Service1(IHttpClientFactory clientFactory)
{
this.httpClient = clientFactory.CreateClient("XBOWServicesApi");
}
Service 2: Handles the login/logout functionality using a custom AuthenticationStateProvider. It has its own httpclient, so that I can set the Auth Header for the http client. The constructor works in the same way as for Service 1.
public Service2(IHttpClientFactory clientFactory)
{
this.httpClient = clientFactory.CreateClient("XBOWServicesApi");
}
The reason for this build up is of course that I like to share the same http client, so when it is set in the login/logout methods, service 1 will have the correct auth header when communicating with the api.
However, the client factory provides a new instance everytime, so this will never work.
Any ideas how to handle this?
/Henrik
You can use named client:
services.AddHttpClient("github", c =>
{
c.BaseAddress = new Uri("https://api.github.com/");
// Github API versioning
c.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
// Github requires a user-agent
c.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");
});
Afterwards, just call CreateClient method with corresponding name parameter.
var client = _clientFactory.CreateClient("github");
Each time CreateClient is called:
A new instance of HttpClient is created.
The configuration action is
called.
You can find more details in Microsoft documentation here.
When I read through the Microsoft IHttpClientFactory docs:
Each time you get an HttpClient object from the IHttpClientFactory, a
new instance is returned. But each HttpClient uses an
HttpMessageHandler that's pooled and reused by the IHttpClientFactory
to reduce resource consumption, as long as the HttpMessageHandler's
lifetime hasn't expired.
Does that answer your question?
You can share scoped services between transient HttpClients by using HttpMessageHandlers.
IHttpClient.CreateClient returns a new instance every time, but you can register a HttpMessageHandler as shown below:
services.AddScoped<HandlerData>();
services.AddTransient<HeaderHandler>();
services.AddHttpClient("XBOWServicesApi", c =>
{
c.BaseAddress = new Uri(XBOWServicesApi);
}).AddHttpMessageHandler<HeaderHandler>();
HeaderHandler Class:
public class HeaderHandler : DelegatingHandler
{
private readonly IHttpContextAccessor httpContextAccessor;
public HeaderHandler(IHttpContextAccessor httpContextAccessor)
{
this.httpContextAccessor = httpContextAccessor;
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken )
{
var Data= this.httpContextAccessor.HttpContext.RequestServices.GetRequiredService<HandlerData>();
request.Headers.Add(Data.HeaderName, Data.HeaderValue);
return base.SendAsync(request, cancellationToken);
}
}
HandlerData Class:
public class HandlerData
{
public string HeaderName { get; set; }
public string HeaderValue { get; set; }
}
ServicesCode:
public Service1(IHttpClientFactory clientFactory, HandlerData data)
{
data.HeaderName = "Header1";
data.HeaderValue = "Value";
this.httpClient = clientFactory.CreateClient("XBOWServicesApi");
}
public Service2(IHttpClientFactory clientFactory)
{
//This will contain the same headers as Service1 as HandlerData is Scoped Service
this.httpClient = clientFactory.CreateClient("XBOWServicesApi");
}
Alternatively, you can also use new IHttpMessageHandlerFactory if you need to create handlers that live in the same DI scope as you request:
Reference: https://github.com/aspnet/HttpClientFactory/issues/166

Reusing HttpClient in a web app when new authorization header is needed on each request

I've read a good deal about how HttpClient instances should be reused as much as possible, perhaps even throughout application lifecycle. For completeness sake, here are a couple of the resources that I'm basing my statements on:
Do HttpClient and HttpClientHandler have to be disposed?
You're Using HttpClient Wrong and it is Destabilizing Your Software
What is the overhead of creating a new HttpClient per call in a WebAPI client?
I have a couple of questions regarding this:
How do I create an application-scoped instance of HttpClient in ASP.NET MVC to be shared among all requests? Let's assume there is no IoC container in picture, so I can't just bind it in Singleton scope with container-name-here and call it a day. How would I do it "manually?"
Also, the web service I'm interacting with requires a new authorization token on each request, so even if come up with a way to do #1 above, how do I supply a new authorization header on every request, so that it doesn't collide with potential multiple concurrent requests (coming from different users and whatnot)? My understanding is that HttpClient is fairly thread-safe itself, when it comes to GetAsync and other methods, but setting DefaultAuthorizationHeaders doesn't seem thread-safe to me, is it?
How do I keep it unit-testable?
This is how my in-progress code looks like so far (in a somewhat simplified form for brevity here):
public class MyHttpClientWrapper : IDisposable
{
private readonly HttpClient _httpClient;
private readonly TokenManager _tokenManager;
public HttpServiceClient(HttpClient httpClient, TokenManager tokenManager)
{
_httpClient = httpClient;
_tokenManager = tokenManager;
_httpClient.BaseAddress = new Uri("https://someapp/api/");
_httpClient.DefaultRequestHeaders.Accept.Add(new
MediaTypeWithQualityHeaderValue("application/json"));
}
public string GetDataByQuery(string query)
{
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
"amx", _tokenManager.GetNewAuthorizationCode());
var response = _httpClient.GetAsync(query).Result;
return response.Content.ReadAsStringAsync().Result;
}
public void Dispose()
{
HttpClient?.Dispose();
}
}
Side note: I am using dependency injection here, but not necessarily an IoC container (for the reasons irrelevant to this discussion).
After some additional reading online, I came up with this:
public class MyHttpClientWrapper
{
private static readonly HttpClient _httpClient;
private readonly TokenManager _tokenManager;
static MyHttpClientWrapper()
{
// Initialize the static http client:
_httpClient = new HttpClient();
_httpClient.BaseAddress = new Uri("https://someapp/api/");
_httpClient.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
}
public HttpServiceClient(TokenManager tokenManager)
{
_tokenManager = tokenManager;
}
public string GetDataByQuery(string query)
{
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
"amx", _tokenManager.GetNewAuthorizationCode());
var response = _httpClient.GetAsync(query).Result;
return response.Content.ReadAsStringAsync().Result;
}
}
The only problem is, it's not unit-testable. I have no way of replacing the http client with a fake one. I could encapsulate _httpClient in a property and make it non-readonly. That way the httpclient could be overwritten from a unit test via the property setter. I'm not sure I'm in love with that solution.
Another idea is to do lazy initialization of the static _httpClient via a property, but I'm not sure if that is better.
Any thoughts about either of those ideas? Any other thoughts?
I decided to do this slightly differently, so that I can allow unit-testing. I'm using property injection here to allow for overriding the Http Client in unit tests. But in production code, it would simply get self-initialized (lazy) on first access of the Client property.
public class MyHttpClientWrapper
{
private static readonly object ThreadLock = new object();
private static HttpClient _httpClient;
private readonly TokenManager _tokenManager;
public Client
{
get
{
if (_httpClient != null) return _httpClient;
// Initialize http client for the first time, and lock for thread-safety
lock (ThreadLock)
{
// Double check
if (_httpClient != null) return _httpClient;
_httpClient = new HttpClient();
InitClient(_httpClient);
return _httpClient;
}
}
set
{
// primarily used for unit-testing
_httpClient = value;
InitClient(_httpClient);
}
}
private void InitClient(HttpClient httpClient)
{
httpClient.BaseAddress = new Uri("https://someapp/api/");
httpClient.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
}
public HttpServiceClient(TokenManager tokenManager)
{
_tokenManager = tokenManager;
}
public string GetDataByQuery(string query)
{
Client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
"amx", _tokenManager.GetNewAuthorizationCode());
var response = _httpClient.GetAsync(query).Result;
return response.Content.ReadAsStringAsync().Result;
}
}

Categories

Resources