Reputation: 11
I have a small Avalonia Project and want to use MediatR for Notifications between ViewModels. To use MediatR i have to use the Splat.Microsoft.Extensions.DependencyInjection. The problem is, every time i use Mediator.Publish to notify the MainViewModel from the StartViewModel a new instance of the MainViewModel is created although it is registerd as singleton.
I think my error is, that the MediatR registration itself creates a new entry in the service collection for the MainViewModel, ignoring the previous registration. But how can i tell MediatR not to create a new instance but using the already registered one.
In the App class i set up DI as folows:
public override void OnFrameworkInitializationCompleted()
{
_host = Host
.CreateDefaultBuilder()
.ConfigureServices((_, services) =>
{
services.UseMicrosoftDependencyResolver();
RegisterViewsAndViewModels(services);
var resolver = Locator.CurrentMutable;
resolver.InitializeSplat();
resolver.InitializeReactiveUI();
services.AddMediatR(cfg => {
cfg.RegisterServicesFromAssembly(typeof(App).Assembly);
});
})
.Build();
Container = _host.Services;
Container.UseMicrosoftDependencyResolver();
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
desktop.MainWindow = Locator.Current.GetService<MainWindow>();
}
else if (ApplicationLifetime is ISingleViewApplicationLifetime singleViewPlatform)
{
singleViewPlatform.MainView = Locator.Current.GetService<MainView>();
}
base.OnFrameworkInitializationCompleted();
}
The Views and ViewModels are registered in MS fashion:
private static void RegisterViewsAndViewModels(IServiceCollection services)
{
services.AddTransient<MainWindow>();
services.AddTransient<MainView>();
services.AddSingleton<MainViewModel>();
}
In the MainWindow i resolve the MainViewModel once in the constructor:
DataContext = Locator.Current.GetService<MainViewModel>();
The MainViewModel itself implements INotificationHandler<ChangeViewModelEvent>
:
public ViewModelBase CurrentViewModel
{
get => _currentViewModel;
private set => this.RaiseAndSetIfChanged(ref _currentViewModel, value);
}
public Task Handle(ChangeViewModelEvent @event, CancellationToken cancellationToken)
{
Dispatcher.UIThread.Post(() => CurrentViewModel = @event.ViewModelName switch
{
nameof(NewViewModel) => Locator.Current.GetService<NewViewModel>() ??
throw new ServiceNotFoundException(nameof(NewViewModel)),
_ => CurrentViewModel
});
return Task.CompletedTask;
}
And in the StartViewModel i publish the notification:
private readonly IMediator _mediator;
public StartViewModel(IMediator mediator)
{
_mediator = mediator;
}
public bool StartCommand()
{
_mediator.Publish(new ChangeViewModelEvent(nameof(NewViewModel)));
return true;
}
I already tried to change the registration to transient, use IRequest instead of INotification, changed the order of ViewModel and MediatR registrations.
Upvotes: 1
Views: 306
Reputation: 11
Ok, so after doing a lot of reading it finally comes down to seperate the view model from the INotificationHandler<ChangeViewModelEvent>
and implement a new Handler like this:
public class ChangeViewModelEventHandler : INotificationHandler<ChangeViewModelEvent>
{
public Task Handle(ChangeViewModelEvent notification, CancellationToken cancellationToken)
{
var mainViewModel = Locator.Current.GetService<MainViewModel>() ??
throw new ServiceNotFoundException(nameof(MainViewModel));
return mainViewModel.Handle(notification, cancellationToken);
}
}
Thats to complex for my needs so i ended in implementing my own, lightweight mediator.
Upvotes: 0