Сохранение состояния в приложении Blazor Server

Вопрос или проблема

У меня есть приложение Blazor server, в котором зарегистрирован сервис для поддержания состояния следующим образом:


    public class AppState
    {
        
        public bool IsAuthenticated { get; set; }
        public string UserId { get; set; }
    }

В program.cs

    builder.Services.AddScoped<AppState>();

В Index.razor в OnInitialized() я устанавливаю некоторое значение в AppState:


    @inject AppState AppState
    protected override void OnInitialized()
    {
      AppState.UserId = "logged in userid";
    }

Я внедряю этот сервис в компоненты, чтобы получать все необходимое из области видимости сервиса.

Однако, если я делаю это в DataService.cs (который тоже имеет область видимости) и создаю экземпляр ApplicationDbContext:


    public class DataService
    {
      private IServiceScopeFactory _scopeFactory;
      private AppState _appState;
      public DataService(IServicesScopeFactory scopeFactory, AppState appState)
      {
         _scopeFactory = scopeFactory;
         _appState = appState;
      } 
      
      public async Task<List<Patient>> GetAllPatientsAsync()
      {
        List<Patient> patients = new List<Patient>();
        using (var scope = _scopeFactory.CreateScope())
        {
            var context = 
            scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
            
            //доступ к ApplicationDbContext
        }
        return patients;
      }
    
        public class ApplicationDbContext : IdentityDbContext
        {
            public Guid _tenantId;
            AppState _state;
            public ApplicationDbContext(DbContextOptions<ApplicationDbContext> 
                 options, ITenantProvider _tenantProvider, AppState appState)
                : base(options)
            {
                _tenantId = _tenantProvider.GetTenantId();
                _state = appState; //похоже, что здесь предоставляется новый экземпляр AppState 
                                                           ...
            }

Похоже, что AppState переинициализируется (внутри вновь созданного ApplicationDbContext), и я не получаю значение userId, которое я установил в Index.razor. Мне кажется, что когда я использую ScopeFactory для создания области и вызываю ApplicationDbContext из этой области, создается новый экземпляр AppState с областью видимости.

Какое решение для этого? Мне нужно, чтобы оригинальный AppState сохранялся.

Ответ или решение

Вопрос о поддержании состояния в приложении Blazor Server — это важный аспект, который влияет на целостность и функциональность вашего приложения. Давайте подробно рассмотрим вашу ситуацию и предоставим рекомендации по правильному управлению состоянием.

Проблема

Вы создаете сервис AppState, который предназначен для хранения состояния пользователя, и регистрируете его как Scoped. Это означает, что для каждого запроса к приложению будет создана новая экземпляр этого сервиса. Когда вы работаете с DataService, который также является Scoped, и создаете новый экземпляр ApplicationDbContext через IServiceScopeFactory, вы сталкиваетесь с тем, что создается новый экземпляр AppState. Этот новый экземпляр не содержит данных, которые вы установили в компонентах, поэтому вы не видите ожидаемое поведение.

Причины проблемы

Когда вы вызываете CreateScope, вы создаете новый контейнер зависимостей, и все Scoped сервисы, включая AppState, будут заново инициализированы. Это происходит потому, что AppState не передается в новый скоуп, а создается новый экземпляр.

Решение

Вариант 1: Передача AppState в ApplicationDbContext

Одно из простых и эффективных решений — передавать AppState в ApplicationDbContext извне. Вам нужно изменить DataService, чтобы он принимал актуальный AppState из текущего контекста при вызове метода, где вы создаете скоуп.

public async Task<List<Patient>> GetAllPatientsAsync(AppState appState)
{
    List<Patient> patients = new List<Patient>();
    using (var scope = _scopeFactory.CreateScope())
    {
        var context = 
            scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
        // Передаем текущий AppState в ApplicationDbContext
        context.SetAppState(appState);

        // access the ApplicationDbContext
    }
    return patients;
}

Затем в ApplicationDbContext вы можете создать метод для установки AppState:

public void SetAppState(AppState appState)
{
    _state = appState;
}

Вариант 2: Избегание создания нового скоупа

Если вам не требуется отдельный скоуп, вы можете рассмотреть вариант использования AppState в том же контексте, что и ваш сервис данных, избегая необходимости обработки новых скоупов. Например, вы можете вызывать методы DataService из тех же компонентов, где инжектируется AppState.

Вариант 3: Использование Singleton

Если состояние должно быть глобально доступно, то вы можете рассмотреть регистрацию AppState как Singleton, однако следует учитывать, что при этом все клиенты будут разделять одно и то же состояние, что может привести к проблемам с параллельностью и состоянием данных.

builder.Services.AddSingleton<AppState>();

Заключение

Поддержание состояния в Blazor Server приложении требует внимательности и правильного подхода к управлению зависимостями. Каждая из предложенных схем имеет свои преимущества и недостатки, поэтому важно выбрать наиболее подходящий вариант, исходя из специфики вашего приложения. Используйте подход, который обеспечивает целостность данных и сохраняет ожидаемое поведение вашего приложения.

Оцените материал
Добавить комментарий

Капча загружается...