c# - 如何在不调用 .Result 的情况下使用作为任务注入的类中的函数?
问题描述
我试图在这里实现 lolsharp 给出的答案:gRPC-Web Channel Authentication with Blazor Webassembly via Dependency Injection
他们注入了一个 GrpcChannel 类型的任务,其中 GrpcChannel 是一个类:
@inject Task<GrpcChannel> Channel
这是因为通道是使用异步注册的。
我尝试在此类中使用一个函数,如下所示:
GetAllResponse getAllResponse = await Channel.Result.GetAllAsync(new Google.Protobuf.WellKnownTypes.Empty());
但这会失败并出现错误“无法在此运行时等待监视器”,因为单线程 Web 程序集不支持该机制(如Blazor 启动错误中所述:System.Threading.SynchronizationLockException: 无法在此运行时等待监视器)。
如果通道是异步注册的,如何在 Razor 页面中使用 Channel 类中的函数?
编辑:根据要求,这是 Program.cs 中声明的依赖项
builder.Services.AddSingleton(async services =>
{
Console.WriteLine("In addsingleton");
var config = services.GetRequiredService<IConfiguration>();
#if DEBUG
var baseUri = "http://localhost:8999/";
#else
var baseUri = "[mysite]";
#endif
Console.WriteLine("About to set new httpclient");
var httpClient = new HttpClient(new
GrpcWebHandler(GrpcWebMode.GrpcWeb, new HttpClientHandler()));
var scopedFactory =
services.GetRequiredService<IServiceScopeFactory>();
using (var scope = scopedFactory.CreateScope())
{
var authenticationService =
scope.ServiceProvider.GetRequiredService<IAccessTokenProvider>();
var tokenResult = await
authenticationService.RequestAccessToken();
if (tokenResult.TryGetToken(out var token))
{
var credentials = CallCredentials.FromInterceptor((context, metadata) =>
{
if (!string.IsNullOrEmpty(token.Value))
{
metadata.Add("Authorization", $"Bearer
{token.Value}");
}
return Task.CompletedTask;
});
var channel = GrpcChannel.ForAddress(baseUri, new
GrpcChannelOptions { HttpClient = httpClient, Credentials =
ChannelCredentials.Create(new SslCredentials(), credentials) });
var client = new
GrpcServices.GrpcServicesClient(channel);
return client;
}
}
解决方案
为了避免不得不调用.Result
,Channel
你将不得不等待它。例如:
var channel = await Channel;
GetAllResponse getAllResponse =
await channel.GetAllAsync(new Google.Protobuf.WellKnownTypes.Empty());
旁注:正如我在评论中提到的,您通常应该希望在对象解析期间避免做任何涉及 I/O 的事情,因为它会使对象解析变得脆弱且无法测试。相反,您应该能够自信地组合您的对象图,正如Mark Seemann所说的。您可以通过将任务隐藏在抽象后面来推迟任务的创建来做到这一点。例如:
public interface IGrpcChannelProvider
{
Task<GrpcChannel> Channel { get; }
}
这允许您将所有注册代码移动到以下实现中IGrpcChannelProvider
:
public sealed class GrpcChannelProvider : IGrpcChannelProvider, IDisposable
{
private readonly IConfiguration config;
private readonly IAccessTokenProvider authenticationService;
private readonly Lazy<Task<GrpcChannel>> channel;
public GrpcChannelProvider(
IConfiguration config, IAccessTokenProvider authenticationService)
{
this.config = config;
this.authenticationService = authenticationService;
this.channel = new Lazy<Task<GrpcChannel>>(this.CreateChannel);
}
public Task<GrpcChannel> Channel => this.channel.Value;
public void Dispose()
{
if (this.channel.IsValueCreated) this.channel.Value.Dispose();
}
// This is your original code
private async Task<GrpcChannel> CreateChannel()
{
#if DEBUG
var baseUri = "http://localhost:8999/";
#else
var baseUri = "[mysite]";
#endif
var httpClient = new HttpClient(new GrpcWebHandler(GrpcWebMode.GrpcWeb, new HttpClientHandler()));
var tokenResult = await this.authenticationService.RequestAccessToken();
if (tokenResult.TryGetToken(out var token))
{
var credentials = CallCredentials.FromInterceptor((context, metadata) =>
{
if (!string.IsNullOrEmpty(token.Value))
{
metadata.Add("Authorization", $"Bearer {token.Value}");
}
return Task.CompletedTask;
});
var channel = GrpcChannel.ForAddress(baseUri,
new GrpcChannelOptions
{
HttpClient = httpClient,
Credentials = ChannelCredentials.Create(new SslCredentials(), credentials)
});
var client = new GrpcServices.GrpcServicesClient(channel);
return client;
}
}
}
该组件可以注册如下:
services.AddSingleton<IGrpcChannelProvider, GrpcChannelProvider>();
或者 - 如果在应用程序域期间缓存通道导致安全问题 - 将组件注册为范围:
services.AddScoped<IGrpcChannelProvider, GrpcChannelProvider>();
在视图中,注入这个IGrpcChannelProvider
而不是通道:
@inject IGrpcChannelProvider Provider
并按如下方式使用它:
var channel = await Provider.Channel;
GetAllResponse getAllResponse =
await channel.GetAllAsync(new Google.Protobuf.WellKnownTypes.Empty());
更进一步,您甚至可能希望阻止对 Razor 页面内的服务进行任何调用,而是依赖于预先填充的模型:
@model AllResponseModel
@model AllResponseModel
GetAllResponse getAllResponse = Model.AllResponses;
现在您可以改为注入IGrpcChannelProvider
Razor AllResponseModel
。