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

Csi*_*ert 14 c# asp.net-web-api dotnet-httpclient asp.net-core-webapi httpclientfactory

在我的 .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");
            });
Run Code Online (Sandbox Code Playgroud)

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

Run Code Online (Sandbox Code Playgroud)

创建 httpClient 时,如果我将鼠标悬停在对象本身上,则基本 URI/标头会丢失,并且我不明白为什么会发生这种情况。如果有人能透露一些信息,我将不胜感激:)

第一次更新

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


Run Code Online (Sandbox Code Playgroud)

有什么建议么?

Jac*_*ack 26

发生这种情况的一个简单但令人沮丧的原因是服务收集语句的顺序。

在 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);
});
Run Code Online (Sandbox Code Playgroud)

编辑

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

以下两条语句都会为该类创建一个临时服务HttpService

services.AddTransient<HttpService>();
services.AddHttpClient<HttpService>();
Run Code Online (Sandbox Code Playgroud)

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


Ser*_*rge 11

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

您应该在构造函数中显式注入 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))
    {
        // ...
    }
}
Run Code Online (Sandbox Code Playgroud)

根据最新的 MS 文档,在这种情况下仅需要键入客户端注册。将您的启动修复为:

// services.AddScoped<IReService, ReService>(); //<-- REMOVE. NOT NEEDED
services.AddHttpClient<IReService, ReService>(c => ...
Run Code Online (Sandbox Code Playgroud)

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

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

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


Csi*_*ert 5

好吧,我会回答我的帖子,因为使用建议的 TYPED 方法会导致 httpClient 内未设置值的问题,EG BaseAddress 始终为空。

在启动时我试图使用类型化的 httpclient 例如

services.AddHttpClient<IReService, ReService>(c => ...
Run Code Online (Sandbox Code Playgroud)

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

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

Run Code Online (Sandbox Code Playgroud)

然后在服务本身中,我使用了 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;
            }
        }
Run Code Online (Sandbox Code Playgroud)