将作用域服务注入 DelegatingHandler 会引发 InvalidOperationException

Iva*_*ono 6 refit asp.net-core blazor blazor-server-side

我正在重构我的代码以使用Refit来调用 WebApi 服务。接口已设置,我还创建了一个委托处理程序:

public class AuthHandler : DelegatingHandler
{
    private readonly TokenProvider tokenProvider;
    private readonly ISessionStorageService sessionStorage;

    public AuthHandler (
        TokenProvider tokenProvider, 
        ISessionStorageService sessionStorage)
    {
        this.tokenProvider = tokenProvider ?? throw new ArgumentNullException(nameof(tokenProvider));
        this.sessionStorage = sessionStorage ?? throw new ArgumentNullException(nameof(sessionStorage));
    }

    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken ct)
    {
        var someToken = await sessionStorage.GetItemAsync<string>("sometoken");

        request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", tokenProvider.AccessToken);
        request.Headers.Add("someToken", someToken);

        return await base.SendAsync(request, ct).ConfigureAwait(false);
    }
}
Run Code Online (Sandbox Code Playgroud)

并在Startup.cs

services.AddBlazoredSessionStorage();
services.AddScoped<TokenProvider>();
services.AddScoped<AuthHandler>();

services.AddRefitClient<IApiService>().ConfigureHttpClient(options =>
{
    options.BaseAddress = new Uri(Configuration["Server:Url"]);
    options.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
}).AddHttpMessageHandler<AuthHandler>();
Run Code Online (Sandbox Code Playgroud)

我有一个剃刀组件,我想使用上面的服务,所以我注入了服务并执行了以下操作:

@code {
    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            var list = await myService.GetListAsync();
        }
    } 
}
Run Code Online (Sandbox Code Playgroud)

另外,在_Host.cshtml

<environment include="Staging,Production">
    <component render-mode="ServerPrerendered" type="typeof(App)" param-InitialState="tokens" />
</environment>
<environment include="Development">
    <component render-mode="Server" type="typeof(App)" param-InitialState="tokens"  />
</environment>
Run Code Online (Sandbox Code Playgroud)

但是我得到以下异常:

System.InvalidOperationException:此时无法发出 JavaScript 互操作调用。这是因为该组件是静态渲染的。启用预渲染后,只能在 OnAfterRenderAsync 生命周期方法期间执行 JavaScript 互操作调用。

因此,为了确定我是否有值,我在调用 api 之前添加了以下内容:

var someToken = await sessionStorage.GetItemAsync<string>("sometoken");
var accessToken = tokenProvider.AccessToken;
Run Code Online (Sandbox Code Playgroud)

我确实在两个变量中都有值。

那么为什么我无法从委托处理程序访问会话存储呢?为什么令牌提供程序实例已实例化,但属性全部为空(也在处理程序中)?

编辑

我只需要一个地方来保存我的代币。无论是令牌提供程序还是会话存储,只要它能在 blazor 页面/组件和其他服务中工作即可。

更新1

人们可以跳过 DI 并像这样创建服务:

var service = RestService.For<IMyService>(new HttpClient(new AuthHandler(tokenProvider, sessionStorage))
   {
        BaseAddress = new Uri(myUrl)
   }
);
Run Code Online (Sandbox Code Playgroud)

这将按预期工作。不过,使用 DI 会好得多。问题可能出在 Blazor 或 Refit 中。

小智 -1

我想我已经找到了解决问题的有效方法。

我没有依赖HttpClientFactoryRefit 使用的逻辑,而是创建了自己的 DI 逻辑。

以前的:

Uri apiBaseUri = dataAccessLayerConfiguration.API.BaseUrl;
foreach (Type repositoryType in repositoryTypes)
{
    services
        .AddRefitClient(repositoryType)
        .ConfigureHttpClient((httpClient) => httpClient.BaseAddress = apiBaseUri)
        .AddHttpMessageHandler<RequestHeaderHandler>();
}
Run Code Online (Sandbox Code Playgroud)

当前的:

foreach (Type repositoryType in repositoryTypes)
{
    services.AddTransient(
        repositoryType,
        (provider) =>
        {
            return
                RepositoryFactory.Create(
                    repositoryType,
                    configuration,
                    provider.GetRequiredService<ITokenCacheProvider>(),
                    () => provider.GetRequiredService<ICurrentUser>());
        });
}
Run Code Online (Sandbox Code Playgroud)

我使用的 RepositoryFactory 为每个 API 接口创建 HttpClient:

return
    RestService.For<TRepository>(
        new HttpClient(
            new RequestHeaderHandler(tokenCacheProvider, fetchCurrentUserDelegate)
            {
                InnerHandler = new HttpClientHandler()
            },
            true)
        {
            BaseAddress = configuration.DataAccessLayer.API.BaseUrl
        });
Run Code Online (Sandbox Code Playgroud)

我目前在该方法的布局代码中获取当前用户OnParametersSet()。我还没有完全满意,但目前对我来说已经足够了。但是,重要的是,在创建 HttpClient 时不要直接注入用户对象,而只注入一个委托(或 )Func<>,然后在需要时解析用户对象。

这样我就能够解决 Refit/HttpClientFactory 的范围问题,但仍然继续使用依赖注入。它可能不是适合每个人的百分百解决方案,但足以为您自己的项目找到正确的方向。