We are currently using the HttpClient to invoke the Web APIs from the MVC application.
The HttpClient is part of a static helper class as shown below
public static class ApiClient
{
private static HttpClient MyHttpClient()
{
HttpClient client = new HttpClient();
...
return client;
}
public static T HttpGet<T>(string requestUri)
{
using (var client = MyHttpClient())
{
...
}
}
}
and it is invoked from the MVC controller as given below
ApiClient.HttpGet<MyModel>("<<API URL>>");
So whenever the ApiClient is invoked, a new underlying connection will be opened which isn't the right way.
I read about HttpClientFactory and read this post and I resulted in modifying the creation logic as
private static HttpClient MyHttpClient()
{
var serviceProvider = new ServiceCollection().AddHttpClient().BuildServiceProvider();
var httpClientFactory = serviceProvider.GetService<IHttpClientFactory>();
var client = httpClientFactory.CreateClient();
//HttpClient client = new HttpClient();
...
return client;
}
Will this prevent from opening multiple connections even if invoked multiple times?
The IHttpClientFactory functionality is predicated on dependency injection, and statics are fundamentally incompatible with dependency injection. As the docs clearly show, the correct way to do this is:
public class ApiClient
{
private readonly HttpClient _client;
public ApiClient(HttpClient client)
{
_client = client;
}
...
}
And then you register this service in ConfigureServices:
services.AddHttpClient<ApiClient>(c => { ... });
The client class should not be static and there's no reason for it to be static.
Related
I know I'll get crucified for asking this question that has been asked a million times before and I promise you that I've looked at most of those questions/answers but I'm still a bit stuck.
This is a .NET Standard 2.0 class library supporting an ASP.NET Core 6 API.
In my Program.cs I create a named HttpClient like this:
builder.Services.AddHttpClient("XYZ_Api_Client", config =>
{
var url = "https://example.com/api";
config.BaseAddress = new Uri(url);
});
I have a custom client that will use this HttpClient and I create a singleton MyCustomClient in Program.cs so that my repositories can use it. The code is below. This is where I'm stuck as I'm not sure how to pass my named HttpClient into MyCustomClient.
builder.Services.AddSingleton(new MyCustomClient(???)); // I think I need to pass the HttpClient to my CustomClient here but not sure how
And my CustomClient needs to use this HttpClient named XYZ_Api_Client to do its job:
public class MyCustomClient
{
private readonly HttpClient _client;
public MyCustomClient(HttpClient client)
{
_client = client;
}
public async Task<bool> DoSomething()
{
var result = await _client.GetAsync();
return result;
}
}
So I'm not sure how I can pass this named HttpClient into MyCustomClient in the Program.cs.
You can directly inject the IHttpClientFactory in you class, and then assign the named HttpClient to a property.
Register the factory and your custom client:
builder.Services.AddHttpClient("XYZ_Api_Client", config =>
{
var url = "https://example.com/api";
config.BaseAddress = new Uri(url);
});
// no need to pass anything, the previous line registered IHttpClientFactory in the container
builder.Services.AddSingleton<MyCustomClient>();
And then in you class:
public class MyCustomClient
{
private readonly HttpClient _client;
public MyCustomClient(IHttpClientFactory factory)
{
_client = factory.CreateClient("XYZ_Api_Client");
}
// ...
}
Or, you can pass the named instance when registering MyCustomClient
Register the factory and your custom client:
builder.Services.AddHttpClient("XYZ_Api_Client", config =>
{
var url = "https://example.com/api";
config.BaseAddress = new Uri(url);
});
// specify the factory for your class
builder.Services.AddSingleton<MyCustomClient>(sp =>
{
var factory = sp.GetService<IHttpClientFactory>();
var httpClient = factory.CreateClient("XYZ_Api_Client");
return new MyCustomClient(httpClient);
});
And then in you class:
public class MyCustomClient
{
private readonly HttpClient _client;
public MyCustomClient(HttpClient client)
{
_client = client;
}
// ...
}
You can also do this:
// register the named client with the name of the class
builder.Services.AddHttpClient("MyCustomClient", config =>
{
config.BaseAddress = new Uri("https://example.com/api");
});
// no need to specify the name of the client
builder.Services.AddHttpClient<MyCustomClient>();
What AddHttpClient<TClient>(IServiceCollection) does is
Adds the IHttpClientFactory and related services to the IServiceCollection and configures a binding between the TClient type and a named HttpClient. The client name will be set to the full name of TClient.
You can find the full documentation here.
In my .Net core application I have NamedHttpClient and I have a TypedHttpClient. I need to use the NamedClient as the default httpclient inside TypedHttpClient.
My configure services:
public static IServiceCollection ConfigureServiceOptions(this IServiceCollection services, IConfiguration config)
{
IConfigurationSection serverSection = config.GetSection(nameof(ServerOptions));
services.Configure<ServerOptions>(serverSection);
//Named
services.AddHttpClient("defaultHttpClient", client =>
{
client.BaseAddress = new System.Uri(dataServerSection.Get<ServerOptions>().ServerUrl);
client.DefaultRequestHeaders.Add("Accept", "application/xml, */*");
client.DefaultRequestHeaders.Add("User-Agent", "Server v1.0.0");
});
}
//startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.ConfigureServiceOptions(Configuration);
//Adding Typed
services.AddHttpClient<DataService>(); //I need to use defaultHttpClient as HttpClient Inside DataService
}
So far I found 3 ways to do this, not sure if there is any pitfall in any of the method
Way 1:
services.AddHttpClient<DataService>();
public class DataService
{
private HttpClient Client { get;}
public DataService(/*need to pass this otherwise cant resolve*/ HttpClient client, IHttpClientFactory factory)
{
Client = factory.CreateClient("defaultHttpClient");
}
}
In Way 1 i need to have the additional HttpClient client parameter in the constructor. Otherwise the DataService is not resolved.
Way2:
services.AddTransient<DataService>( cfg =>
{
var clientFactory = cfg.GetRequiredService<System.Net.Http.IHttpClientFactory>();
var httpClient = clientFactory.CreateClient("defaultHttpClient");
return new DataService(httpClient);
});
public class DataService
{
private HttpClient Client { get;}
public DataService(HttpClient client)
{
Client = client;
}
}
Way 3:
services.AddTransient<DataService>();
public class DataService
{
private HttpClient Client { get;}
public DataService(IHttpClientFactory factory)
{
Client = factory.CreateClient("defaultHttpClient");
}
}
I think Way2 and Way3 might be same. Not sure if there is any difference.
Can someone tell what is the recommended way ? or if there is any other way?
Since you are not actually setting up the typed client for DataService the "Way 1" does not make much sense, it is basically "Way 3" but with extra steps.
"Way 2" make service code cleaner but will become cumbersome to maintain if new dependencies will be needed for it later.
"Way 3" is how using named HttpClient is shown in the docs so I would go with it.
With HttpClientFactory we can configure dependency injection to create and manage the lifetime of HttpClients, but this creates a lot of code understanding and transparency problems:
public class GitHubService
{
private readonly HttpClient _client;
private readonly string _repositoryName;
public GitHubService(HttpClient client, string repositoryName)
{
_client = client;
_repositoryName = repositoryName;
}
public async Task<IEnumerable<GitHubIssue>> GetAspNetDocsIssues()
{
var response = await _client.GetAsync(
$"/repos/aspnet/{_repositoryName}/issues?state=open&sort=created&direction=desc");
response.EnsureSuccessStatusCode();
using var responseStream = await response.Content.ReadAsStreamAsync();
return await JsonSerializer.DeserializeAsync
<IEnumerable<GitHubIssue>>(responseStream);
}
}
Then in Startup.cs we configure DI:
services.AddHttpClient<GitHubService>(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");
});
However, this raises a number of problems:
If I share the GitHubService code with someone, they will not understand that the class is using a non-standard HttpClient. To understand the logic of building requests, headers, authorization, you need to additionally study the Startup.cs class.
You might think that we are setting up an implementation of only the HttpClient for the GitHubService class. However, in fact, we are setting up dependency injection for the entire GitHubService class, without the possibility of managing its lifecycle and creation factory.
If the GitHubService class requires additional parameters in the constructor, we cannot configure them, because we do not have access to control the creation of the object.
Why not just inject a typed HttpClient<GitHubService> (like it does with ILogger<T>) that will not affect the main class and make it clear that you are not using a regular HttpClient? How can this problem be solved?
public class GitHubService
{
private readonly HttpClient<GitHubService> _client;
private readonly string _repositoryName;
public GitHubService(HttpClient<GitHubService> client, string repositoryName)
{
_client = client;
_repositoryName = repositoryName;
}
// Code removed for brevity.
}
I would like to preface this post with the fact that I am VERY new to C#, .NET Core, and Blazor after being a long-time Java person.
I am following along with Microsoft's documentation on Make HTTP requests using IHttpClientFactory in ASP.NET Core, but I cannot seem to understand how to use this with my application.
I have a IP address for an API endpoint, So I created a Named Client:
services.AddHttpClient("TheAPIEndpoint", client =>
{
client.BaseAddress = new Uri("192.168.1.234");
client.DefaultRequestHeaders.Add("Accept", "application/json");
client.DefaultRequestHeaders.Add("User-Agent", "Skynet v1.0");
});
Next, I went to the "Basic Usage" section of the documentation at the top of the page where it says:
An IHttpClientFactory can be requested using dependency injection (DI). The following code uses IHttpClientFactory to create an HttpClient instance
It says to create a constructor for my class and pass in an IHttpClientFactory:
public class BasicUsageModel : PageModel
{
private readonly IHttpClientFactory _clientFactory;
public BasicUsageModel(IHttpClientFactory clientFactory)
{
_clientFactory = clientFactory;
}
public async Task OnGet()
{
var client = _clientFactory.CreateClient();
}
}
Here is where my issue "begins". When you create a new Razor component for a Blazor application, the generated .RAZOR file looks like this:
<h3>HelloWorld</h3>
#code {
}
When I need to call the API, I need to create this method:
private async Task GetSearchResults()
{
// ...
}
All together, it looks like this:
<h3>HelloWorld</h3>
#if (fetchedResults != null)
{
#foreach (var result in fetchedResults)
{
<p>#(result.someData)</p>
}
}
#code {
private async Task GetSearchResults()
{
HttpClient client = new HttpClient();
client.BaseAddress = new Uri(#"http://192.168.1.234:8082/");
// ...
fetchedResults = JsonConvert.DeserializeObject<ResultSet>(response);
}
}
As you see, I don't have a "normal" class structure in which to create the constructor, to which to pass an IHttpClientFactory parameter, with which I can create a new HttpClient.
So, how do I go about doing this?
Blazor does not support constructors as you've already discovered, instead it provides the #inject directive to support dependency injection. The syntax is:
#inject <dependency-type-name> <variable-name>
In your case:
#inject IHttpClientFactory _clientFactory
<h3>HelloWorld</h3>
#if (fetchedResults != null)
{
#foreach (var result in fetchedResults)
{
<p>#(result.someData)</p>
}
}
#code {
private async Task GetSearchResults()
{
var client = _clientFactory.CreateClient();
client.BaseAddress = new Uri(#"http://192.168.1.234:8082/");
// ...
fetchedResults = JsonConvert.DeserializeObject<ResultSet>(response);
}
}
Don't forget to register your dependencies in the Startup.ConfigureServices method!
I have an old version of ASP.NET MVC app that doesn't have a Startup.cs. I wanted to implement a clean way to have an HttpClient that I would use for my API calls to third parties.
Here's what I've done so far based on some ideas/recommendations I've received for this question. The problem is that when I make the API call, it goes nowhere. I put it in a try catch but I'm not even getting an exception. The API provider tells me that they're not seeing the search parameter.
First, I created this HttpClientAccessor for lazy loading.
public static class HttpClientAccessor
{
public static Func<HttpClient> ValueFactory = () =>
{
var client = new HttpClient();
client.BaseAddress = new Uri("https://apiUrl.com");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.TryAddWithoutValidation("APIAccessToken", "token1");
client.DefaultRequestHeaders.TryAddWithoutValidation("UserToken", "token2");
return client;
};
private static Lazy<HttpClient> client = new Lazy<HttpClient>(ValueFactory);
public static HttpClient HttpClient
{
get { return client.Value; }
}
}
I then created an API client of my own so that I can have the API call functions in one place which looks like this:
public class MyApiClient
{
public async Task GetSomeData()
{
var client = HttpClientAccessor.HttpClient;
try
{
var result = await client.GetStringAsync("somedata/search?text=test");
var output = JObject.Parse(result);
}
catch(Exception e)
{
var error = e.Message;
}
}
}
Then in my ASP.NET Controller action, I do this:
public class MyController : Controller
{
private static readonly MyApiClient _apiClient = new MyApiClient ();
public ActionResult ApiTest()
{
var data = _apiClient.GetSomeData().Wait();
}
}
Any idea where my mistake is?
UPDATE:
This simple approach works fine:
public class MyController : Controller
{
private static readonly HttpClient _client = new HttpClient();
public ActionResult ApiTest()
{
_client.BaseAddress = new Uri("https://apiUrl.com");
_client.DefaultRequestHeaders.Accept.Clear();
_client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
_client.DefaultRequestHeaders.TryAddWithoutValidation("APIAccessToken", "token1");
_client.DefaultRequestHeaders.TryAddWithoutValidation("UserToken", "token2");
var response = _client.GetStringAsync("somedata/search?text=test").Result;
}
}
As mentioned, dependency injection is not being utilized so technically there is no need for a composition root where these things would have been initialized.
If there is no need to actually initialize the client on start up you could consider using a Lazy singleton approach.
An example
public static class HttpClientAccessor {
public static Func<HttpClient> ValueFactory = () => {
var client = new HttpClient();
client.BaseAddress = new Uri("https://apiUrl.com");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.TryAddWithoutValidation("APIAccessToken", "token1");
client.DefaultRequestHeaders.TryAddWithoutValidation("UserToken", "token2");
return client;
};
private static Lazy<HttpClient> client = new Lazy<HttpClient>(ValueFactory);
public static HttpClient HttpClient {
get { return client.Value; }
}
}
The factory delegate of the Lazy<HttpClient> can be made more complex if additional settings are needed on the client.
And where ever the client is needed you call the service
var client = HttpClientAccessor.HttpClient;
var response = await client.GetStringAsync("{url}");
the client will be initialized on first use and you will get the same instance on subsequent calls for the instance.
As used in your controller, you are mixing async calls with blocking calls line .Wait() or .Result. This can lead to deadlocks and should be avoided.
public class MyController : Controller {
private static readonly MyApiClient _apiClient = new MyApiClient ();
public async Task<ActionResult> ApiTest() {
var data = await _apiClient.GetSomeData();
//...
}
}
Code should be async all the way through.
Reference Async/Await - Best Practices in Asynchronous Programming
The Application_Start() method is the right place. But I would have to ask: why you have to create the HttpClient instance when the "application starts"? In general, HttpClient is some "resource" and you can just create it when you want to use it. And also it's no need to set it as "Singleton". Just wrap it in the using block. (Maybe you want to make the API wrapper as Singleton?)
public class APICaller
{
//make the APICaller singleton in some way here
//...
// the api calling method:
public string CallAPI(string someParameter)
{
var response = "";
using (var client = new HttpClient())
{
//calling the API
}
return response;
}
}
The main issue is incorrect asynchronous code.
You are using Task.Wait() which alongside asynchronous MyApiClient.GetSomeData() causes a deadlock on ASP.NET request context. That is a very common issue, see An async/await example that causes a deadlock on StackOverflow. Code with Task.Result property call is working because HttpClient.GetStringAsync() probably takes preventative measures against deadlocks. See Task.ConfigureAwait() page on MSDN and Best practice to call ConfigureAwait for all server-side code discussion on StackOverflow.
There are multiple options to write a singleton using C#. See Implementing the Singleton Pattern in C# article by Jon Skeet for a detailed overview.
As you mentioned, you can just use a static class member on the controller. HttpClient only needs to be setup once; so do this in the static constructor of the controller. Also, make sure that you use async/await for async methods, especially with long running http requests. IOC and an abstraction layer would make sense depending on your needs.
using System;
using System.Net.Http;
using System.Threading.Tasks;
namespace TestApi
{
public class MyController : Controller
{
private const string ApiUrlString = "https://apiUrl.com";
private static readonly Uri ApiUri = new Uri(ApiUrlString);
private static readonly HttpClient RestClient;
static MyController()
{
this.RestClient = new HttpClient{
BaseAddress = ApiUri
}
this.RestClient.DefaultRequestHeaders.Accept.Clear();
this.RestClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
RestClient.DefaultRequestHeaders.TryAddWithoutValidation("APIAccessToken", "token1");
RestClient.DefaultRequestHeaders.TryAddWithoutValidation("UserToken", "token2");
}
public async Task<IActionResult> ApiTest()
{
return this.Ok(await this.RestClient.GetStringAsync("somedata/search?text=test"));
}
}
}