为啥 HttpClient 不保存基地址,即使它在 Startup 中设置

Posted

技术标签:

【中文标题】为啥 HttpClient 不保存基地址,即使它在 Startup 中设置【英文标题】:Why HttpClient does not hold the base address even when it`s set in Startup为什么 HttpClient 不保存基地址,即使它在 Startup 中设置 【发布时间】:2021-01-18 15:58:13 【问题描述】:

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

除此之外,我还有 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);
        
    



有什么建议吗?

【问题讨论】:

好酷。在控制器操作中从 urlEndpoint 中删除前导斜杠。 为了清楚起见。 IReService 真的是IRecipeService 您是否按照之前的建议删除了AddScoped,只使用了AddHttpClient @Nkosi 好的,所以我正在尝试回复您的 3 cmets。在你发表的第一条评论中,我做到了,我得到了这个错误An invalid request URI was provided. The request URI must either be an absolute URI or BaseAddress must be set. 虽然应该设置基础......在第二条评论中,是的,它们是相同的,但我没有留下来缩短事情起来了......它是一样的 @Nkosi 我确实删除了AddScoped,并且按照建议只使用了AddHttpClient,所以有1-1个相似的代码 【参考方案1】:

这可能发生的一个简单而令人沮丧的原因是您的服务集合分配的顺序。

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

【讨论】:

是的,这是正确的答案!我为此苦苦挣扎,只是像这样切换顺序。 是的。这是正确的【参考方案2】:

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

您应该在构造函数中显式注入 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的工厂会不会更好? @CsibiNorbert 你是什么意思它不起作用?错误是什么? 对不起,我的意思是,即使我调试,_httpClient 也没有标头中的基地址。错误如下Invalid URI: The format of the URI could not be determined.,因为在baseAddress = null。这有意义吗? 微软在客户端类型方面存在问题,并且不断地改变一切。查看我的代码中@Nkosi 所做的最后更改。试试这个。 / Nikosi 问题仍然存在,正如我上面提到的 :(【参考方案3】:

好的,所以我会回答我的帖子,因为建议的 TYPED 方式会导致未在 httpClient 中设置值的问题,E.G BaseAddress 始终为空。

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

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

但我没有这样做,而是选择与命名客户端一起使用。也就是说在启动时我们需要像这样注册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;
            
        

【讨论】:

【参考方案4】:

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

【讨论】:

这似乎与@Jack 的回答一致。你能澄清一下你试图画出的区别吗?

以上是关于为啥 HttpClient 不保存基地址,即使它在 Startup 中设置的主要内容,如果未能解决你的问题,请参考以下文章

为啥即使使用提交后 sql plus 也不会保存新行?

如何在没有时间的情况下将 LocalDate 保存到 MongoDB(即使我只保存日期,为啥 mongo 会随时间保存日期)?

即使 AllowAutoRedirect = true HttpClient 也不会重定向

MSVC 中的地址消毒器:为啥它在启动时报告错误?

是否可以将 HttpClient 配置为不保存 cookie?

为啥 RStudio/R 中显示为 1 GB 大小的对象会被 RData 或 RDS 文件格式以更大的大小保存,即使没有压缩?