Почему HttpClient не содержит базовый адрес, даже если он установлен при запуске

#c# #asp.net-web-api #asp.net-core-webapi #dotnet-httpclient #httpclientfactory

#c# #asp.net-web-api #asp.net-core-webapi #dotnet-httpclient #httpclientfactory

Вопрос:

В моем проекте .net core web api я хотел бы использовать внешний API, чтобы получить ожидаемый ответ.

Способ регистрации и использования HttpClient заключается в следующем. При запуске я добавляю следующий код, который называется named typed httpclient way .

  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 / заголовки отсутствуют, и я не понимаю, почему именно это происходит. Я был бы признателен, если бы кто-нибудь мог показать некоторый свет 🙂

ОБНОВЛЕНИЕ 1ST

Служба используется в одном из контроллеров, показанных ниже. Служба вводится 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);
        }
    }
}


 

Есть предложения?

Комментарии:

1. Хорошо, круто. удалите начальную косую черту из urlEndpoint действия контроллера.

2. И просто для ясности. IReService действительно ли так и должно быть IRecipeService ?

3. Вы удалили AddScoped , как предлагалось ранее, и использовали только AddHttpClient ?

4. @Nkosi Хорошо, итак, я пытаюсь ответить на ваши 3 комментария. В первом комментарии, который вы сделали, я сделал это, и я получил эту ошибку An invalid request URI was provided. The request URI must either be an absolute URI or BaseAddress must be set. , хотя база должна была быть установлена… Во втором комментарии, да, они одинаковы, но я не остался, чтобы сократить все anymore…it это то же самое

5. @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);
});
 

Комментарии:

1. Да, это правильный ответ! Я боролся с этим и просто переключал порядок, как это работает.

2. ДА. Это правильно

Ответ №2:

Вы настроили свой клиент как типизированный клиент, а не именованный клиент. Нет необходимости в factory.

Вместо этого вы должны явно ввести 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-клиенты.

Комментарии:

1. Я сделал именно так, как вы сказали, но это все равно не работает. Однако у меня есть вопрос, не лучше ли использовать фабрику httpclient?

2. @CsibiNorbert Что вы имеете в виду, это не работает? В чем ошибка?

3. Извините, я хотел сказать, что даже при отладке в заголовках _httpClient и nor нет базового адреса. И ошибка заключается в следующем Invalid URI: The format of the URI could not be determined. , потому что в BaseAddress = null . Имеет ли это смысл?

4. У Microsoft проблемы с типизированными клиентами и она постоянно все меняет. Посмотрите на последние изменения, внесенные @Nkosi в мой код. Попробуйте это.

5. / Nikosi проблема по-прежнему сохраняется, как я упоминал выше: (

Ответ №3:

Хорошо, поэтому я отвечу на свой пост, потому что предложенный типизированный способ выполнения вызывал проблемы со значениями, которые не были установлены внутри HttpClient, например, 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 как временную службу, которой он передает HttpClient, созданный только для него

Вызывая сначала AddTransient, а затем AddHttpClient<>, вы добавляете 2 реализации одной зависимости, и будет возвращена только последняя добавленная зависимость

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

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

Комментарии:

1. Это согласуется с ответом @Jack. Можете ли вы уточнить различие, которое вы пытаетесь провести?

2. Разница в том, что второй вызов — единственный, который вам нужен. AddTransient Вызов перезапишет AddHttpClient вызов. Дополнительная информация о MSDN в AddHttpClient<TClient,TImplementation>(IServiceCollection, Func<HttpClient,TImplementation>) разделе. Цитируя документы, » TClient Тип введенного клиента. Указанный тип будет зарегистрирован в коллекции сервисов как временная служба «.