I just moved a project from Prism to MvvmLight and it seems to work faster (very subjective).
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.
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)
it no longer actively developed
- 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.
Regions
. It hasEventAggregator
forViewModel
toViewModel
comms. You could use Blend Behaviors (System.Windows.Interactivity, Microsoft.Expression.Interactions) for Interactions. Unit Testing depends on how well you writeViewModel
unit test friendly rather than on the MVVM framework you use. So, If you do wantRegion
support in your app then go with PRISM otherwise MVVM-Light. I do not know if there is a way to achieve Regions with MVVM-Light as well. – Spill