I'm writing a class that uses HttpClient to access an API and I want to throttle the number of concurrent calls that can be made to a certain function in this class. The trick is though that the limit is per tenant and multiple tenants might be using their own instance of the class at a time.
My Tenant class is just a container for read-only context information.
public class Tenant
{
public string Name { get; }
public string ApiKey { get; }
}
Here's the ApiClient:
public class ApiClient
{
private readonly Tenant tenant;
public ApiClient(Tenant tenant)
{
this.tenant = tenant;
}
public async Task<string> DoSomething()
{
var response = await this.SendCoreAsync();
return response.ToString();
}
private Task<XElement> SendCore()
{
using (var httpClient = new HttpClient())
{
var httpRequest = this.BuildHttpRequest();
var httpResponse = await httpClient.SendAsync(httpRequest);
return XElement.Parse(await httpResponse.Content.ReadAsStringAsync());
}
}
}
What I want to do is throttle the SendCore method and limit it to two concurrent requests per tenant. I've read suggestions of using TPL or SemaphoreSlim to do basic throttling (such as here: Throttling asynchronous tasks), but I'm not clear on how to add in the further complication of the tenant.
Thanks for the suggestions.
UPDATE
I've attempted to use a set of SemaphoreSlim objects (one per tenant) contained in a ConcurrentDictionary. This seems to work, but I'm not sure if this is ideal. The new code is:
public class ApiClient
{
private static readonly ConcurrentDictionary<string, SemaphoreSlim> Semaphores = new ConcurrentDictionary<string, SemaphoreSlim>();
private readonly Tenant tenant;
private readonly SemaphoreSlim semaphore;
public ApiClient(Tenant tenant)
{
this.tenant = tenant;
this.semaphore = Semaphores.GetOrAdd(this.tenant.Name, k => new SemaphoreSlim(2));
}
public async Task<string> DoSomething()
{
var response = await this.SendCoreAsync);
return response.ToString();
}
private Task<XElement> SendCore()
{
await this.semaphore.WaitAsync();
try
{
using (var httpClient = new HttpClient())
{
var httpRequest = this.BuildHttpRequest();
var httpResponse = await httpClient.SendAsync(httpRequest);
return XElement.Parse(await httpResponse.Content.ReadAsStringAsync());
}
}
finally
{
this.semaphore.Release();
}
}
}
Your SemaphoreSlim approach seems mostly reasonable to me.
One potential issue is that if Tenants can come and go over the lifetime of the application, then you'll be keeping semaphores even for Tenants that don't exist anymore.
A solution to that would be to use ConditionalWeakTable<Tenant, SemaphoreSlim> instead of your ConcurrentDictionary, which makes sure its keys can be garbage collected and when they are, it releases the value.
Related
I am trying to use SimpleInjector in a WPF Application (.NET Framework). We use it in exactly the same way in many of our Services but for some reason when I am attempting to implement the same logic in this WPF Application, the call to the HttpClient().GetAsync is hanging. We think it is because for some reason the Task is not executing.
I am registering the objects from the OnStartUp element of App.xaml.cs as below. Inside the SetupService Constructor we call a SetupService URL (set in the SetupConfiguration Section of the App.Config) to get the SetupResponse to use in the app.
It is ultimately hanging in the ServiceClient.GetAsync method, I have tried to show the flow below:
All classes appear to have been injected correctly, and the ServiceClient is populated in exactly the same way as the same point in one of our working services. We're at a loss as to what is happening, and how to fix this.
Finally, SetupService is being injected in other Classes - so I would rather get it working like this, rather than remove the call from the SimpleInjector mechanism.
Any help is very much appreciated.
public partial class App : Application
{
private static readonly Container _container = new Container();
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
RegisterDependencies();
_container.Verify();
}
private void RegisterDependencies()
{
var serviceConfigSection = ServiceConfigurationSection.Get();
_container.RegisterSingle<ILoggingProvider, LoggingProvider>();
_container.RegisterSingle<IServiceClient>(() => new ServiceClient(_container.GetInstance<ILoggingProvider>()));
_container.RegisterSingle<IConfigurationSection>(() => SetupConfigurationSection.Get());
_container.RegisterSingle<ISetupService, SetupService>();
}
}
public class SetupService: ISetupService
{
private static readonly Dictionary<string, string> AcceptType = new Dictionary<string, string>
{
{"Accept", "application/xml"}
};
private const string AuthenticationType = "Basic";
private readonly IServiceClient _serviceClient;
private readonly ILoggingProvider _logger;
private readonly IConfigurationSection _configuration;
public SetupService(IConfigurationSection configuration, IServiceClient serviceClient, ILoggingProvider logger)
{
_serviceClient = serviceClient;
_logger = logger;
_configuration = kmsConfiguration;
RefreshSetup();
}
public void RefreshSetup()
{
try
{
var token = BuildIdentityToken();
var authHeaderClear = string.Format("IDENTITY_TOKEN:{0}", token);
var authenticationHeaderValue =
new AuthenticationHeaderValue(AuthenticationType, Convert.ToBase64String(Encoding.ASCII.GetBytes(authHeaderClear)));
_serviceClient.Url = _configuration.Url;
var httpResponse = _serviceClient.GetAsync(string.Empty, authenticationHeaderValue, AcceptType).Result;
var responseString = httpResponse.Content.ReadAsStringAsync().Result;
_response = responseString.FromXML<SetupResponse>();
}
catch (Exception e)
{
throw
}
}
public class ServiceClient : IServiceClient
{
private const string ContentType = "application/json";
private string _userAgent;
private ILoggingProvider _logger;
public string Url { get; set; }
public string ProxyAddress { get; set; }
public int TimeoutForRequestAndResponseMs { get; set; }
public int HttpCode { get; private set; }
public ServiceClient(ILoggingProvider logger = null)
{
_logger = logger;
}
public async Task<HttpResponseMessage> GetAsync(string endpoint, AuthenticationHeaderValue authenticationHeaderValue = null, IDictionary<string, string> additionalData = null, IDictionary<string, string> additionalParams = null)
{
using (var client = new HttpClient())
{
client.BaseAddress = new Uri(Url);
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(ContentType));
if (authenticationHeaderValue != null)
client.DefaultRequestHeaders.Authorization = authenticationHeaderValue;
ProcessHeader(client.DefaultRequestHeaders, additionalData);
var paramsQueryString = ProcessParams(additionalParams);
if (!string.IsNullOrEmpty(paramsQueryString))
endpoint = $"{endpoint}?{paramsQueryString}";
return await client.GetAsync(endpoint); **// HANGS ON THIS LINE!**
}
}
}
}
If you block on asynchronous code from a UI thread, then you can expect deadlocks. I explain this fully on my blog. In this case, the cause of the deadlock is Result. There's a couple of solutions.
The one I recommend is to rethink your user experience. Your UI shouldn't be blocking on an HTTP call to complete before it shows anything; instead, immediately (and synchronously) display a UI (i.e., some "loading..." screen), and then update that UI when the HTTP call completes.
The other is to block during startup. There's a few patterns for this. None of them work in all situations, but one that usually works is to wrap the asynchronous work in Task.Run and then block on that, e.g., var httpResponse = Task.Run(() => _serviceClient.GetAsync(string.Empty, authenticationHeaderValue, AcceptType)).GetAwaiter().GetResult(); and similar for other blocking calls.
Blocking before showing a UI is generally considered a bad UX. App stores generally disallow it. So that's why I recommend changing the UX. You may find an approach like this helpful.
Thanks for your Responses, I just wanted to sync the solution I've gone for.
It was risky for me to change the code in SetupService to remove the .Result, even though this was probably the correct solution, as I did not want to affect the other working Services using the SetupService library already there.
I ended up moving the regsitrations off the UI Thread by embedding the SimpleInjector code in a Code library, Creating a Program.cs and Main() and setting that as my Entry point.
static class Program
{
public static readonly Container _container = new Container();
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
public static void Main(){
var app = new MyApp.App();
Register();
app.Run(_container.GetInstance<MainWindow>());
}
static void Register()
{
_container.Register<MainWindow>();
MySimpleInjector.Register(_container);
_container.Verify();
}
}
and then, in a Separate .dll project, MyApp.Common
public class MySimpleInjector
{
private readonly Container _container;
public static void Register(Container container)
{
var injector = new MySimpleInjector(container);
}
private void RegisterDependencies()
{
var serviceConfigSection = ServiceConfigurationSection.Get();
_container.RegisterSingle<ILoggingProvider, LoggingProvider>();
_container.RegisterSingle<IServiceClient>(() => new ServiceClient(_container.GetInstance<ILoggingProvider>()));
_container.RegisterSingle<IConfigurationSection>(() => SetupConfigurationSection.Get());
_container.RegisterSingle<ISetupService, SetupService>();
}
}
I appreciate that this may not be the ideal solution - but it suits my purposes.
Again, thanks for your help and comments!
Andrew.
I'm looking for an approach to locking that, by default, makes sure that all calls to a single API are run mutually exclusive using distributed locking. However, at the same time I need the option to instead lock larger blocks of code (critical procedures) containing several calls to that API. Those calls should still be run mutually exclusive. In those cases the approach should be re-entrant, so that each call isn't blocked because the block of code it is in already holds the lock. It should also support re-entrancy if there are several methods nested that lock sections of code.
Examples of use:
// Should have lock registered by default (f.ex. in HttpMessageHandler)
await _deviceClient.PerformAction();
async Task CriticalProcedure()
{
// Should only use one lock that is reused in nested code (re-entrant)
await using (await _reentrantLockProvider.AcquireLockAsync())
{
await _deviceClient.TriggerAction();
await SharedCriticalProcedure();
}
// Should only dispose lock at this point
}
async Task SharedCriticalProcedure()
{
await using (await _customLockProvider.AcquireLockAsync())
{
await _deviceClient.HardReset();
await _deviceClient.Refresh();
}
}
// Should be forced to run sequentially even though they are not awaited (mutex)
var task1 = _deviceClient.PerformAction1();
var task2 = _deviceClient.PerformAction2();
await Task.WhenAll(task1, task2);
Background:
My team is working on a WebAPI that is responsible for making calls to hardware devices. When an endpoint in our API is called, we get a header that identifies the hardware device in question, used in startup to configure the baseUrl of our HttpClients, and we make one or more calls to that API. We have the following limitations:
A device shouldn't be called when it is already busy with a request (mutual exclusion)
Some procedures against the device (blocks of code containing several calls) are critical and shouldn't be interrupted by other calls to the device (why I want re-entry)
A user may run multiple requests to our API simultaneously, so the locking should work across requests
Our WebAPI may have multiple deployments, so the locking should be distributed
We use Refit to describe the API of our hardware devices and create HttpClients
I have created the following solution which I believe works. However, it seems clumsy and overengineered, mostly because HttpMessageHandlers have unpredictable lifetimes, not scoped to request, so I needed to use TraceIdentifier and a dictionary to enable re-entry during the request lifecycle.
// In startup
service.AddSingleton<IReentrantLockProvider, ReentrantLockProvider>();
services.
.AddHttpClient(IDeviceClient)
.AddTypedClient(client => RestService.For<IDeviceClient>(client, refitSettings))
.ConfigureHttpClient((provider, client) => ConfigureHardwareBaseUrl())
.AddHttpMessageHandler<HardwareMutexMessageHandler>();
public class HardwareMutexMessageHandler : DelegatingHandler
{
private readonly IReentrantLockProvider _reentrantPanelLockProvider;
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly ConcurrentDictionary<string, object> _locks;
public HardwareMutexMessageHandler(IReentrantLockProvider reentrantPanelLockProvider, IHttpContextAccessor httpContextAccessor)
{
_reentrantPanelLockProvider = reentrantPanelLockProvider;
_httpContextAccessor = httpContextAccessor;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
await using (await _reentrantPanelLockProvider.AcquireLockAsync(cancellationToken))
{
var hardwareId = _httpContextAccessor.HttpContext.Request.Headers["HardwareId"];
var mutex = _locks.GetOrAdd(hardwareId, _ => new());
// This is only used to handle cases where developer chooses to batch calls or forgets to await a call
lock (mutex)
{
return base.SendAsync(request, cancellationToken).Result;
}
}
}
}
public class ReentrantLockProvider : IReentrantLockProvider
{
private readonly IDistributedLockProvider _distributedLockProvider;
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly ConcurrentDictionary<string, ReferenceCountedDisposable> _lockDictionary;
private readonly object _lockVar = new();
public ReentrantLockProvider(IDistributedLockProvider distributedLockProvider, IHttpContextAccessor httpContextAccessor)
{
_distributedLockProvider = distributedLockProvider;
_httpContextAccessor = httpContextAccessor;
_lockDictionary = new ConcurrentDictionary<string, ReferenceCountedDisposable>();
}
public async Task<IAsyncDisposable> AcquireLockAsync(CancellationToken cancellationToken = default)
{
var hardwareId = _httpContextAccessor.HttpContext.Request.Headers["HardwareId"];
var requestId = _httpContextAccessor.HttpContext.TraceIdentifier;
lock (_lockVar)
{
if (_lockDictionary.TryGetValue(requestContext.CorrelationId, out referenceCountedLock))
{
referenceCountedLock.RegisterReference();
return referenceCountedLock;
}
acquireLockTask = _distributedLockProvider.AcquireLockAsync(hardwareId, timeout: null, cancellationToken);
referenceCountedLock = new ReferenceCountedDisposable(async () =>
await RemoveLock(acquireLockTask.Result, requestContext.CorrelationId)
);
_lockDictionary.TryAdd(requestContext.CorrelationId, referenceCountedLock);
}
}
private async Task RemoveLock(IDistributedSynchronizationHandle acquiredLock, string correlationId)
{
ValueTask disposeAsyncTask;
lock (_lockVar)
{
disposeAsyncTask = acquiredLock.DisposeAsync();
_ = _lockDictionary.TryRemove(correlationId, out _);
}
await disposeAsyncTask;
}
}
public class ReferenceCountedDisposable : IAsyncDisposable
{
private readonly Func<Task> _asyncDispose;
private int _refCount;
public ReferenceCountedDisposable(Func<Task> asyncDispose)
{
_asyncDispose = asyncDispose;
_refCount = 1;
}
public void RegisterReference()
{
Interlocked.Increment(ref _refCount);
}
public async ValueTask DisposeAsync()
{
var references = Interlocked.Decrement(ref _refCount);
if (references == 0)
{
await _asyncDispose();
}
else if (references < 0)
{
throw new InvalidOperationException("Can't dispose multiple times");
}
else
{
GC.SuppressFinalize(this);
}
}
}
I am trying to assign a class property with the result of a GET request, such that all other methods in the class can use the value of that property without having to call the GET request more than once. Bottom line is I want to call the GET Request during class instantiaton, assign it to a variable, and then never call it again during the objects lifetime.
public class Example {
private readonly HttpClient _http;
private readonly List<Thing> _things;
public Example(HttpClient http)
{
_http = http;
_things = _http.GetFromJsonAsync<List<Thing>>("https://api-to-call/endpoint").Result;
}
public void UseThings()
{
// Do something with _things;
}
}
However, when the method is called, it is sending a new GET request to retrieve an updated value of the property I assigned in the constructor. How can I code this so it only calls the GET request once during object instantiation?
You should not block in the constructor and .Result can have nasty side-effects. While there have been discussions of a language feature to support this, until then, you should move this out to a cached operation. You can ensure that the operation only runs once by wrapping the http call with a SemaphoreSlim.
public class Example
{
private readonly HttpClient _http;
private static readonly SemaphoreSlim _lock = new SemaphoreSlim(1, 1);
private List<Thing> _things;
public Example(HttpClient http)
{
_http = http;
}
public async Task UseThings()
{
// Do something with _things;
var localThings = _things ?? await GetThingsAsync();
}
private async Task<List<Thing>> GetThingsAsync()
{
if (_things != null)
{
return await Task.FromResult(_things);
}
await _lock.WaitAsync();
try
{
// double check in case another thread has completed
if (_things != null)
{
return _things;
}
_things = await _http.GetFromJsonAsync<List<Thing>>("https://api-to-call/endpoint");
return _things;
}
finally
{
_lock.Release();
}
}
}
In my .Net Core 3.0 app I want to use the Microsoft Graph Nuget library. I have created a connection class that authenticates my application using [MSAL][1] and then creates the connection and returns this. My idea was to inject this connection object in the constructor using Dependency Injection. However, since the method that creates the connection is async, I seem to have a problem how to use it in the constructor.
My Connect Class
public class AuthorizeGraphApi: IAuthorizeGraphApi
{
private readonly IConfiguration _config;
public AuthorizeGraphApi(IConfiguration config)
{
_config = config;
}
public async Task<GraphServiceClient> ConnectToAAD()
{
string accessToken = await GetAccessTokenFromAuthorityAsync();
var graphServiceClient = new GraphServiceClient(new DelegateAuthenticationProvider((requestMessage) => {
requestMessage
.Headers
.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
return Task.FromResult(0);
}));
return graphServiceClient;
}
private async Task<string> GetAccessTokenFromAuthorityAsync()
{
// clientid, authUri, etc removed for this example.
IConfidentialClientApplication _conn;
_conn = ConfidentialClientApplicationBuilder.Create(clientId)
.WithClientSecret(clientSecret)
.WithAuthority(new Uri(authUri))
.Build();
string[] scopes = new string[] { $"api://{clientId}/.default" };
AuthenticationResult result = null;
// AcquireTokenForClient only has async method.
result = await _conn.AcquireTokenForClient(scopes)
.ExecuteAsync();
return result.AccessToken;
}
}
My Graph Service to send requests
public class AzureIntuneService
{
private readonly IAuthorizeGraphApi _graphClient;
public AzureIntuneService(IAuthorizeGraphApi client)
{
//Gives: cannot implicitely convert to Threading.Tasks.Task.... error
_graphClient = client.ConnectToAAD();
}
public async Task<IList<string>> GetAADInformationAsync()
{
// then here, use the graphClient object for the request...
var payload = await _graphClient.Groups.Request().GetAsync();
return payload
}
}
I register the above classess in my startup as follows:
services.AddScoped<IAuthorizeGraphApi, AuthorizeGraphApi>();
The idea was that this way, I don't need to call the _graphClient in each method. How can I inject the connection object in a correct way? Or what are the best practices regarding this (injecting connection objects)?
One way would be to store a reference to the Task and make sure any public methods that use the connection are async:
public class AzureIntuneService
{
private readonly Task<GraphServiceClient> _graphClientTask;
public AzureIntuneService(IAuthorizeGraphApi client)
{
_graphClientTask = client.ConnectToAAD();
}
public async Task<IList<string>> GetAADInformationAsync()
{
var client = await _graphClientTask; // Get the client when connected
var payload = await client.Groups.Request().GetAsync();
return payload;
}
}
Constructors aren't async and should never be used to initialize anything async. The only way to workaround it is to do sync-over-async by doing a .Result which is always a problem.
In your case, the GraphServiceClient that takes in DelegateAuthenticationProvider, accepts an AuthenticateRequestAsyncDelegate. This allows you to have an async delegate to construct the client.
So now you can do
new DelegateAuthenticationProvider(async requestMessage =>
{
string accessToken = await GetAccessTokenFromAuthorityAsync();
//rest of code here
}
)
and this allows you to change your ConnectToAAD signature to just return a GraphServiceClient and not a Task<GraphServiceClient>.
When you need async data you have to look away from the regular constructor and create a factory method (private static function). Something like below:
public sealed class MyClass
{
private MyData asyncData;
private MyClass() { ... }
private async Task<MyClass> InitializeAsync()
{
asyncData = await GetDataAsync();
return this;
}
public static Task<MyClass> CreateAsync()
{
var ret = new MyClass();
return ret.InitializeAsync();
}
}
public static async Task UseMyClassAsync()
{
MyClass instance = await MyClass.CreateAsync();
...
}
More here: https://blog.stephencleary.com/2013/01/async-oop-2-constructors.html
After reading this blog post and thisofficial note on www.asp.net:
HttpClient is intended to be instantiated once and re-used throughout
the life of an application. Especially in server applications,
creating a new HttpClient instance for every request will exhaust the
number of sockets available under heavy loads. This will result in
SocketException errors.
I discovered that our code was disposing the HttpClient on each call. I'm updating our code so that we reuse the HttClient, but I'm concerned our implement but not thread-safe.
Here is the current draft of new code:
For Unit Testing, we implemented an wrapper for HttpClient, the consumers call the wrapper:
public class HttpClientWrapper : IHttpClient
{
private readonly HttpClient _client;
public Uri BaseAddress
{
get
{
return _client.BaseAddress;
}
set
{
_client.BaseAddress = value;
}
}
public HttpRequestHeaders DefaultRequestHeaders
{
get
{
return _client.DefaultRequestHeaders;
}
}
public HttpClientWrapper()
{
_client = new HttpClient();
}
public Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, String userOrProcessName)
{
IUnityContainer container = UnityCommon.GetContainer();
ILogService logService = container.Resolve<ILogService>();
logService.Log(ApplicationLogTypes.Debug, JsonConvert.SerializeObject(request), userOrProcessName);
return _client.SendAsync(request);
}
#region IDisposable Support
private bool disposedValue = false; // To detect redundant calls
protected virtual void Dispose(bool disposing)
{
if (!disposedValue)
{
if (disposing && _client != null)
{
_client.Dispose();
}
disposedValue = true;
}
}
public void Dispose()
{
Dispose(true);
}
#endregion
}
Here is a service that calls:
public class EnterpriseApiService : IEnterpriseApiService
{
private static IHttpClient _client;
static EnterpriseApiService()
{
IUnityContainer container = UnityCommon.GetContainer();
IApplicationSettingService appSettingService = container.Resolve<IApplicationSettingService>();
_client = container.Resolve<IHttpClient>();
}
public EnterpriseApiService() { }
public Task<HttpResponseMessage> CallApiAsync(Uri uri, HttpMethod method, HttpContent content, HttpRequestHeaders requestHeaders, bool addJsonMimeAccept = true)
{
IUnityContainer container = UnityCommon.GetContainer();
HttpRequestMessage request;
_client.BaseAddress = new Uri(uri.GetLeftPart(UriPartial.Authority));
if (addJsonMimeAccept)
_client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
request = new HttpRequestMessage(method, uri.AbsoluteUri);
// Removed logic that built request with content, requestHeaders and method
return _client.SendAsync(request, UserOrProcessName);
}
}
My questions:
Is this an appropriate approach to reuse the HttpClient object?
Is the static _httpClient field (populated with the static constructor) shared for all instances of EnterpriseApiService? I wanted to confirm since is being called by instance methods.
When CallApiAsync() is called, when that makes changes to the static HttpClient, such as the "_client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"))" could those values be overwriten by another process before the last line "_client.SendAsync" is called? I'm concerned that halfway through processing CallApiAsync() the static instance is updated.
Since it is calling SendAsync(), are we guaranteed the response is mapped back to the correct caller? I want to confirm the response doesn't go to another caller.
Update:
Since I've removed the USING statements, and the Garage Collection doesn't call Dispose, I'm going to go with the safer approach of creating a new instance within the method. To reuse an instance of HttpClient even within the thread lifetime, it would require a significant reworking of the logic because the method sets HttpClient properties per call.
Do you really want one instance?
I don't think you want one instance application-wide. You want one instance per thread. Otherwise you won't get very good performance! Also, this will resolve your questions #3 and #4, since no two threads will be accessing the same HttpClient at the same time.
You don't need a singleton
Just use Container.Resolve with the PerThreadLifetimeManager.
For those lucky enough to be using .NET Core this is fairly straightforward.
As John Wu so eloquently stated, you don't want a singleton per se, but rather a singleton per request. As such, the AddScoped<TService>() method is what you're after.
In your ConfigureServices(IServiceCollection services) method:
services.AddScoped<HttpClient>();
To consume:
public class HomeController
{
readonly HttpClient client;
public HomeController (HttpClient client)
{
this.client = client;
}
//rest of controller code
}
Since it is calling SendAsync(), are we guaranteed the response is mapped back to the correct caller? I want to confirm the response doesn't go to another caller.
This will be handled via callback pointers. It has nothing to do with using HttpClient as singleton. More details here - https://stackoverflow.com/a/42307650/895724
This is what i use
public abstract class BaseClient : IDisposable
{
private static object locker = new object();
private static volatile HttpClient httpClient;
protected static HttpClient Client
{
get
{
if (httpClient == null)
{
lock (locker)
{
if (httpClient == null)
{
httpClient = new HttpClient();
}
}
}
return httpClient;
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
if (httpClient != null)
{
httpClient.Dispose();
}
httpClient = null;
}
}
}
Its used in the extension method like this:
public static Task<HttpResponseMessage> PostAsJsonAsync<T>(
this HttpClient httpClient, string url, T data, string token, IDictionary<string, string> dsCustomHeaders = null)
{
ThrowExceptionIf.Argument.IsNull(httpClient, nameof(httpClient));
var dataAsString = JsonConvert.SerializeObject(data);
var httpReqPostMsg = new HttpRequestMessage(HttpMethod.Post, url)
{
Content = new StringContent(dataAsString, Encoding.UTF8, "application/json")
};
httpReqPostMsg.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
httpReqPostMsg.Headers.Add(Constants.TelemetryCorrelationKey, Utilities.GetRequestCorrelationId());
if (dsCustomHeaders != null) {
foreach (var keyValue in dsCustomHeaders)
{
httpReqPostMsg.Headers.Add(keyValue.Key, keyValue.Value);
}
}
return httpClient.SendAsync(httpReqPostMsg);
}