Best way to use HTTPClient in ASP.Net Core as a DI Singleton
Solution 1
If using .net core 2.1 or higher, the best approach would be to use the new HttpClientFactory
. I guess Microsoft realized all the issues people were having so they did the hard work for us. See below for how to set it up.
NOTE: Add a reference to Microsoft.Extensions.Http
.
1 - Add a class that uses HttpClient
public interface ISomeApiClient
{
Task<HttpResponseMessage> GetSomethingAsync(string query);
}
public class SomeApiClient : ISomeApiClient
{
private readonly HttpClient _client;
public SomeApiClient (HttpClient client)
{
_client = client;
}
public async Task<SomeModel> GetSomethingAsync(string query)
{
var response = await _client.GetAsync($"?querystring={query}");
if (response.IsSuccessStatusCode)
{
var model = await response.Content.ReadAsJsonAsync<SomeModel>();
return model;
}
// Handle Error
}
}
2 - Register your clients in ConfigureServices(IServiceCollection services)
in Startup.cs
var someApiSettings = Configuration.GetSection("SomeApiSettings").Get<SomeApiSettings>(); //Settings stored in app.config (base url, api key to add to header for all requests)
services.AddHttpClient<ISomeApiClient, SomeApiClient>("SomeApi",
client =>
{
client.BaseAddress = new Uri(someApiSettings.BaseAddress);
client.DefaultRequestHeaders.Add("api-key", someApiSettings.ApiKey);
});
3 - Use the client in your code
public class MyController
{
private readonly ISomeApiClient _client;
public MyController(ISomeApiClient client)
{
_client = client;
}
[HttpGet]
public async Task<IActionResult> GetAsync(string query)
{
var response = await _client.GetSomethingAsync(query);
// Do something with response
return Ok();
}
}
You can add as many clients and register as many as needed in your startup with services.AddHttpClient
Thanks to Steve Gordon and his post here for helping me use this in my code!
Solution 2
In answer to a question from @MuqeetKhan regarding using authentication with the httpclient request.
Firstly, my motivation to use DI and a factory was to allow me to extend my application easily to different and multiple API’s and have easy access to that throughout my code. It’s a template I hope to be able to reuse multiple times.
In the case of my ‘GetUserPicture’ controller decribed in the original question above, I indeed for simplicity reasons removed the authentication. Honestly however, I am still in doubt if I need it there to simply retrieve an image from the imageserver. Anyhow, in other controllers I definitely do need it, so…
I’ve implemented Identityserver4 as my authentication server. This provides me with the authentication on top of ASP Identity. For authorization (using roles in this case), I implemented IClaimsTransformer in my MVC ‘and’ API projects (you can read more about this here at How to put ASP.net Identity Roles into the Identityserver4 Identity token).
Now, the moment I enter my controller I have an authenticated and authorized user for which I can retrieve an access token. I use this token to call my api which is of course calling the same instance of identityserver to verify if the user is authenticated.
The last step is to allow my API to verify if the user is authorized to call the requested api controller. In the request pipeline of the API using IClaimsTransformer as explained before, I retrieve the authorization of the calling user and add it to the incoming claims. Note that in case of an MVC calling and API, I thus retrieve the authorization 2 times; once in the MVC request pipeline and once in the API request pipeline.
Using this set-up I am able to use my HttpClientsFactory with Authorization and Authentication.
On big security part I am missing is HTTPS of course. I hope I can somehow add it to my factory. I'll update it once I've implemented it.
As always, any suggestions are welcome.
Below an example where I upload an image to the Imageserver using authentication (user must be logged in and have role admin).
My MVC controller calling the ‘UploadUserPicture’:
[Authorize(Roles = "Admin")]
[HttpPost]
public async Task<ActionResult> UploadUserPicture()
{
// collect name image server
var imageServer = _optionsAccessor.Value.ImageServer;
// collect image in Request Form from Slim Image Cropper plugin
var json = _httpContextAccessor.HttpContext.Request.Form["slim[]"];
// Collect access token to be able to call API
var accessToken = await HttpContext.Authentication.GetTokenAsync("access_token");
// prepare api call to update image on imageserver and update database
var client = _httpClientsFactory.Client("imageServer");
client.DefaultRequestHeaders.Accept.Clear();
client.SetBearerToken(accessToken);
var content = new FormUrlEncodedContent(new[]
{
new KeyValuePair<string, string>("image", json[0])
});
HttpResponseMessage response = await client.PostAsync("api/UserPicture/UploadUserPicture", content);
if (response.StatusCode != HttpStatusCode.OK)
{
return StatusCode((int)HttpStatusCode.InternalServerError);
}
return StatusCode((int)HttpStatusCode.OK);
}
API handling the user upload
[Authorize(Roles = "Admin")]
[HttpPost]
public ActionResult UploadUserPicture(String image)
{
dynamic jsonDe = JsonConvert.DeserializeObject(image);
if (jsonDe == null)
{
return new StatusCodeResult((int)HttpStatusCode.NotModified);
}
// create filname for user picture
string userId = jsonDe.meta.userid;
string userHash = Hashing.GetHashString(userId);
string fileName = "User" + userHash + ".jpg";
// create a new version number
string pictureVersion = DateTime.Now.ToString("yyyyMMddHHmmss");
// get the image bytes and create a memory stream
var imagebase64 = jsonDe.output.image;
var cleanBase64 = Regex.Replace(imagebase64.ToString(), @"^data:image/\w+;base64,", "");
var bytes = Convert.FromBase64String(cleanBase64);
var memoryStream = new MemoryStream(bytes);
// save the image to the folder
var fileSavePath = Path.Combine(_env.WebRootPath + ("/imageuploads"), fileName);
FileStream file = new FileStream(fileSavePath, FileMode.Create, FileAccess.Write);
try
{
memoryStream.WriteTo(file);
}
catch (Exception ex)
{
_logger.LogDebug(LoggingEvents.UPDATE_ITEM, ex, "Could not write file >{fileSavePath}< to server", fileSavePath);
return new StatusCodeResult((int)HttpStatusCode.NotModified);
}
memoryStream.Dispose();
file.Dispose();
memoryStream = null;
file = null;
// update database with latest filename and version
bool isUpdatedInDatabase = UpdateDatabaseUserPicture(userId, fileName, pictureVersion).Result;
if (!isUpdatedInDatabase)
{
return new StatusCodeResult((int)HttpStatusCode.NotModified);
}
return new StatusCodeResult((int)HttpStatusCode.OK);
}
Solution 3
For situations when you can't use DI:
using System.Net.Http;
public class SomeClass
{
private static readonly HttpClient Client;
static SomeClass()
{
var handler = new SocketsHttpHandler
{
// Sets how long a connection can be in the pool to be considered reusable (by default - infinite)
PooledConnectionLifetime = TimeSpan.FromMinutes(1),
};
Client = new HttpClient(handler, disposeHandler: false);
}
...
}
Related videos on Youtube
Laobu
Updated on July 23, 2021Comments
-
Laobu almost 3 years
I am trying to figure out how to best use the HttpClient class in ASP.Net Core.
According to the documentation and several articles, the class is best instantiated once for the lifetime of the application and shared for multiple requests. Unfortunately, I could not find an example of how to correctly do this in Core so I’ve come up with the following solution.
My particular needs require the use of 2 different endpoints (I have an APIServer for business logic and an API driven ImageServer), so my thinking is to have 2 HttpClient singletons that I can use in the application.
I’ve configured my servicepoints in the appsettings.json as follows:
"ServicePoints": { "APIServer": "http://localhost:5001", "ImageServer": "http://localhost:5002", }
Next, I created a HttpClientsFactory that will instantiate my 2 httpclients and hold them in a static Dictionary.
public class HttpClientsFactory : IHttpClientsFactory { public static Dictionary<string, HttpClient> HttpClients { get; set; } private readonly ILogger _logger; private readonly IOptions<ServerOptions> _serverOptionsAccessor; public HttpClientsFactory(ILoggerFactory loggerFactory, IOptions<ServerOptions> serverOptionsAccessor) { _logger = loggerFactory.CreateLogger<HttpClientsFactory>(); _serverOptionsAccessor = serverOptionsAccessor; HttpClients = new Dictionary<string, HttpClient>(); Initialize(); } private void Initialize() { HttpClient client = new HttpClient(); // ADD imageServer var imageServer = _serverOptionsAccessor.Value.ImageServer; client.BaseAddress = new Uri(imageServer); HttpClients.Add("imageServer", client); // ADD apiServer var apiServer = _serverOptionsAccessor.Value.APIServer; client.BaseAddress = new Uri(apiServer); HttpClients.Add("apiServer", client); } public Dictionary<string, HttpClient> Clients() { return HttpClients; } public HttpClient Client(string key) { return Clients()[key]; } }
Then, I created the interface that I can use when defining my DI later on. Notice that the HttpClientsFactory class inherits from this interface.
public interface IHttpClientsFactory { Dictionary<string, HttpClient> Clients(); HttpClient Client(string key); }
Now I am ready to inject this into my Dependency container as follows in the Startup class under the ConfigureServices method.
// Add httpClient service services.AddSingleton<IHttpClientsFactory, HttpClientsFactory>();
All is now set-up to start using this in my controller.
Firstly, I take in the dependency. To do this I created a private class property to hold it, then add it to the constructor signature and finish by assigning the incoming object to the local class property.private IHttpClientsFactory _httpClientsFactory; public AppUsersAdminController(IHttpClientsFactory httpClientsFactory) { _httpClientsFactory = httpClientsFactory; }
Finally, we can now use the Factory to request a htppclient and execute a call. Below, an example where I request an image from the imageserver using the httpclientsfactory:
[HttpGet] public async Task<ActionResult> GetUserPicture(string imgName) { // get imageserver uri var imageServer = _optionsAccessor.Value.ImageServer; // create path to requested image var path = imageServer + "/imageuploads/" + imgName; var client = _httpClientsFactory.Client("imageServer"); byte[] image = await client.GetByteArrayAsync(path); return base.File(image, "image/jpeg"); }
Done!
I’ve tested this and it work great on my development environment. However, I am not sure if this is the best way to implement this. I remain with the following questions:
- Is this solution thread safe? (according to the MS doc: ‘Any public static (Shared in Visual Basic) members of this type are thread safe.’)
- Will this set-up be able to handle a heavy load without opening many separate connection?
- What to do in ASP.Net core to handle the DNS problem described in ‘Singleton HttpClient? Beware of this serious behaviour and how to fix.’ located at http://byterot.blogspot.be/2016/07/singleton-httpclient-dns.html
- Any other improvements or suggestions?
-
Muqeet Khan over 7 yearsInteresting approach, I have HTTPClient static methods as a service but I dint think of a factory pattern. I was wondering have to tried this under an API that required authentication or your API case is an Open API? How would you handle that for different requests that required different tokens.
-
Laobu over 7 years@MuqeetKhan my answer to your question landed up being a little longer then anticipated. So, please find it below with an example.
-
galdin almost 7 yearsYou don't need two
HttpClient
instances. Just register one singleton and use it. The DNS issue will still exist. As a sidenote,Factory
classes create an object instance. Read more about the Factory pattern here. -
aruno over 6 years@gldraphael but one reason to create an instance is to reuse things like BaseAddress, credentials and connections. That's not going to help if we're calling different APIs.
-
galdin over 6 years@Simon_Weaver true that. I prefer setting those parameters for the individual requests rather than to the client.
-
aruno over 6 years@gldraphael I'm still struggling because I guess I just expect things like this to be solved problems by the framework by now but they dont seem to be. Quite odd really considering how important it is to get right! Fortunately this is an internal app so I can just wing it for now.and probably do exactly what you said :)
-
Bernoulli IT almost 4 yearsDoes this approach create a new
HttpClient
forSomeAPIClient
each and every timeSomeAPIClient
is used? I don't see aSingleton
orTransient
keyword state somewhere as how to configure theIHttpClientFactory
. There is a pattern(s) around to explicitly define theHttpClient
to be a singleton in the DI container (although this can give issues with changing endpoints forHttpClients
in a multi tenancy scenario where different consumers of the API need different endpoints for theHttpClient
. -
Bernoulli IT almost 4 yearshow do you know? You provide it as a service with
AddHttpClient
in the DI container and every timeMyController
gets constructed (on every request to it) it will have a fresh(I)SomeApiClient
from the DI container? Or doesAddHttpClient
work differently under the hood fromAddService
which we use for other (normal) services? -
garethb almost 4 years
tldr; don't take my word for it, the source is linked somewhere in the article in the answer above
. Sorry, I'm far from an expert and you can ignore the my last comment (deleted for misinformation). From memory SomeApiClient would be transient and the underlying HttpClient is singleton with an expiry timer. If you got this far, read the first sentence in this comment again -
Bernoulli IT almost 4 yearsA lot of framework information regarding
HttpClient(Factory)
and relatedHttpMessageHandler
can be read here: docs.microsoft.com/en-us/dotnet/architecture/microservices/… -
Noah Stahl almost 4 yearsKey excerpt from the docs indicating this isn't a singleton, but does allow some reuse: "A Typed Client is, effectively, a transient object, meaning that a new instance is created each time one is needed and it will receive a new HttpClient instance each time it's constructed. However, the HttpMessageHandler objects in the pool are the objects that are reused by multiple HttpClient instances."