为什么HttpClient不持有基地地址,即使它在启动时设置



在我的。net core web api项目中,我想要击中外部api,以便我得到预期的响应。

我注册和使用HttpClient的方式如下。在启动中,我添加了以下代码,称为命名类型的httpclient方式。

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");
});

除此之外,我还有一个注入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))
{
// ...
}
}
}

当httpClient被创建,如果我悬停在对象本身,基本的URI/报头丢失,我不明白为什么会发生这种情况。如果有人能指点一下,我将不胜感激:)

更新1

服务正在如下所示的控制器之一中使用。服务是由DI注入的,然后相对路径被解析为服务(我假设我已经在客户端中存储了基本URL)。


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);
}
}
}

有什么建议吗?

可能发生这种情况的一个简单而令人沮丧的原因是您的服务集合语句的顺序。

在之后分配依赖服务HTTPClient不能工作,它必须在

// 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);
});

编辑

正如LIFEfreedom在他们的回答中正确指出的那样:虽然语句的顺序在这里有影响,但它不是行为的原因。

下面两个语句都为HttpService类创建了一个瞬态服务:

services.AddTransient<HttpService>();
services.AddHttpClient<HttpService>();

但是,当添加这两个语句时,只使用最新的语句,覆盖在它之前的所有语句。在我的示例中,只有当带有基址配置的AddHttpClient语句最后出现时,我才得到了预期的结果。

您将客户端配置为类型化客户端而不是命名客户端。不需要工厂

应该显式地在构造函数中注入http客户端,而不是http客户端工厂。

把你的代码改成:

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))
{
// ...
}
}

根据最后的MS文档,在这种情况下只需要键入客户端注册。修改你的启动:

// services.AddScoped<IReService, ReService>(); //<-- REMOVE. NOT NEEDED
services.AddHttpClient<IReService, ReService>(c => ...

但你仍然可以尝试添加你的基地地址,请添加尾斜杠(并让我们知道它是否仍然有效):

services.AddHttpClient<IReService, ReService>(c => {
c.BaseAddress = new Uri("https://sooome-api-endpoint.com/");
});

如果问题仍然存在,我建议您尝试命名http客户端。

好的,所以我会回答我的帖子,因为建议的打字方式会导致httpClient内未设置值的问题,例如BaseAddress始终为空。

在启动时,我试图使用键入httpclient,例如

services.AddHttpClient<IReService, ReService>(c => ...

但是我没有这样做,而是选择使用Named客户端。这意味着在启动时我们需要像这样注册httpclient

services.AddHttpClient("recipeService", c => {
....

然后在服务本身中,我像下面这样使用HttpClientFactory。

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写了一条评论,几个人支持他,认为这是正确的决定,但这是错误的决定。

AddHttpClient创建一个TService服务作为一个瞬态服务,它向它传递一个仅为它创建的HttpClient

首先调用AddTransient,然后调用AddHttpClient<>,你添加了一个依赖的两个实现,只有最后添加的一个才会返回

// Create first dependency
services.AddTransient<HttpService>();
// Create second and last dependency
services.AddHttpClient<HttpService>(client =>
{
client.BaseAddress = new Uri(baseAdress);
});

正如@LIFEfreedom所指出的那样。注册顺序会产生一些影响。但是为什么呢?如果我们在注入的服务之后注册客户端类型,它就会工作。原因类型客户机再次注册服务。所以有多个服务被注入。但是正如我们所知道的那样,DI提供了您最后注册的服务,这就是为什么如果在最后注册了客户端类型,它会工作的原因。

解决实际问题的简单解决方案是不单独注册服务。AddHttpClient将为您做这件事。据我所知,客户端注册服务类型是暂时的,所以在使用它之前一定要确定。

// Do not register this
// services.AddTransient<HttpService>();
// This will automatically register HttpService.
services.AddHttpClient<HttpService>(client =>
{
client.BaseAddress = new Uri(baseAdress);
});```

我就经历过这种情况,对我来说有效的方法是像这样使用接口和服务的具体实现,而不是只使用具体的类:

services.AddHttpClient<**ICategoryManager, CategoryManager**>(httpClient => 
{ 
httpClient.BaseAddress = new Uri("https://sooome-api-endpoint.com/categories"); 
}); 

最新更新