如何在ASP.NET Core MVC 2.2中的SignalR集线器和BackgroundService之间共享信息

时间:2019-04-02 08:24:53

标签: c# asp.net-core architecture background-service asp.net-core-signalr

我遇到了体系结构问题,只是找不到适合的解决方案。在基于ASP.NET Core MVC 2.2的Web应用程序中,我想从JWT安全的API中提取数据并使用SignalR将其发布到连接的客户端。此外,仅当至少一个客户端实际连接到SignalR集线器时,才应提取数据。我的问题:当连接其他客户端时,我找不到在while循环中取消Task.Delay的方法。为澄清起见,让我向您展示我到目前为止的想法。

首先,这是API客户端类:

public class DataApiClient : IDataApiClient {

    private readonly HttpClient httpClient;
    private readonly string dataApiDataUrl;

    public DataApiClient(HttpClient httpClient, IOptionsMonitor<DataSettings> dataSettings) {
        this.httpClient = httpClient;
        dataApiUrl = dataSettings.CurrentValue.dataApiUrl;
    }

    public async Task<DataOverview> GetData(string accessToken) {

        DataOverview dataOverview = new DataOverview();

        try {
            httpClient.DefaultRequestHeaders.Accept.Clear();
            // more httpClient setup

            Task<Stream> streamTask = httpClient.GetStreamAsync(dataApiUrl);
            dataOverview = serializer.ReadObject(await streamTask) as DataOverview;

        } catch(Exception e) {
            Debug.WriteLine(e.Message);
        }
        return dataOverview;
    }
}

SignalR集线器:

public interface IDataClient {
    Task ReceiveData(DataOverview dataOverview);
}

public class DataHub : Hub<IDataClient> {

    private volatile static int UserCount = 0;

    public static bool UsersConnected() {
        return UserCount > 0;
    }

    public override Task OnConnectedAsync() {
        Interlocked.Increment(ref UserCount);
        return base.OnConnectedAsync();
    }

    public override Task OnDisconnectedAsync(Exception exception) {
        Interlocked.Decrement(ref UserCount);
        return base.OnDisconnectedAsync(exception);
    }
}

还有BackgroundService完成工作:

public class DataService : BackgroundService {

    private readonly IHubContext<DataHub, IDataClient> hubContext;
    private readonly IDataApiClient dataApiClient;
    private readonly IAccessTokenGenerator accessTokenGenerator;
    private AccessToken accessToken;

    public DataService(IHubContext<DataHub, IDataClient> hubContext, IDataApiClient dataApiClient, IAccessTokenGenerator accessTokenGenerator) {
        this.hubContext = hubContext;
        this.dataApiClient = dataApiClient;
        this.accessTokenGenerator = accessTokenGenerator;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken) {

        while (!stoppingToken.IsCancellationRequested) {

            if (DataHub.UsersConnected()) {

                if (NewAccessTokenNeeded()) {
                    accessToken = accessTokenGenerator.GetAccessToken();
                }

                DataOverview dataOverview = dataApiClient.GetData(accessToken.Token).Result;
                dataOverview.LastUpdated = DateTime.Now.ToString();

                await hubContext.Clients.All.ReceiveData(dataOverview);
            }
            // how to cancel this delay, as soon as an additional user connects to the hub?
            await Task.Delay(60000);
        }
    }

    private bool NewAccessTokenNeeded() {
        return accessToken == null || accessToken.ExpireDate < DateTime.UtcNow;
    }
}

所以我的问题是:

  1. 我的主要问题:如何在其他用户连接时取消Task.Delay()内的ExecuteAsync()循环,因此新连接的客户端可以立即获取数据,而不必等到Delay()任务结束吗?我想这应该放在OnConnectedAsync()中,但是从那里调用服务似乎不是一个好的解决方案。

  2. 这个体系结构还不错吗?如果没有,您将如何实现这种情况?

  3. 是否有更好的方法来统计当前连接的SignalR用户?我读到Hub中的静态属性可能会出现问题,如果涉及多个SignalR服务器(但这对我来说不是这种情况)。

  4. IHostedService / BackgroundService在这里是否还有意义?由于使用AddHostedService()添加的服务现在是临时的,因此ExecuteAsync()方法内的while循环是否会违反这种方法的目的?

  5. 存储令牌(例如JWT访问令牌)的最佳位置是什么,以便临时实例可以访问它(如果有效)并在过期时对其进行更新?

我也读过injecting a reference to a specific IHostedService,但这似乎是错误的。 this discussion on Github也让我感到必须有一种更好的方法来设计SignalR和连续运行的服务之间的通信。

任何帮助将不胜感激。

0 个答案:

没有答案