Consider this code
public class ScopedService {
public readonly HttpClient client;
public readonly MyHostedService hostedService;
public ScopedService(HttpClient client, MyHostedService hostedService) {
this.client = client;
this.hostedService = hostedService;
}
public void LongAsyncOperationWeDontWantToWaitFor()
{
var httpTask = client.GetAsync("...");
hostedService.SaveTaskForProcessSometimeInTheFuture(httpTask);
}
}
I have a scoped service which makes an http call. I want the scope to close before the http call completes. If I created and disposed httpClient manually, then I'd do it out of scope myself. But since in this case the client is Dependency Injected, I have no control over when it's going to be disposed. Can there be a situation, that the context won't be able to close until the injected httpClient is disposed?
You could inject IHttpClientFactory and use it to create the HttpClient when you need it.
Related
I'm working on a Blazor WebAssembly application in .NET 6.0.
I'm using MediatR requests and handlers.
public class DummyRequest : IRequest<string>
{
public Guid Test { get; } = new Guid("e9f41a5d-5da6-4aad-b118-83476b7f40f4");
}
public class DummyHandler : IRequestHandler<DummyRequest, string>
{
private readonly HttpClient _httpClient;
public DummyHandler(HttpClient httpClient)
{
_httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
}
public async Task<string> Handle(DummyRequest request, CancellationToken cancellationToken)
{
// This should be the value configured in Program.cs
string baseAddress = _httpClient.BaseAddress?.AbsoluteUri ?? string.Empty;
// But it's always blank, so we can't make any calls with the HttpClient
await Task.CompletedTask;
return "foobar";
}
}
I'm configuring a different HttpClient for each request handler in Program.cs, then I'm adding MediatR:
builder.Services.AddHttpClient<DummyHandler>((client) => { client.BaseAddress = new Uri("https://api.somewhere.com"); });
builder.Services.AddMediatR(Assembly.GetExecutingAssembly());
I have also tried reversing those calls, so that I add MediatR first, and register the HttpClient for the DummyHandler type afterwards.
At runtime, after that Handler has been instantiated, it should have an _httpClient with a BaseAddress property set to "https://api.somewhere.com".
However, it always gets an HttpClient with a null BaseUri, so the Handler can't use the HttpClient in any operations.
Can anybody see what's gone wrong please?
It seems that MediatR registers interface-implemetation pair so you need to follow the same pattern for the typed client registration. Try the following:
services.AddHttpClient<IRequestHandler<DummyRequest, string>, DummyHandler>((client) => { client.BaseAddress = new Uri("https://api.somewhere.com"); });
Gist with full test code.
Instead of a typed httpclient, you could use a named httpclient.
Thus register as
builder.Services.AddHttpClient("somename", client => { client.BaseAddress = new Uri("https://api.somewhere.com"); });
And in the constructor, inject the httpclientfactory instead:
public DummyHandler(HttpClientFactory httpClientFactory)
{
_httpClient = httpClientFactory.CreateClient("somename");
}
I suggest you to create the wrapper class around your Http client and register it instead.It hides implementation of your connection type and can be extended by other logic or other realization if you need.
Example:
class ApiConnection : IConnection
{
private readonly HttpClient _client;
public ApiConnection(...)
{
_client = new HttpClient();
}
// some other logic
}
Add this class to your Handler (IConnection connection) and use it in handler.
Register as: services.AddSingleton<IConnection, APIConnection>();
In case you are using the IRequestHandler<SomeCommand> alternative where there is no response, MediatR internally converts it to to IRequestHandler<SomeCommand, Unit>, which is what you will need to use to properly inject the HTTP client in your DI:
serviceCollection
.AddHttpClient<IRequestHandler<SomeCommand, Unit>, SomeCommandHandler>((httpClient) =>
{
...
});
Whenever a client makes a request to my ASP .NET Core 3.1 API, I want to use a HttpClient to do some actions in other services.
I registered HttpClient with Dependency Injection:
services.AddHttpClient<MyHttp>();
Unfortunately the BaseAddressof this HttpClient is not static. Instead the client sends a custom header including a reference to a database-entry.
So in order to determine the BaseAddress for my HttpClient i need to make an async call to my Database.
Currently I do something like the following:
public class SomeController
{
public SomeController(MyHttp http, AddressRepository db)
{
_http = http;
_db = db;
}
public async Task<dynamic> SomeAction([FromRoute] string id)
{
var address = await _db.Get(id);
_http.BaseAddress = new Uri(address);
var res = await _http.GetAsync("some-path");
//Handle response and do some business-logic
return new { };
}
}
This works, but whenever i use MyHttp in any service I need to make sure that this services sets the BaseAddress property.
I'd rather provide the Address as a constructor parameter.
First i thought of a factory implementation. But services.AddScoped(serviceProvider => {}) does not support async operations (And I understand that object initialization should be fast and reliable).
But I feel like my current solution is also a bad practice. Is there any better way of doing this?
That is right that service collection registration does not support async factory - because service resolving should be always fast.
But there is nothing wrong with initializing baseAddress after client creation. So if your wanna make sure that httpClient is initialized with correct base address how about extracting that logic into separate class that will build your http client configuration?
public YourHttpClientFactory {
private IHttpClientFactory _factory;
public YourHttpClientFactory(IHttpClientFactory factory)
{
_factory = factory;
}
public Task<HttpClient> Create(Guid id) {
var client = factory.createClient();
// do your async client initialization
return client;
}
}
I'm looking for suggestions on how to improve on my current design for testing a class (example below) that depends on HttpClient with a custom HttpClientHandler configuration. I normally use constructor injection to inject a HttpClient that is consistent across the application, however because this is in a class library I can't rely on the consumers of the library to set up the HttpClientHandler correctly.
For testing I follow the standard approach of replacing HttpClientHandler in the HttpClient constructor. Because I can't rely on the consumer of the library to inject a valid HttpClient I'm not putting this in a public constructor, instead I'm using a private constructor with an internal static method (CreateWithCustomHttpClient()) to create it. The intent behind this is:
Private constructor should not be called by a dependency injection library automatically. I'm aware that if I made it public/internal then some DI libraries that had a HttpClient already registered would call that constructor.
Internal static method can be called by a unit testing library using InternalsVisibleToAttribute
This setup seems quite complex to me and I'm hoping someone might be able to suggest an improvement, I am however aware that this could be quite subjective so if there are any established patterns or design rules to follow in this case I would really appreciate hearing about them.
I've included the DownloadSomethingAsync() method just to demonstrate why the non-standard configuration is required for HttpClientHandler. The default is for redirect responses to automatically redirect internally without returning the response, I need the redirect response so that I can wrap it in a class that report progress on the download (the functionality of that is not relevant to this question).
public class DemoClass
{
private static readonly HttpClient defaultHttpClient = new HttpClient(
new HttpClientHandler
{
AllowAutoRedirect = false
});
private readonly ILogger<DemoClass> logger;
private readonly HttpClient httpClient;
public DemoClass(ILogger<DemoClass> logger) : this(logger, defaultHttpClient) { }
private DemoClass(ILogger<DemoClass> logger, HttpClient httpClient)
{
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
this.httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
}
[Obsolete("This is only provided for testing and should not be used in calling code")]
internal static DemoClass CreateWithCustomHttpClient(ILogger<DemoClass> logger, HttpClient httpClient)
=> new DemoClass(logger, httpClient);
public async Task<FileSystemInfo> DownloadSomethingAsync(CancellationToken ct = default)
{
// Build the request
logger.LogInformation("Sending request for download");
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, "http://example.com/downloadredirect");
// Send the request
HttpResponseMessage response = await httpClient.SendAsync(request, ct);
// Analyse the result
switch (response.StatusCode)
{
case HttpStatusCode.Redirect:
break;
case HttpStatusCode.NoContent:
return null;
default: throw new InvalidOperationException();
}
// Get the redirect location
Uri redirect = response.Headers.Location;
if (redirect == null)
throw new InvalidOperationException("Redirect response did not contain a redirect URI");
// Create a class to handle the download with progress tracking
logger.LogDebug("Wrapping release download request");
IDownloadController controller = new HttpDownloadController(redirect);
// Begin the download
logger.LogDebug("Beginning release download");
return await controller.DownloadAsync();
}
}
In my opinion, I'd use IHttpClientFactory in Microsoft.Extensions.Http, and create a custom dependency injection extension for consumers of the class library to use:
public static class DemoClassServiceCollectionExtensions
{
public static IServiceCollection AddDemoClass(
this IServiceCollection services,
Func<HttpMessageHandler> configureHandler = null)
{
// Configure named HTTP client with primary message handler
var builder= services.AddHttpClient(nameof(DemoClass));
if (configureHandler == null)
{
builder = builder.ConfigurePrimaryHttpMessageHandler(
() => new HttpClientHandler
{
AllowAutoRedirect = false
});
}
else
{
builder = builder.ConfigurePrimaryHttpMessageHandler(configureHandler);
}
services.AddTransient<DemoClass>();
return services;
}
}
In DemoClass, use IHttpClientFactory to create named HTTP client:
class DemoClass
{
private readonly HttpClient _client;
public DemoClass(IHttpClientFactory httpClientFactory)
{
// This named client will have pre-configured message handler
_client = httpClientFactory.CreateClient(nameof(DemoClass));
}
public async Task DownloadSomethingAsync()
{
// omitted
}
}
You could require consumers to must call AddDemoClass in order to use DemoClass:
var services = new ServiceCollection();
services.AddDemoClass();
In this way, you could hide details of HTTP client construction.
Meanwhile, in tests, you could mock IHttpClientFactory to return HttpClient for testing purpose.
I want to register a typed HttpClient as here Microsoft docs.
Basically, the approach should be
services.AddHttpClient();
normally the pattern of these classes receive only the HttpClient class as a parameter and you implement the logic to call the endpoint. In my case, I need to use 2 HttpClient inside my MyHttpClient, one that pings the endpoint and the other one that talks with an IdentityProvider to discover the refreshEndpoints to refresh my cookies.
public class MyHttpClient : IMyHttpClient
{
public MyHttpClient (HttpClient httpClient,
HttpClient refreshHttpClient)
{
}
}
If I am trying to resolve from a controller an IMyHttpClient, I get an error saying it can't resolve an HttpClient.
In the GitHub code on line 43 AddHttpClient you can see that is calling
DefaultTypedHttpClientFactory.
If you go to the implementation of the DefaultTypedHttpClientFactory implementation you will notice that is a generic type. And when it calls CreateClient it only passes one parameter to the constructor on line 39.
The only workaround I am seeing here is to not create a typed client and register a normal class that receives an IHttpClientFactory and create and configure my clients on the fly, not as typed.
Any other idea?
You can't. You'll either need to inject another service layer or IHttpClientFactory directly
Another service
public class MyRefreshClient
{
private readonly HttpClient _httpClient;
public MyRefreshClient(HttpClient httpClient)
{
_httpClient = httpClient;
}
...
}
public class MyHttpClient : IMyHttpClient
{
private readonly HttpClient _httpClient;
private readonly MyRefreshClient _refreshClient;
public MyHttpClient(HttpClient httpClient, MyRefreshClient refreshClient)
{
_httpClient = httpClient;
_refreshClient = refreshClient;
}
}
Then:
services.AddHttpClient<MyRefreshClient>(c => { ... });
services.AddHttpClient<MyHttpClient>(c => { ... });
Inject IHttpClientFactory (and use named clients):
public class MyHttpClient : IMyHttpClient
{
private readonly HttpClient _httpClient;
private readonly HttpClient _refreshClient;
public MyHttpClient(IHttpClientFactory httpClientFactory)
{
_httpClient = httpClientFactory.CreateClient("MyHttpClient");
_refreshClient = httpClientFactory.CreateClient("MyRefreshClient");
}
}
Then:
services.AddHttpClient("MyHttpClient", c => { ... });
services.AddHttpClient("MyRefreshClient", c=> { ... });
Due on Improper Instantiation problem it is recommended to create private static readonly instance of HttpClient.
Due on lack of time I have injected mocked client into test method with client as their parameter.
The problem is how can I in simple way inject mock into private static readonly HttpClient field of SingleHttpClientInstanceController?
how can I in simple way inject mock into private static readonly
HttpClient field of SingleHttpClientInstanceController?
Answer: There is no simple way.
Suggestion:
Abstract the resource behind an accessor
public interface IHttpClientAccessor {
HttpClient HttpClient { get; }
}
and inject that into the dependent controller.
public class SingleHttpClientInstanceController : ApiController {
private readonly HttpClient HttpClient;
public SingleHttpClientInstanceController(IHttpClientAccessor httpClientAccessor) {
HttpClient = httpClientAccessor.HttpClient;
}
// This method uses the shared instance of HttpClient for every call to GetProductAsync.
public async Task<Product> GetProductAsync(string id) {
var hostName = HttpContext.Current.Request.Url.Host;
var result = await HttpClient.GetStringAsync(string.Format("http://{0}:8080/api/...", hostName));
return new Product { Name = result };
}
}
The same should also be done for accessing HttpContext which is what was recently introduced in Asp.Net-Core's IHttpContextAccessor
An implementation of the IHttpClientAcessor can look something like this
public class HttpClientAccessor : IHttpClientAccessor {
static readonly Lazy<HttpClient> client = new Lazy<HttpClient>(() => new HttpClient());
public HttpClient HttpClient { get { return client.Value; } }
}
So now for tests you can inject mock of the dependency.
If using a DI container remember to register the accessor as a singleton as well.