Вопрос или проблема
У меня есть приложение 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 приложении требует внимательности и правильного подхода к управлению зависимостями. Каждая из предложенных схем имеет свои преимущества и недостатки, поэтому важно выбрать наиболее подходящий вариант, исходя из специфики вашего приложения. Используйте подход, который обеспечивает целостность данных и сохраняет ожидаемое поведение вашего приложения.