Implement IDisposable on Views and ViewModels

This commit is contained in:
2025-09-01 14:20:06 +02:00
parent 90f937ccd2
commit 766cb885a5
4 changed files with 70 additions and 6 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
<Project>
<PropertyGroup>
<Version>0.2.9</Version>
<Version>0.3.0</Version>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
+13 -1
View File
@@ -1,8 +1,12 @@
<DynamicComponent Type="@this.CurrentViewType" Parameters="@this.CurrentParameters" />
<CascadingValue Value="this">
<DynamicComponent Type="@this.CurrentViewType" Parameters="@this.CurrentParameters" />
</CascadingValue>
@using Microsoft.Extensions.Logging
@using TrailBlazr.Models
@using TrailBlazr.Services
@using TrailBlazr.Views
@implements IDisposable
@code {
[Inject]
public required IViewManager ViewManager { get; init; }
@@ -41,4 +45,12 @@
this.Logger.LogError(ex, "Error changing view to {newView}", request.ViewType.Name);
}
}
public void Dispose()
{
if (this.ViewManager is not null)
{
this.ViewManager.ShowViewRequested -= this.UpdateCurrentView;
}
}
}
+23 -1
View File
@@ -3,10 +3,12 @@ using TrailBlazr.Views;
namespace TrailBlazr.ViewModels;
public abstract class ViewModelBase<TViewModel, TView> : INotifyPropertyChanged
public abstract class ViewModelBase<TViewModel, TView> : INotifyPropertyChanged, IDisposable
where TViewModel : ViewModelBase<TViewModel, TView>
where TView : ViewBase<TView, TViewModel>
{
private bool disposed = false;
public event PropertyChangedEventHandler? PropertyChanged;
protected TView? View
@@ -42,4 +44,24 @@ public abstract class ViewModelBase<TViewModel, TView> : INotifyPropertyChanged
public virtual ValueTask Initialize(CancellationToken cancellationToken) => ValueTask.CompletedTask;
public virtual ValueTask ParametersSet(TView view, CancellationToken cancellationToken) => ValueTask.CompletedTask;
protected virtual void Dispose(bool disposing)
{
if (!this.disposed)
{
if (disposing)
{
this.View = null;
this.PropertyChanged = null;
}
this.disposed = true;
}
}
public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
}
+33 -3
View File
@@ -1,18 +1,22 @@
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.Logging;
using TrailBlazr.Components;
using TrailBlazr.ViewModels;
namespace TrailBlazr.Views;
public abstract class ViewBase<TView, TViewModel> : ComponentBase
public abstract class ViewBase<TView, TViewModel> : ComponentBase, IDisposable
where TView : ViewBase<TView, TViewModel>
where TViewModel : ViewModelBase<TViewModel, TView>
{
public readonly static TimeSpan InitializationTimeout = TimeSpan.FromSeconds(5);
private bool disposed = false;
[Inject]
public TViewModel ViewModel { get; set; } = default!;
public required TViewModel ViewModel { get; set; }
[Inject]
protected ILogger<TView> Logger { get; set; } = default!;
public required ILogger<TView> Logger { get; set; }
[CascadingParameter]
public required ViewContainer ViewContainer { get; set; }
protected override async Task OnAfterRenderAsync(bool firstRender)
{
@@ -27,6 +31,11 @@ public abstract class ViewBase<TView, TViewModel> : ComponentBase
throw new InvalidOperationException($"View is not of type {typeof(TView).Name}. Actual type: {this.GetType().Name}");
}
if (this.ViewContainer is null)
{
throw new InvalidOperationException("ViewContainer is not set. Ensure that this view is placed within a ViewContainer component");
}
this.ViewModel.SetView(view);
using var initializationCts = new CancellationTokenSource(InitializationTimeout);
_ = this.ViewModel ?? throw new InvalidOperationException("Cannot initialize ViewModel. ViewModel is null");
@@ -61,5 +70,26 @@ public abstract class ViewBase<TView, TViewModel> : ComponentBase
{
await this.InvokeAsync(this.StateHasChanged);
}
protected virtual void Dispose(bool disposing)
{
if (!this.disposed)
{
if (disposing)
{
this.ViewModel = null!;
this.Logger = null!;
this.ViewContainer = null!;
}
this.disposed = true;
}
}
public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
}