为什么即使在 Startup 中设置 HttpClient 也不保存基地址

Why HttpClient does not hold the base address even when it`s set in Startup

在我的 .net 核心网络 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");
            });

除此之外,我还有 1 个注入 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/Headers 会丢失,我不明白为什么会发生这种情况。如果有人能展示一些光,我将不胜感激:)

第一次更新

该服务正在如下所示的控制器之一中使用。服务由 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);
        }
    }
}


有什么建议吗?

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

您应该在构造函数中显式注入 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 客户端。

好的,所以我会回答我的 post,因为建议的 TYPED 方法会导致问题,即未在 httpClient 中设置值,E.G 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;
            }
        }

发生这种情况的一个简单而令人沮丧的原因是您的服务收集分配顺序。

分配依赖服务之后 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);
});

@jack 发表了评论,几个人支持他,这是正确的决定,但这是错误的决定。

AddHttpClient 创建一个 TService 服务作为 Transient 服务,它传递一个专门为它创建的 HttpClient

首先调用 AddTransient,然后调用 AddHttpClient<>,您添加了一个依赖项的 2 个实现,并且只会返回最后添加的一个

// Create first dependency
services.AddTransient<HttpService>();

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