Создание объекта (ViewModel) один раз для каждой схемы Blazor - PullRequest
0 голосов
/ 26 сентября 2019

В настоящее время я пишу небольшой веб-интерфейс Blazor.Я пытаюсь принять / реализовать шаблон MVVM.Для этого я создал два компонента базовых классов.Один, который просто обрабатывает методы жизненного цикла Blazor (добавляет некоторую обработку исключений), а другой - инициализацию ViewModel на основе выполнения этих методов жизненного цикла.Компонент также реализует интерфейс IDisposable, который автоматически вызывается Blazor, когда компонент становится невидимым.

Вот фрагмент кода из моего класса WebComponentBase и класса ViewModelAwareComponent, чтобы примерно дать представление о шаблоне:


    public abstract class WebFpComponentBase : ComponentBase, IDisposable, IHtmlStyles
    {

         private const string DEFAULT_DIM_VALUE = "auto";

         [Inject]
         protected IExceptionUiHandler<ErrorRedirectViewModel> ExceptionUiHandler { get; set; }

         //fields and parameters omitted for brevity

         #region Blazor Component LifeCycle
         /// <summary>
         /// OnInitialized is called after OnInitialized, when the component has received all initial parameters. Place any asynchronous operations here,
         /// which require the component to re-render.
         /// </summary>
         /// <returns></returns>
         protected override async Task OnInitializedAsync()
         {
              try
              {
                  await base.OnInitializedAsync();
                  _logger.Info($"{nameof(OnInitializedAsync)} - method invoked in component of type ${this.GetType().FullName}");
                  await OnInitializedInternalAsync();
                  _logger.Info($"{nameof(OnInitializedAsync)} - method finished in component of type ${this.GetType().FullName}");
              } catch(Exception ex)
              {
                 //Exception, if any happend, is forwared using the IExceptionUiHandler in the AfterRenderAsync() method
                  _forwardException = ex;
                  _logger.Error($"{nameof(OnInitializedAsync)}: Catching and forwarding exception of type {_forwardException.GetType().FullName}");
              }
         }

         protected abstract Task OnInitializedInternalAsync();
         //other methods of the class omitted for brevity
    }

Далее мой ViewModelAwareComponent, который имеет свойство, содержащее ViewModelи автоматически запускает инициализацию и деинициализацию ViewModel (закрытие любых подключений к службам, сброс любых значений и т. д.) путем реализации внутренних абстрактных методов [BlazorLifecycleMethod].

public abstract class ViewModelAwareComponent<TViewModel> : WebFpComponentBase where TViewModel : BaseViewModel
{
    private Logger _logger = LogManager.GetCurrentClassLogger();

    [Parameter]
    public virtual TViewModel ViewModel { get; set; }

    protected override async Task OnInitializedInternalAsync()
    {
        await ViewModel.InitializeAsync();
        ViewModel.PropertyChanged += this.FireComponentStateHasChanged;
    }

    public override async void Dispose()
    {
        base.Dispose();
        await ViewModel.DeactivateAsync();
    }

    protected virtual async void FireComponentStateHasChanged(object sender, PropertyChangedEventArgs e)
    {
        _logger.Trace($"FireComponentStateHasChanged: property {e.PropertyName} has changed!");
        await InvokeAsync(this.StateHasChanged);
    }

    protected override async Task OnParametersSetAsyncInternal()
    {
        if (ViewModel == null)
        {
            throw new ArgumentNullException($"{nameof(ViewModel)}", "Parameter must be supplied!");
        }
    }
}

Тип BaseViewModel реализует INotifyPropertyChanged только в обычном виде,У меня есть «MainViewModel», который должен быть создан только один раз для всего соединения (Blazor Circuit).Поэтому я добавил его через services.AddScoped() в Startup.cs.Поскольку он не привязан к какому-либо конкретному компоненту, я вставляю его в мой MainLayout.razor, который является макетом для каждого компонента Razor, который я написал.

Макет действительно реализует protected override async Task OnInitializedAsync(), как указано ниже:

protected override async Task OnInitializedAsync()
{
    MainViewModel.PropertyChanged += (obj, args) => InvokeAsync(StateHasChanged);
    await MainViewModel.InitializeAsync();
    //some other stuff happening afterwards
}

Моя проблема сейчас заключается в том, что инициализация и выполняется дважды каждый раз, когда я запускаю приложение, а не только один раз для каждого соединения.То же самое верно и для деинициализации, потому что Dispose () компонента вызывается также дважды.

При отладке я заметил, что OnInitializedAsync не вызывается повторно при переключении между двумя моими существующими страницами(маршрутизируемые компоненты).Он вызывается только дважды при запуске.

Возможно, у вас есть какие-то предложения для такого поведения?Есть ли лучший способ добиться желаемого поведения для MainViewModel?

С наилучшими пожеланиями,

tilt

1 Ответ

0 голосов
/ 27 сентября 2019

Ответ на комментарий Дани Эрреры:

Может, речь идет о предварительной визуализации?Смотрите мой ответ здесь: stackoverflow.com/a/57696602/842935

Отключил предварительный рендеринг, изменив @(await Html.RenderComponentAsync(RenderMode.ServerPrerendered)) на @(await Html.RenderComponentAsync(RenderMode.Server)) Теперь OnInitializedAsync () MainLayout.razor толькозвонил один раз.

...