prism vs mvvm light for wpf

  1. I just moved a project from Prism to MvvmLight and it seems to work faster (very subjective).

  2. Both Prism and MvvmLight have Mediator realisation (IEventAggregator in Prism, IMessenger in MvvmLight). But IMessenger has more abilities (for instance, sending messages with tokens) compared to IEventAggregator and is much more convenient to use (see next item).

    MvvmLight also has a more powerful ViewModelBase class.

  3. Applications that use MvvmLight are much easier to test than those that use Prism. For instance, IMessenger is easier to mock than IEventAggregator.

PrismViewModel.cs

using System;
using Microsoft.Practices.Prism.Events;
using Microsoft.Practices.Prism.ViewModel;

// An ugly empty event class
public class StringEvent : CompositePresentationEvent<string> { }

public sealed class PrismViewModel : NotificationObject
{
    private readonly IEventAggregator _eventAggregator;

    private string _name;

    public PrismViewModel(IEventAggregator eventAggregator)
    {
        if (eventAggregator == null)
            throw new ArgumentNullException("eventAggregator");

        _eventAggregator = eventAggregator;
        _eventAggregator.GetEvent<StringEvent>().Subscribe(s => Name = s);
    }

    public string Name
    {
        get { return _name; }
        set
        {
            // boiler-plate code
            if (value == _name) 
                return;
            _name = value;
            RaisePropertyChanged(() => Name);
        }
    }

    public void SendMessage(string message)
    {
        _eventAggregator.GetEvent<StringEvent>().Publish(message);
    }
}

PrismViewModelTestCase.cs

using System;
using FluentAssertions;
using Microsoft.Practices.Prism.Events;
using NSubstitute;
using NUnit.Framework;

public class PrismViewModelTestCase
{
    private static PrismViewModel CreateViewModel(IEventAggregator eventAggregator = null)
    {
        // You can't return Substitute.For<IEventAggregator>()
        // because it returns null when PrismViewModel's constructor
        // invokes GetEvent<StringEvent>() method which leads to NullReferenceException
        return new PrismViewModel(eventAggregator ?? CreateEventAggregatorStub());
    }

    private static IEventAggregator CreateEventAggregatorStub()
    {
        var eventAggregatorStub = Substitute.For<IEventAggregator>();
        eventAggregatorStub.GetEvent<StringEvent>().Returns(Substitute.For<StringEvent>());
        return eventAggregatorStub;
    }

    [Test]
    public void Constructor_WithNonNullEventAggregator_ExpectedSubscribesToStringEvent()
    {
        // Arrange
        var stringEventMock = Substitute.For<StringEvent>();

        var eventAggregatorStub = Substitute.For<IEventAggregator>();
        eventAggregatorStub.GetEvent<StringEvent>().Returns(stringEventMock);

        // Act
        CreateViewModel(eventAggregatorStub);

        // Assert
        // With constrained isolation framework you can only mock virtual members
        // CompositePresentationEvent<TPayload> has only one virtual Subscribe overload with four parameters
        stringEventMock.Received()
                       .Subscribe(Arg.Any<Action<string>>(), Arg.Any<ThreadOption>(), Arg.Any<bool>(),
                                  Arg.Any<Predicate<string>>());
    }

    [Test]
    public void Name_ExpectedRaisesPropertyChanged()
    {
        var sut = CreateViewModel();
        sut.MonitorEvents();

        sut.Name = "any-value";

        sut.ShouldRaisePropertyChangeFor(vm => vm.Name);
    }

    [Test]
    public void SendMessage_ExpectedPublishesStringEventThroughEventAggregator()
    {
        // Arrange
        var stringEventMock = Substitute.For<StringEvent>();

        var eventAggregatorStub = Substitute.For<IEventAggregator>();
        eventAggregatorStub.GetEvent<StringEvent>().Returns(stringEventMock);

        var sut = CreateViewModel(eventAggregatorStub);
        const string expectedPayload = "any-string-payload";

        // Act
        sut.SendMessage(expectedPayload);

        // Assert
        stringEventMock.Received().Publish(expectedPayload);
    }
}

MvvmLightViewModel.cs

using System;
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Messaging;

public sealed class MvvmLightViewModel : ViewModelBase
{
    private string _name;

    public MvvmLightViewModel(IMessenger messenger)
    {
        if (messenger == null)
            throw new ArgumentNullException("messenger");

        // ViewModelBase already have field for IMessenger
        MessengerInstance = messenger; 
        MessengerInstance.Register<string>(this, s => Name = s);
    }

    public string Name
    {
        get { return _name; }
        set { Set(() => Name, ref _name, value); // Chic!  }
    }

    public void SendMessage(string message)
    {
        MessengerInstance.Send(message);
    }
}

MvvmLightViewModelTestCase.cs

using System;
using FluentAssertions;
using GalaSoft.MvvmLight.Messaging;
using NSubstitute;
using NUnit.Framework;

public class MvvmLightViewModelTestCase
{
    private static MvvmLightViewModel CreateViewModel(IMessenger messenger = null)
    {
        return new MvvmLightViewModel(messenger ?? Substitute.For<IMessenger>());
    }

    [Test]
    public void Constructor_WithNonNullMessenger_ExpectedRegistersToStringMessage()
    {
        var messengerStub = Substitute.For<IMessenger>();

        var sut = CreateViewModel(messengerStub);

        messengerStub.Received().Register(sut, Arg.Any<Action<string>>());
    }

    [Test]
    public void Name_ExpectedRaisesPropertyChanged()
    {
        var sut = CreateViewModel();
        sut.MonitorEvents();

        sut.Name = "any-value";

        sut.ShouldRaisePropertyChangeFor(vm => vm.Name);
    }

    [Test]
    public void SendMessage_ExpectedSendsStringMessageThroughMessenger()
    {
        var messengerMock = Substitute.For<IMessenger>();
        var sut = CreateViewModel(messengerMock);
        const string expectedMessage = "message";

        sut.SendMessage(expectedMessage);

        messengerMock.Received().Send(expectedMessage);
    }
}

Disadvantages of Prism:

  • it’s non fully open-source project (official Prism repository is read-only)
    • 2015-10-30: now it’s fully open-sourced: https://github.com/PrismLibrary/Prism
  • it no longer actively developed
    • 2015-10-30: new version of Prism: https://github.com/PrismLibrary/Prism
  • directly using of its classes leads to boiler-plate and less readable code

I think that any new project should be based on modern solutions and approaches.
IMHO, any modern MVVM-framework (like Catel, Caliburn.Micro, MvvmLight, ReactiveUI) is much better than Prism.

Leave a Comment