LuckyGeorge1975
LuckyGeorge1975

Reputation: 11

Instance of ViewModel is created every time, when using MediatR Publish and Splat in an Avalonia Project

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

Answers (1)

LuckyGeorge1975
LuckyGeorge1975

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

Related Questions