c# - 如何在 Blazor 服务器应用程序的服务注册中将访问 token 注入(inject)到 HttpClient?

标签 c# dependency-injection dotnet-httpclient blazor-server-side

我有 Blazor 服务器应用程序来使用 Identity Server 4 进行身份验证和授权。而且,我有一个 protected api(JWT token )来向 Blazor 服务器应用程序提供数据。

我已经按照this post获取访问 token 并在服务注册期间将其传递给HttpClient,如下所示,

Startup.cs

public void ConfigureServices(IServiceCollection services)
{
    services.AddAntDesign();

    services.AddRazorPages();
    services.AddServerSideBlazor();
    services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
    services.AddScoped<TokenProvider>(); <--
    ApplicationInitializer.Initialize(Configuration, services);
}

ApplicationInitializer.cs

public static class ApplicationInitializer
{
    public static void Initialize(IConfiguration configuration, IServiceCollection services)
    {
        var installers = typeof(Startup).Assembly.ExportedTypes
            .Where(w => typeof(IInstaller).IsAssignableFrom(w) && !w.IsInterface && !w.IsAbstract)
            .Select(Activator.CreateInstance)
            .Cast<IInstaller>()
            .ToList();

        installers.ForEach(installer => installer.InstallServices(services, configuration));
    }
}

ServiceCollectionRegistration.cs

public class ServiceCollectionRegistration : IInstaller
{
    public void InstallServices(IServiceCollection services, IConfiguration configuration)
    {
        //Register api (NSwag generated) clients with HttpClient from HttpClientFactory
        var apiOptions = new ApiOptions();
        configuration.GetSection(nameof(ApiOptions)).Bind(apiOptions);
        services.AddHttpClient("api", (provider, client) =>
        {
            client.BaseAddress = new Uri(apiOptions.ApiUrl);

            //This is not working as expected. Access Token is always null
            var tokenProvider = services.BuildServiceProvider().GetRequiredService<TokenProvider>();
            var accessToken = tokenProvider.AccessToken;
            if (accessToken != null)
                client.DefaultRequestHeaders.Add("Authorization", "Bearer " + accessToken);
        });

        var asm = Assembly.GetExecutingAssembly();
        var interfaces = asm.GetInterfaces();
        foreach (var interfaceType in interfaces.Where(x => x.Name.EndsWith("Client")))
        {
            var currentInterfaceType = interfaceType;
            var implementations = asm.GetImplementationsOfInterface(interfaceType);
            implementations.ToList().ForEach(i =>
            {
                services.AddScoped(currentInterfaceType, ctx =>
                {
                    var clientFactory = services.BuildServiceProvider().GetRequiredService<IHttpClientFactory>();
                    var httpClient = clientFactory.CreateClient("api");
                    return Activator.CreateInstance(i, httpClient);
                });
            });
        }

        //Register all provider type to their interface type using reflection
        foreach (var interfaceType in interfaces.Where(x => !x.Name.EndsWith("Client")))
        {
            var currentInterfaceType = interfaceType;
            var implementations = asm.GetImplementationsOfInterface(interfaceType);
            if (implementations.Count > 1)
                implementations.ToList().ForEach(i => services.AddScoped(currentInterfaceType, i));
            else
                implementations.ToList().ForEach(i => services.AddScoped(currentInterfaceType, i));
        }
        new AutoMapperConfiguration().RegisterProfiles(services);
    }
}

我无法将访问 token 分配给 HttpClient 对象,因为它始终为 null。我错过了什么吗?

最佳答案

I am unable to assign access token to HttpClient object since it was null always.

由于在请求期间要从当前 HttpContext 访问 token ,因此在注册客户端时该 token 将不可用。

这使得尝试添加默认 header 并不理想。

考虑改变方法。

创建消息处理程序以在请求范围内拦截/提取并注入(inject)所需的 header

public class TokenHandler : DelegatingHandler {
    private readonly IHttpContextAccessor accessor;
    
    public TokenHandler(IHttpContextAccessor accessor) => this.accessor = accessor;

    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) {
        //get the token
        var accessToken = await accessor.HttpContext.GetTokenAsync("access_token");
        //add header
        request.Headers.Authorization =
            new AuthenticationHeaderValue("Bearer", accessToken);
        //continue down stream request
        return await base.SendAsync(request, cancellationToken);
    }
}

在注册客户端时将消息处理程序包含在管道中,就像在这个添加名为 client 的 API 的简化示例中一样

public void ConfigureServices(IServiceCollection services) {
    services.AddAntDesign();

    services.AddRazorPages();
    services.AddServerSideBlazor();
    services.AddHttpContextAccessor();
    services.AddScoped<TokenHandler>();
    
    //Register api (NSwag generated) clients with HttpClient from HttpClientFactory
    ApiOptions apiOptions = Configuration.GetSection(nameof(ApiOptions)).Get<ApiOptions>();
    services
        .AddHttpClient("api", (client) => {
            client.BaseAddress = new Uri(apiOptions.ApiUrl);
        })
        .AddHttpMessageHandler<TokenHandler>(); //inject token using our token handler
    
    //...
}

ServiceCollectionRegistration.InstallServices 过于复杂,并且难以维护 (IMO)。 关注点分离 (SoC) 违规以及不正确地调用 BuildServiceProvider 会导致问题稍后。

在注册类型时使用工厂委托(delegate)中的提供程序,以便使用正确的 IServiceProvider 来解析服务依赖项。

//...

foreach (var interfaceType in interfaces.Where(x => x.Name.EndsWith("Client"))) {
    var currentInterfaceType = interfaceType;
    var implementations = asm.GetImplementationsOfInterface(interfaceType);
    implementations.ToList().ForEach(i => {
        services.AddScoped(currentInterfaceType, provider => {
            var clientFactory = provider.GetRequiredService<IHttpClientFactory>();
            var httpClient = clientFactory.CreateClient("api");
            return Activator.CreateInstance(i, httpClient);
        });
    });
}

//...

关于c# - 如何在 Blazor 服务器应用程序的服务注册中将访问 token 注入(inject)到 HttpClient?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/66325590/

相关文章:

java - 我可以将 SessionBean 注入(inject) Java EE AroundInvoke-Interceptor 吗?

.net-core - 如何在 .NET Core 控制台应用程序中为 HttpClient 启用日志记录?

c# - 将 C# 转换为 VB : New. .. 使用{只读属性}

c# - 系统范围持久存储?

c# - 如何在没有语法 11 错误的情况下在 vb.net 中的 SQL 查询中使用可变日期

c# - Apache Ignite.NET 服务中的依赖注入(inject)

c# - 为什么异步方法在这里只执行一次 HTTP 请求?

c# - C# MongoClient 是否可以在不首先序列化为 .NET 类型的情况下用于返回有效的 JSON?

c# - WPF XAML 数据触发器在以编程方式更改属性值 C# 后无法触发

symfony - 如何覆盖 yaml 中父服务的第一个参数?