Как обмениваться информацией между концентратором SignalR и BackgroundService в ASP.NET Core MVC 2.2 - PullRequest
1 голос
/ 02 апреля 2019

У меня архитектурная проблема, для которой я просто не могу найти подходящего решения.В моем веб-приложении на основе ASP.NET Core MVC 2.2 я хочу получить данные из защищенного JWT API и опубликовать их на подключенных клиентах с помощью SignalR.Кроме того, данные должны выбираться только в том случае, если хотя бы один клиент действительно подключен к концентратору SignalR.Моя проблема: я не могу найти способ отменить Task.Delay внутри цикла while, когда подключен дополнительный клиент.Чтобы пояснить, позвольте мне показать вам, что я придумал.

Прежде всего, вот класс клиента 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() цикла while в тот момент, когда подключается дополнительный пользователь, поэтому вновь подключенный клиент получает данные немедленно и ему не нужно ждать, пока задача Delay() закончится?Я думаю, это должно быть помещено в OnConnectedAsync(), но вызов службы оттуда, кажется, не является хорошим решением.

  2. Является ли эта архитектура даже хорошей?Если нет, то как бы вы реализовали такой сценарий?

  3. Есть ли лучший способ сохранить количество подключенных в данный момент пользователей SignalR?Я читал, что статическое свойство в Hub может быть проблематичным, если задействовано более одного сервера SignalR (но это не так для меня).

  4. Имеет IHostedService /BackgroundService здесь вообще есть смысл?Так как сервисы, добавляемые с помощью AddHostedService(), теперь являются временными, не нарушает ли цикл while внутри метода ExecuteAsync() цель этого подхода?

  5. Что было бы лучшим местомхранить токен, такой как токены доступа JWT, чтобы временные экземпляры могли получить к нему доступ, если он действителен, и обновить его по истечении срока действия?

Я также читал о , добавляющем ссылку наконкретный IHostedService, но это, кажется, просто неправильно.Также эта дискуссия о Github дала мне понять, что должен быть лучший способ создать связь между SignalR и постоянно работающими сервисами.

Любая помощь будет принята с благодарностью.

...