Que*_*ner 3 c# dependency-injection dotnet-httpclient asp.net-core httpclientfactory
使用 C#、.NET Core 3.1
我httpclient通过 in添加一个单例startup.cs:
services.AddHttpClient<IClientLogic, ClientLogicA>().ConfigurePrimaryHttpMessageHandler(() =>
{
var handler = new HttpClientHandler();
var cert= GetCertFromX();
handler.ClientCertificates.Add(cert);
return handler;
});
Run Code Online (Sandbox Code Playgroud)
但是,假设稍后在ClientLogicA课堂上,我想更改证书,我该如何执行此操作,并且更改是否会持续存在以供将来使用 httpclient 单例?
因此,您要做的就是修改 .aHttpClient生成的 .a的证书IHttpClientFactory。看起来 Microsoft 可能会在 .NET 5 中添加此类功能,但与此同时,我们现在需要想出一种方法来实现这一点。
该解决方案适用于命名对象HttpClient和类型HttpClient对象。
因此,这里的问题是创建命名或类型HttpClient,其中绑定到的证书集合HttpClient可以随时更新。问题是我们只能设置HttpClient一次创建参数。之后,他们IHttpClientFactory会一遍又一遍地重复使用这些设置。
那么,让我们首先看看如何注入我们的服务:
命名的 HttpClient 注入例程
services.AddTransient<IMyService, MyService>();
services.AddSingleton<ICertificateService, CertificateService>();
services.AddHttpClient("MyCertBasedClient").
ConfigurePrimaryHttpMessageHandler(sp =>
new CertBasedHttpClientHandler(
sp.GetRequiredService<ICertificateService>()));
Run Code Online (Sandbox Code Playgroud)
类型化 HttpClient 注入例程
services.AddSingleton<ICertificateService, CertificateService>();
services.AddHttpClient<IMyService, MyService>().
ConfigurePrimaryHttpMessageHandler(sp =>
new CertBasedHttpClientHandler(
sp.GetRequiredService<ICertificateService>()));
Run Code Online (Sandbox Code Playgroud)
我们注入一个单ICertificateService例来保存我们当前的证书并允许其他服务更改它。使用 Named 时手动注入,而使用 Typed 时将自动注入。当创建 our时,它将调用 lambda 并生成一个扩展,它将我们的服务管道中的 our作为构造函数参数。IMyServiceHttpClientHttpClientIMyServiceIHttpClientFactoryHttpClientHttpClientHandlerICertificateService
下一部分是ICertificateService. 该服务使用“id”维护证书(这只是上次更新时间的时间戳)。
证书服务.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;
}
}
}
Run Code Online (Sandbox Code Playgroud)
最后一部分是实现自定义HttpClientHandler. 这样我们就可以挂钩客户端发出的所有HTTP 请求。如果证书已更改,我们会在发出请求之前将其更换。
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);
}
}
Run Code Online (Sandbox Code Playgroud)
现在我认为最大的缺点是如果HttpClient处于另一个线程的请求中间,我们可能会遇到竞争条件。SendAsync您可以通过使用一个或任何其他异步线程同步模式来保护代码来缓解这种情况SemaphoreSlim,但这可能会导致瓶颈,所以我没有费心这样做。如果您想看到添加的内容,我将更新此答案。