I have ApiAdapter with using HttpClient to send HTTP requests to my server. to avoid create to many HttpClient, I am using DependenceInjection with singleton mode for "ApiAdapter" class. it works well when I call only one by one HTTP request
I want to use Parallel.ForEachAsync to reduce performance time because I want to create 1000 people through ApiAdapter. It throw exception with below message error after it created ~ 30 - 100 person with message:
System.Net.Http.HttpRequestException: The SSL connection could not be established, see inner exception.
---> System.ArgumentNullException: Value cannot be null. (Parameter 'value')
at System.Collections.CollectionBase.OnValidate(Object value)
at System.Security.Cryptography.X509Certificates.X509CertificateCollection.OnValidate(Object value)
at System.Collections.CollectionBase.System.Collections.IList.Add(Object value)
at System.Security.Cryptography.X509Certificates.X509CertificateCollection.AddRange(X509CertificateCollection value)
at System.Net.Security.CertificateHelper.GetEligibleClientCertificate(X509CertificateCollection candidateCerts)
at System.Net.Http.HttpClientHandler.<set_ClientCertificateOptions>b__57_0(Object sender, String targetHost, X509CertificateCollection localCertificates, X509Certificate remoteCertificate, String[] acceptableIssuers)
at System.Net.Security.SslStream.UserCertSelectionCallbackWrapper(String targetHost, X509CertificateCollection localCertificates, X509Certificate remoteCertificate, String[] acceptableIssuers)
at System.Net.Security.SecureChannel.AcquireClientCredentials(Byte[]& thumbPrint)
at System.Net.Security.SecureChannel.GenerateToken(ReadOnlySpan1 inputBuffer, Byte[]& output) at System.Net.Security.SecureChannel.NextMessage(ReadOnlySpan
1 incomingBuffer)
at System.Net.Security.SslStream.ForceAuthenticationAsync[TIOAdapter](TIOAdapter adapter, Boolean receiveFirst, Byte[] reAuthenticationData, Boolean isApm)
at System.Net.Http.ConnectHelper.EstablishSslConnectionAsync(SslClientAuthenticationOptions sslOptions, HttpRequestMessage request, Boolean async, Stream stream, CancellationToken cancellationToken)
--- End of inner exception stack trace ---
at System.Net.Http.ConnectHelper.EstablishSslConnectionAsync(SslClientAuthenticationOptions sslOptions, HttpRequestMessage request, Boolean async, Stream stream, CancellationToken cancellationToken)
at System.Net.Http.HttpConnectionPool.ConnectAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
at System.Net.Http.HttpConnectionPool.CreateHttp11ConnectionAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
at System.Net.Http.HttpConnectionPool.AddHttp11ConnectionAsync(HttpRequestMessage request)
at System.Threading.Tasks.TaskCompletionSourceWithCancellation`1.WaitWithCancellationAsync(CancellationToken cancellationToken)
at System.Net.Http.HttpConnectionPool.GetHttp11ConnectionAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
at System.Net.Http.HttpConnectionPool.SendWithVersionDetectionAndRetryAsync(HttpRequestMessage request, Boolean async, Boolean doRequestAuth, CancellationToken cancellationToken)
at System.Net.Http.RedirectHandler.SendAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
at System.Net.Http.HttpClient.g__Core|83_0(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationTokenSource cts, Boolean disposeCts, CancellationTokenSource pendingRequestsCts, Cancellatio
nToken originalCancellationToken)
at ---------.ApiAdapter.PostRequestAsync
Here is my code
public async Task RequestPerson()
{
var threadRequestPins = Enumerable.Range(0, listSize).ToList();
await Parallel.ForEachAsync(datasToProcess, async (_, token) =>{
var fakfurTestApiService = LoadTestKernel.Kernel.Get<IFakfurApiService>();
var personCreated = await fakfurTestApiService.CallPostRequestAsync(<Url path>, <dataBody>);
//CallPostRequestAsync will call ApiAdapter with PostRequestAsync() method below
});
}
//constructor
public ApiAdapter()
{
_cookieContainer = new CookieContainer();
_httpClientHandler = new HttpClientHandler
{
CookieContainer = _cookieContainer
};
_httpClient = new HttpClient(_httpClientHandler);
}
private void AddCertificates()
{
if (_certificate2Collection != null )
{
_httpClientHandler.ClientCertificates.AddRange(_certificate2Collection);
}
}
public async Task<TResult> PostRequestAsync<TResult, TBodyRequest>(string testApiRelativeUrl, TBodyRequest bodyRequest,
CancellationToken cancellationToken = default) where TResult : class
{
var uriBuilder = BuildRequestUri(testApiRelativeUrl);
var request = new HttpRequestMessage(HttpMethod.Post, uriBuilder.Uri);
AddCertificate(request);
request.Content = new StringContent(_jsonSerializer.SerializeObject(bodyRequest), Encoding.UTF8,;
var response = await _httpClient.SendAsync(request, cancellationToken);
return await ExtractBodyFromResponseAsync<TResult>(response);
}
It's difficult to guage what's actually going on here as the code you have provided is missing alot.
However, as per this issue > https://github.com/dotnet/runtime/issues/52779 You cannot add certificates whilst building the request or when the HttpClient is in use.
You should use the SocketsHttpHandler
class instead of HttpClientHandler
.
You can assign your certificate handler delegate in your constructor.
private readonly CookieContainer _cookieContainer;
private readonly SocketsHttpHandler _socketsHttpHandler;
private readonly HttpClient _httpClient;
//constructor
public ApiAdapter()
{
_cookieContainer = new CookieContainer();
_socketsHttpHandler = new SocketsHttpHandler
{
CookieContainer = _cookieContainer,
SslOptions = new System.Net.Security.SslClientAuthenticationOptions()
{
LocalCertificateSelectionCallback = (object sender, string targetHost, X509CertificateCollection localCertificates, X509Certificate? remoteCertificate, string[] acceptableIssuers) =>
{
return <your certificate here>;
}
}
};
_httpClient = new HttpClient(_socketsHttpHandler);
}
Sidenote: If you're using .NET Core you should really look into using Dependency Injection to reference your services such as the HttpClient.