I am working on a Blazor project, and to make the question I have easier to understand, we can say that I am using two different services that handles the Authentication part. Those are registered in the configureservices startup method together with a named httpclient.
services.AddHttpClient("XBOWServicesApi", c =>
{
c.BaseAddress = new Uri(XBOWServicesApi);
});
services.AddSingleton<IService1, Service1>();
services.AddSingleton<IService2, Service2>();
Service 1: Wraps all functionality available in a REST Api. It uses an http client which is set in the constructor via an instanciated httpclientfactory. This needs to be set with a baseurl and an Auth-header to work.
public Service1(IHttpClientFactory clientFactory)
{
this.httpClient = clientFactory.CreateClient("XBOWServicesApi");
}
Service 2: Handles the login/logout functionality using a custom AuthenticationStateProvider. It has its own httpclient, so that I can set the Auth Header for the http client. The constructor works in the same way as for Service 1.
public Service2(IHttpClientFactory clientFactory)
{
this.httpClient = clientFactory.CreateClient("XBOWServicesApi");
}
The reason for this build up is of course that I like to share the same http client, so when it is set in the login/logout methods, service 1 will have the correct auth header when communicating with the api.
However, the client factory provides a new instance everytime, so this will never work.
Any ideas how to handle this?
/Henrik
You can use named client:
services.AddHttpClient("github", 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");
});
Afterwards, just call CreateClient method with corresponding name parameter.
var client = _clientFactory.CreateClient("github");
Each time CreateClient is called:
A new instance of HttpClient is created.
The configuration action is
called.
You can find more details in Microsoft documentation here.
When I read through the Microsoft IHttpClientFactory docs:
Each time you get an HttpClient object from the IHttpClientFactory, a
new instance is returned. But each HttpClient uses an
HttpMessageHandler that's pooled and reused by the IHttpClientFactory
to reduce resource consumption, as long as the HttpMessageHandler's
lifetime hasn't expired.
Does that answer your question?
You can share scoped services between transient HttpClients by using HttpMessageHandlers.
IHttpClient.CreateClient returns a new instance every time, but you can register a HttpMessageHandler as shown below:
services.AddScoped<HandlerData>();
services.AddTransient<HeaderHandler>();
services.AddHttpClient("XBOWServicesApi", c =>
{
c.BaseAddress = new Uri(XBOWServicesApi);
}).AddHttpMessageHandler<HeaderHandler>();
HeaderHandler Class:
public class HeaderHandler : DelegatingHandler
{
private readonly IHttpContextAccessor httpContextAccessor;
public HeaderHandler(IHttpContextAccessor httpContextAccessor)
{
this.httpContextAccessor = httpContextAccessor;
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken )
{
var Data= this.httpContextAccessor.HttpContext.RequestServices.GetRequiredService<HandlerData>();
request.Headers.Add(Data.HeaderName, Data.HeaderValue);
return base.SendAsync(request, cancellationToken);
}
}
HandlerData Class:
public class HandlerData
{
public string HeaderName { get; set; }
public string HeaderValue { get; set; }
}
ServicesCode:
public Service1(IHttpClientFactory clientFactory, HandlerData data)
{
data.HeaderName = "Header1";
data.HeaderValue = "Value";
this.httpClient = clientFactory.CreateClient("XBOWServicesApi");
}
public Service2(IHttpClientFactory clientFactory)
{
//This will contain the same headers as Service1 as HandlerData is Scoped Service
this.httpClient = clientFactory.CreateClient("XBOWServicesApi");
}
Alternatively, you can also use new IHttpMessageHandlerFactory if you need to create handlers that live in the same DI scope as you request:
Reference: https://github.com/aspnet/HttpClientFactory/issues/166
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;
}
}
In my .net core web api project I would like to hit an external API so that I get my response as expected.
The way I`m registering and using the HttpClient is as follows. In the startup, I'm adding the following code which is called named typed httpclient way.
services.AddHttpClient<IRecipeService, RecipeService>(c => {
c.BaseAddress = new Uri("https://sooome-api-endpoint.com");
c.DefaultRequestHeaders.Add("x-raay-key", "123567890754545645gggg");
c.DefaultRequestHeaders.Add("x-raay-host", "sooome-api-endpoint.com");
});
In addition to this, I have 1 service in which I inject the HttpClient.
public class RecipeService : IRecipeService
{
private readonly HttpClient _httpClient;
public RecipeService(HttpClient httpClient)
{
_httpClient = httpClient;
}
public async Task<List<Core.Dtos.Recipes>> GetReBasedOnIngAsync(string endpoint)
{
using (var response = await _httpClient.GetAsync(recipeEndpoint))
{
// ...
}
}
}
When the httpClient is created, if I hover over the object itself, the base URI/Headers are missing, and I don't understand why exactly this is happening. I would appreciate if someone could show some light :)
UPDATE 1ST
The service is being used in one of the Controllers shown below. The service is injected by the DI and then the relative path is parsed to the service ( I assumed I already have the base URL stored in the client ) Maybe I`m doing it wrong?
namespace ABC.Controllers
{
[ApiController]
[Route("[controller]")]
public class FridgeIngredientController : ControllerBase
{
private readonly IRecipeService _recipeService;
private readonly IMapper _mapper;
public FridgeIngredientController(IRecipeService recipeService, IMapper mapper)
{
_recipeService = recipeService;
_mapper = mapper;
}
[HttpPost("myingredients")]
public async Task<ActionResult> PostIngredients(IngredientsDto ingredientsDto)
{
var readyUrIngredientStr = string.Join("%2", ingredientsDto.ingredients);
var urlEndpoint = $"recipes/findByIngredients?ingredients={readyUrIngredientStr}";
var recipesResponse = await _recipeService.GetRecipeBasedOnIngredientsAsync(urlEndpoint);
InMyFridgeRecipesDto recipesFoundList = new InMyFridgeRecipesDto
{
FoundRecipes = recipesResponse
};
return Ok(recipesFoundList);
}
}
}
Any suggestions?
A simple, frustrating reason this may happen is due to the order of your service collection statements.
Assigning the dependant service after the HTTPClient will not work, it must come before:
// NOT WORKING - BaseAddress is null
services.AddTransient<Controller1>();
services.AddTransient<Controller2>();
services.AddHttpClient<HttpService>(client =>
{
client.BaseAddress = new Uri(baseAdress);
});
services.AddTransient<HttpService>();
// WORKING - BaseAddress is not null
services.AddTransient<Controller1>();
services.AddTransient<Controller2>();
services.AddTransient<HttpService>();
services.AddHttpClient<HttpService>(client =>
{
client.BaseAddress = new Uri(baseAdress);
});
EDIT
As LIFEfreedom rightfully pointed out in their answer: while the order of the statements has an effect here, it is not the reason for behaviour.
Both of the following statements create a transient service for the HttpService class:
services.AddTransient<HttpService>();
services.AddHttpClient<HttpService>();
However, when adding both of these statements only the latest one will be used, overwriting any statements before it. In my example, I only got the expected result when the AddHttpClient statement with the base address configuration came last.
You configured your client as a typed client and not a named client. No need for the factory.
You should explicitly inject the http client in constructor instead, not the http client factory.
Change your code to this:
private readonly HttpClient _httpClient;
public ReService(HttpClient httpClient;) {
_httpClient = httpClient;
}
public async Task<List<Core.Dtos.Re>> GetReBasedOnIngAsync(string endpoint)
{
///Remove this from your code
var client = _httpClientFactory.CreateClient(); <--- HERE the base URL/Headers are missing
var request = new HttpRequestMessage
{
Method = HttpMethod.Get,
RequestUri = new Uri(endpoint)
};
//////
using (var response = await _httpClient.GetAsync(endpoint))
{
// ...
}
}
And according to last MS documentation only the typed client registration is needed in this case. Fix your startup to this:
// services.AddScoped<IReService, ReService>(); //<-- REMOVE. NOT NEEDED
services.AddHttpClient<IReService, ReService>(c => ...
But you still can try to add you base address, please add trailing slash ( and let us know if it still works):
services.AddHttpClient<IReService, ReService>(c => {
c.BaseAddress = new Uri("https://sooome-api-endpoint.com/");
});
if problem still persists I recommend you to try named http clients.
Okay so I will answer my post because with the suggested TYPED way of doing it was causing problems with the values not being set inside the httpClient, E.G BaseAddress was always null.
In the startup I was trying to go with typed httpclient e.g
services.AddHttpClient<IReService, ReService>(c => ...
But instead of doing that, I choose the to go with the Named client. Which means that in the startup we need to register the httpclient like this
services.AddHttpClient("recipeService", c => {
....
And then in the service itself I used HttpClientFactory like below.
private readonly IHttpClientFactory _httpClientFactory;
public RecipeService(IHttpClientFactory httpClientFactory)
{
_httpClientFactory = httpClientFactory;
}
public async Task<List<Core.Dtos.Recipes>> GetRecipeBasedOnIngredientsAsync(string recipeEndpoint)
{
var client = _httpClientFactory.CreateClient("recipeService");
using (var response = await client.GetAsync(client.BaseAddress + recipeEndpoint))
{
response.EnsureSuccessStatusCode();
var responseRecipies = await response.Content.ReadAsStringAsync();
var recipeObj = ConvertResponseToObjectList<Core.Dtos.Recipes>(responseRecipies);
return recipeObj ?? null;
}
}
#jack wrote a comment and several guys supported him that this is the right decision, but it is the wrong decision.
AddHttpClient creates a TService service as a Transient service, to which it passes an HttpClient created only for it
Calling first AddTransient, and then AddHttpClient<>, you add 2 implementations of one dependency and only the last added one will be returned
// Create first dependency
services.AddTransient<HttpService>();
// Create second and last dependency
services.AddHttpClient<HttpService>(client =>
{
client.BaseAddress = new Uri(baseAdress);
});
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.
With the new HttpClientFactory in ASP.NET Core 2.1, it's quite easy to configure custom HTTP clients with things like base urls, default headers etc.
However, I haven't found a way to centralize configuration that lets me inject headers from the current request context. For example, consider a service called with an Authorization header, which I wish to pass on to all the underlying services as well. It would be awesome to be able to configure this in the .AddHttpClient() call on services in the Startup class, but I can't figure out how to get at the request context from there.
Any ideas?
Working on this answer lead me to multiple answers. I think the first approach is what you are looking for, the second is a good alternative.
In order to configure multiple clients you can use named clients. These clients are registered as transient. Use DI to get the service that has access to the request context.
For that we need IHttpContextAccessor. In this case you don't have to register it yourself, because Identity already does that for you.
Otherwise add the following line in startup:
services.AddHttpContextAccessor();
Next we can configure the named client "github":
services.AddHttpClient("github", c =>
{
// access the DI container
var serviceProvider = services.BuildServiceProvider();
// Find the HttpContextAccessor service
var httpContextAccessor = serviceProvider.GetService<IHttpContextAccessor>();
// Get the bearer token from the request context (header)
var bearerToken = httpContextAccessor.HttpContext.Request
.Headers["Authorization"]
.FirstOrDefault(h => h.StartsWith("bearer ", StringComparison.InvariantCultureIgnoreCase));
// Add authorization if found
if (bearerToken != null)
c.DefaultRequestHeaders.Add("Authorization", bearerToken);
// Other settings
c.BaseAddress = new Uri("https://api.github.com/");
c.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json"); // Github API versioning
c.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample"); // Github requires a user-agent
});
Call the client like this:
public class MyController : ControllerBase
{
private readonly IHttpClientFactory _clientFactory;
public MyController(IHttpClientFactory clientFactory)
{
_clientFactory = clientFactory;
}
public async Task<ActionResult> StartCall()
{
var client = _clientFactory.CreateClient("github");
var response = await client.GetAsync("/repos/aspnet/docs/issues");
}
}
Another option is to use Typed clients. Here's a short example. For a full example check the link.
Register IHttpContextAccessor:
services.AddHttpContextAccessor();
Create a typed client. I've added two options to add settings. One through the request context and one through a singleton class:
public class GitHubService
{
public HttpClient Client { get; }
public GitHubService(HttpClient client, HttpClientSettings httpClientSettings, IHttpContextAccessor httpContextAccessor)
{
var bearerToken = httpContextAccessor.HttpContext.Request
.Headers["Authorization"]
.FirstOrDefault(h => h.StartsWith("bearer ", StringComparison.InvariantCultureIgnoreCase));
// Add authorization if found
if (bearerToken != null)
client.DefaultRequestHeaders.Add("Authorization", bearerToken);
// Or the value from httpClientSettings:
client.DefaultRequestHeaders.Add("Authorization", httpClientSettings.BearerToken);
client.BaseAddress = new Uri("https://api.github.com/");
client.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json"); // GitHub API versioning
client.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample"); // GitHub requires a user-agent
Client = client;
}
}
Register the client:
// The typed client is registered as transient with DI.
services.AddHttpClient<GitHubService>();
Please note, the code below is just an example. Since the token can't be persisted in the client, you can use the shared HttpClientSettings instead:
services.AddSingleton<HttpClientSettings>();
Where HttpClientSettings is:
public class HttpClientSettings
{
public string BearerToken { get; set; }
}
You can use the client like this:
public class MyController : ControllerBase
{
private readonly GitHubService _gitHubService;
public MyController(GitHubService gitHubService)
{
_gitHubService = gitHubService;
}
public async Task<ActionResult> StartCall()
{
var response = await _gitHubService.Client.GetAsync("/repos/aspnet/docs/issues");
}
}
Since .NET Core 3.0 you can use HeaderPropagation.
ConfigureServices in Startup.cs
services.AddHeaderPropagation(o =>
{
o.Headers.Add("Authorization");
});
services.AddHttpClient<YourTypedHttpClient>().AddHeaderPropagation();
Configure in Startup.cs
app.UseHeaderPropagation();
And this will automatically propagate Authorization header. You can also use it for other headers as well.