7

在我的 .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");
            });

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


有什么建议么?

4

4 回答 4

11

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

在 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);
});
于 2021-06-09T16:21:12.497 回答
6

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

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

于 2021-01-18T16:11:02.113 回答
4

好的,所以我会回答我的帖子,因为建议的 TYPED 方式会导致未在 httpClient 中设置值的问题,EG 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;
            }
        }
于 2021-01-18T19:38:27.827 回答
0

@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);
});
于 2021-12-13T20:42:28.363 回答