c# #.net-core #dependency-injection #.net-5 #dotnet-httpclient
#c# #.net-ядро #внедрение зависимостей #.net-5 #dotnet-httpclient
Вопрос:
Мне нужно сразу ввести HttpClient и подготовить его к использованию. Но предостережение заключается в том, что HttpClient должен иметь Authorization
установленный заголовок, и для этого мне нужно сделать еще один вызов, получив токен. Мне удалось выполнить всю эту настройку в RegisterServices запуска, но я сомневаюсь, что это хорошая идея.
services.AddHttpClient("OidcClient", (isp, client) =>
{
var options = isp.GetRequiredService<IOptions<MyConfig>>().Value;
client.BaseAddress = new Uri(options.OidcUrl);
});
services.AddHttpClient("MyClient", (isp, client) =>
{
var options = isp.GetRequiredService<IOptions<MyConfig>>().Value;
var oidcClient = isp.GetRequiredService<IHttpClientFactory>().CreateClient("OidcClient");
var data = new Dictionary<string, string>
{
{"client_id", options.ClientId},
{"client_secret", options.ClientSecret}
};
var request = new HttpRequestMessage(HttpMethod.Post, "/connect/token") { Content = new FormUrlEncodedContent(data) };
var response = oidcClient.SendAsync(request).Resu<
var token = response.Content.ReadFromJsonAsync<TokenResponse>().Resu<
client.BaseAddress = new Uri(options.Url);
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token.Token);
});
Затем он правильно вводится в мой код, и я могу использовать клиент с заголовком авторизации.
Итак, мои опасения:
- нормально ли выполнять HTTP-вызовы в конфигурации зависимостей?
- не нашли ничего лучше, чем
.Result
для асинхронного вызова, есть ли альтернатива?
И главный вопрос: является ли «хорошей идеей» выполнять вызовы зависимостей внутри конфигурации DI?
Комментарии:
1. Используйте обработчик делигаринга, это один из вариантов использования, для которого они были разработаны, и они дружественны к асинхронности
2. Вау, DelegatingHandler потрясающий и точно соответствует моим потребностям. Спасибо за подсказку.
Ответ №1:
Нормально ли [/ «хорошая идея»] выполнять HTTP-вызовы в конфигурации зависимостей?
Нет, это далеко от минимума, необходимого для запуска и запуска ваших зависимостей, он содержит поведение, зависящее от конкретной реализации, которое относится к вашему API-client-class.
Вы также не знаете, как долго будет выполняться приложение и как долго токен будет действительным, поэтому код, ожидающий получить действительный и готовый к работе HttpClient в своем конструкторе, может удерживать его столько, сколько пожелает.
Оберните ваш HttpClient в стороннюю или собственную оболочку, которая включает поток OAuth. Выполняйте вызов аутентификации, когда ваш код хочет выполнить свой первый вызов API, или когда срок действия токена истек, или когда вы получаете 401, используя существующий токен.
Как прокомментировал @TheGeneral, одним из подходов к такой оболочке является использование a DelegatingHandler
, как описано, например, в этом сообщении в блоге . Краткие сведения:
Настройка:
// The DelegatingHandler has to be registered as a Transient Service
services.AddTransient<ProtectedApiBearerTokenHandler>();
// Register our ProtectedApi client with a DelegatingHandler
// that knows how to obtain an access_token
services.AddHttpClient<IProtectedApiClient, ProtectedApiClient>(client =>
{
client.BaseAddress = new Uri("http://localhost:5002");
client.DefaultRequestHeaders.Add("Accept", "application/json");
}).AddHttpMessageHandler<ProtectedApiBearerTokenHandler>();
Обработчик:
public class ProtectedApiBearerTokenHandler : DelegatingHandler
{
private readonly IIdentityServerClient _identityServerClient;
public ProtectedApiBearerTokenHandler(
IIdentityServerClient identityServerClient)
{
_identityServerClient = identityServerClient
?? throw new ArgumentNullException(nameof(identityServerClient));
}
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
// request the access token
var accessToken = await _identityServerClient.RequestClientCredentialsTokenAsync();
// set the bearer token to the outgoing request
request.SetBearerToken(accessToken);
// Proceed calling the inner handler, that will actually send the request
// to our protected api
return await base.SendAsync(request, cancellationToken);
}
}
Ответ №2:
Хотя кодек-кодеры отвечают очень правильно. В идеале вы должны использовать делегирующий обработчик для этой задачи.
Это позволяет вам перехватывать запрос на разных этапах и добавлять в запрос любые необходимые артефакты, в вашем случае атрибуты авторизации, это также удобно для асинхронности.
Однако позаботьтесь об ошибках и о том, что вы возвращаете вызывающему. чтобы не было никаких сюрпризов для потребителя
Ответ №3:
В то время как @CodeCaster помог мне понять, что получение токена не должно быть частью конфигурации, @TheGeneral намекнул на использование DelegatingHandler, который, на мой взгляд, является идеальным решением для этого. Наконец, я пришел к этому коду в конфигурации:
services.AddTransient<AuthorizationHandler>();
services.AddHttpClient<AuthorizationHandler>((isp, client) =>
{
var options = isp.GetRequiredService<IOptions<MyConfig>>().Value;
client.BaseAddress = new Uri(options.OidcUrl);
});
services.AddHttpClient("MyClient", (isp, client) =>
{
var options = isp.GetRequiredService<IOptions<MyConfig>>().Value;
client.BaseAddress = new Uri(options.Url);
}).AddHttpMessageHandler<AuthorizationHandler>();
и сам обработчик:
public class AuthorizationHandler : DelegatingHandler
{
private readonly HttpClient _client;
private readonly MyConfig _options;
public AuthorizationHandler(HttpClient client, IOptions<MyConfig> options)
{
_client = client;
_options = options.Value;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var token = await GetToken();
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
return await base.SendAsync(request, cancellationToken);
}
...