从依赖注入配置进行依赖调用
Make dependency calls from dependency injection configuration
我需要注入一个 HttpClient 并立即使用。但需要注意的是,HttpClient 需要设置 Authorization
header,为此我需要再打一个电话,获取令牌。
我设法在初创公司的 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).Result;
var token = response.Content.ReadFromJsonAsync<TokenResponse>().Result;
client.BaseAddress = new Uri(options.Url);
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token.Token);
});
然后它被正确地注入到我的代码中,我可以使用具有授权的客户端 header。
所以,我担心的是:
- 在依赖配置中进行HTTP调用是否正常?
- 没有找到比
.Result
更好的异步调用方法,还有其他选择吗?
主要问题:在 DI 配置中进行依赖调用是 'good idea' 吗?
Is it normal[/ a 'good idea'] to make HTTP calls within dependency configuration?
不,这与建立依赖项和 运行ning 所需的最低要求相去甚远,它包含属于您的 API-client-[=29] 的特定于实现的行为=].
您也不知道应用程序将 运行 以及令牌有效多长时间,因此期望在其构造函数中获得有效且随时可用的 HttpClient 的代码可能会保留只要它愿意就可以。
将您的 HttpClient 包装在第三方或您自己的包含 OAuth 流程的包装器中。当您的代码想要首次调用 API,或者当令牌过期,或者当您使用现有令牌获得 401 时,进行身份验证调用。
正如@TheGeneral 所评论的,这种包装器的一种方法是使用 DelegatingHandler
,如 this blog post 中的示例所述。摘要:
设置:
// 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);
}
}
虽然CodeCasters的回答很正确。理想情况下,您将为此任务使用委托处理程序。
它允许您在各个阶段拦截请求并在请求中添加任何需要的人工制品,在您的情况下是授权属性,它也是异步友好的。
但是请注意错误以及您 return 给来电者的内容。以免给消费者带来惊喜
虽然@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);
}
...
我需要注入一个 HttpClient 并立即使用。但需要注意的是,HttpClient 需要设置 Authorization
header,为此我需要再打一个电话,获取令牌。
我设法在初创公司的 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).Result;
var token = response.Content.ReadFromJsonAsync<TokenResponse>().Result;
client.BaseAddress = new Uri(options.Url);
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token.Token);
});
然后它被正确地注入到我的代码中,我可以使用具有授权的客户端 header。
所以,我担心的是:
- 在依赖配置中进行HTTP调用是否正常?
- 没有找到比
.Result
更好的异步调用方法,还有其他选择吗?
主要问题:在 DI 配置中进行依赖调用是 'good idea' 吗?
Is it normal[/ a 'good idea'] to make HTTP calls within dependency configuration?
不,这与建立依赖项和 运行ning 所需的最低要求相去甚远,它包含属于您的 API-client-[=29] 的特定于实现的行为=].
您也不知道应用程序将 运行 以及令牌有效多长时间,因此期望在其构造函数中获得有效且随时可用的 HttpClient 的代码可能会保留只要它愿意就可以。
将您的 HttpClient 包装在第三方或您自己的包含 OAuth 流程的包装器中。当您的代码想要首次调用 API,或者当令牌过期,或者当您使用现有令牌获得 401 时,进行身份验证调用。
正如@TheGeneral 所评论的,这种包装器的一种方法是使用 DelegatingHandler
,如 this blog post 中的示例所述。摘要:
设置:
// 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);
}
}
虽然CodeCasters的回答很正确。理想情况下,您将为此任务使用委托处理程序。
它允许您在各个阶段拦截请求并在请求中添加任何需要的人工制品,在您的情况下是授权属性,它也是异步友好的。
但是请注意错误以及您 return 给来电者的内容。以免给消费者带来惊喜
虽然@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);
}
...