#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
Тип введенного клиента. Указанный тип будет зарегистрирован в коллекции сервисов как временная служба «.