MVP In Silverlight/WPF

MVP In Silverlight/WPF: Conclusions

18 commentsWritten on August 8th, 2010 by
Categories: MVP In Silverlight/WPF

Note: This post is part of a series. You can find the introduction and overview of the series here.

We've (finally) reached the end of this series. As i mentioned before, this series was a response to a lot of the criticism that i received for my own critique on MVVM. Obviously, if you're going to dish it out, you have to be able to take it as well. And i did. I honestly can't even care about criticism on my work as long as i believe in what i do. But what did tick me off a lot by those reactions was the lack of willingness to at least try and think about what i was saying. A lot of people (particularly in the .NET world) are completely unwilling to invest just a little bit of time and effort in reading something and trying to understand it. They just want to glance over a post until they see a little bit of code and they'll often base their opinions solely on that. Unfortunately for them, they usually miss the appropriate context due to their laziness and they either won't get it, or won't understand the difference with what they already think they know.

People wanted code, so i showed them code. I also provided all the context in the world to go along with that code, and i can only hope that people took (or will take) the time to let it all soak in. If, after that, they still disagree with what i'm saying, then at least their opinion will be based on something more than just a quick glance over a post.

Other than that, i don't really have a lot more to say about the whole thing. If you're not convinced by the merits of the approach i've shown by now, nothing i can say in this post will change your mind about it either. And again, there's absolutely nothing wrong with disagreeing with this, as long as you show willingness to learn and put in some effort. That's all i can ask for.

MVP In Silverlight/WPF: Automated Tests

2 commentsWritten on August 7th, 2010 by
Categories: MVP In Silverlight/WPF

Note: This post is part of a series. You can find the introduction and overview of the series here.

The MVP approach that i've shown in this series makes it easy to write clean code where responsibilities are properly separated. One of the biggest benefits of that is that you get easy testability because of it as well. Now, the MVVM proponents will tell you that MVVM leads to code that is highly testable as well and they're right. However, i think that because of the cleaner separation in MVP, the automated tests you can write for your code is often cleaner, simpler, requiring less set up and often resulting in more focused tests. I'm going to show a few tests of the sample project accompanying this series to show you what i mean.

First of all, let's start off with our BindingModels. Every single thing that your BindingModel does should be tested. That means testing whether the PropertyChanged event is always raised as expected, testing the validation you do, and testing that your population and mutation methods work properly.

For instance, the UserGroupDetailBindingModel from the sample has a Name property. Since the View binds to that property, we need to make sure that the PropertyChanged event is raised when its value is set. We've also defined some validation for the Name property so we need to test that our BindingModel raises the ErrorsChanged event and that the correct validation message is made available through the GetErrors method. Now, i'm a big fan of using base classes for test fixtures which contain utility assert methods which reduce code noise in your tests as much as possible. It makes it possible to write tests like these:

        [TestMethod]
        public void SettingNameRaisesPropertyChangedEvent()
        {
            AssertThatPropertyChangesIsTriggeredCorrectly(m => m.Name, "some name");
        }
 
        [TestMethod]
        public void SettingNameToInvalidValueCausesValidationError()
        {
            BindingModel.Name = null;
            AssertHasErrorMessageForProperty(m => m.Name, "name is a required field");
        }

The BindingModelFixture base class that is used in the sample automatically creates the correct BindingModel and exposes it through the BindingModel property. There are various utility methods to assert that the PropertyChanged event is correctly raised when you want it to, as well as utility methods to assert that the validation is working correctly. These utility methods use the BindingModel property, which is why we only need to pass an Expression to the utility methods in the 2 tests above to indicate which property we're testing.

In the first test, the utility method will assign the given value (in this case a string) to the property passed in with the Expression and will then make sure that the PropertyChanged event for that property was indeed raised. In the second test, the utility method will retrieve the errors of the current BindingModel and make sure that the expected error message is among them. As you can see, both tests are very short and very simple. And they most definitely provide value. It doesn't really get any easier than that folks.

It's also important to test the methods you have on your model to populate it with data, or to change the data that is present, like these 2 tests for instance:

        [TestMethod]
        public void Populate_SetsSelectedParentUserGroupIfCurrentGroupHasParent()
        {
            var currentUserGroup = new UserGroupDto {Id = Guid.NewGuid(), ParentId = Guid.NewGuid()};
            var suitableParents = new[] {new UserGroupDto {Id = currentUserGroup.ParentId.Value}};
            BindingModel.Populate(suitableParents, currentUserGroup);
 
            Assert.AreEqual(currentUserGroup.ParentId.Value, BindingModel.SelectedParentUserGroup.Id);
        }
 
        [TestMethod]
        public void RevertToOriginalValues_SetsIdAndNameToOriginalValues()
        {
            var userGroup = new UserGroupDto {Id = Guid.NewGuid(), Name = "some name"};
            BindingModel.Populate(new UserGroupDto[0], userGroup);
            BindingModel.Id = Guid.NewGuid();
            BindingModel.Name = "some other name";
 
            BindingModel.RevertToOriginalValues();
 
            Assert.AreEqual(userGroup.Id, BindingModel.Id);
            Assert.AreEqual(userGroup.Name, BindingModel.Name);
        }

Again, both test are short, very simple and focused. They don't have to worry about anything that isn't relevant to what we're actually trying to test.

I only showed 4 tests for BindingModels here, but the sample of this series has 30 tests for 3 BindingModels and i encourage you to check them out.

Obviously, you want to cover the logic in your Presenters with valuable and maintainable tests as well. Typical things that you need to test in the presenter are things like:

  • making sure the presenter makes the correct service layer calls at the right time
  • making sure that the presenter doesn't inadvertently make unwanted service layer calls at the wrong time
  • that it handles received events from the Event Aggregator correctly
  • that it publishes the correct events through the Event Aggregator at the correct time
  • that it doesn't publish events through the Event Aggregator when it shouldn't do so
  • that it interacts with the BindingModel correctly
  • that it interacts with the View correctly

The only thing that makes testing the Presenter a little bit more difficult than typical automated tests, is the fact that the presenter always calls the service layer asynchronously. Asynchronous calls are typically somewhat more complex to test, but luckily for me i'm using Agatha's RequestDispatchers to call the service layer, and i can use Agatha's RequestDispatcherStub class in my tests to make the whole thing a lot easier.

Let's go to some examples. The UserGroupDetailPresenter needs to retrieve the selected UserGroup from the service layer (to make sure we're working with the latest data), and it also needs to retrieve a list of suitable parent UserGroups. When the data is returned from the service, the model needs to be populated. Take a look at the following 3 tests:

        [TestMethod]
        public void RetrievesUserGroupDetails()
        {
            var userGroupId = Guid.NewGuid();
            Presenter.Handle(new UserGroupSelectedEvent(userGroupId));
            Assert.AreEqual(userGroupId, RequestDispatcherStub.GetRequest<GetUserGroupRequest>().UserGroupId);
        }
 
        [TestMethod]
        public void RetrievesSuitableParentUserGroups()
        {
            var userGroupId = Guid.NewGuid();
            Presenter.Handle(new UserGroupSelectedEvent(userGroupId));
            Assert.AreEqual(userGroupId, RequestDispatcherStub.GetRequest<GetSuitableParentUserGroupsRequest>().UserGroupId.Value);
        }
 

        [TestMethod]
        public void ResponsesReceived_PopulatesModel()
        {
            var userGroup = new UserGroupDto { Id = Guid.NewGuid() };
            var suitableParents = new[] { new UserGroupDto { Id = Guid.NewGuid() } };
 
            Presenter.Handle(new UserGroupSelectedEvent(Guid.NewGuid()));
            RequestDispatcherStub.SetResponsesToReturn(new GetUserGroupResponse { UserGroup = userGroup },
                                                       new GetSuitableParentUserGroupsResponse { SuitableParentUserGroups = suitableParents });
            RequestDispatcherStub.ReturnResponses();
 
            Assert.AreEqual(userGroup.Id, Presenter.BindingModel.Id);
            Assert.AreEqual(suitableParents[0].Id, Presenter.BindingModel.SuitableParentUserGroups[1].Id);
        }

Once again, these tests are very short (except for the last one, which is still pretty short considering what it's actually testing) and highly focused. We're not doing anything here that isn't relevant to what we're actually testing.

We can also easily test whether the presenter interacts with the view as expected, like these 2 tests illustrate:

        [TestMethod]
        public void ResponsesReceived_DoesNotTellViewToPreventModificationIfUserHasPermission()
        {
            Presenter.Handle(new UserGroupSelectedEvent(Guid.NewGuid()));
            RequestDispatcherStub.SetResponsesToReturn(new GetUserGroupResponse(),
                                                       new GetSuitableParentUserGroupsResponse {SuitableParentUserGroups = new UserGroupDto[0]},
                                                       new CheckPermissionsResponse
                                                       {
                                                               AuthorizationResults = new Dictionary<Guid, bool> {{Permissions.DeleteUserGroup, true}, {Permissions.EditUserGroup, true}}
                                                       });
            RequestDispatcherStub.ReturnResponses();
 
            ViewMock.Verify(v => v.PreventModification(), Times.Never());
        }
 
        [TestMethod]
        public void ResponsesReceived_ShowsTheView()
        {
            var userGroup = new UserGroupDto { Id = Guid.NewGuid() };
            var suitableParents = new[] { new UserGroupDto { Id = Guid.NewGuid() } };
 
            Presenter.Handle(new UserGroupSelectedEvent(Guid.NewGuid()));
            RequestDispatcherStub.SetResponsesToReturn(new GetUserGroupResponse { UserGroup = userGroup },
                                                       new GetSuitableParentUserGroupsResponse { SuitableParentUserGroups = suitableParents });
            RequestDispatcherStub.ReturnResponses();
 
            ViewMock.Verify(v => v.Show());
        }

For those wondering: i'm using the (excellent) Moq library to mock the view in these tests.

I also said that we can easily test that the presenter doesn't make unwanted service layer calls, which you can see in this test:

        [TestMethod]
        public void DoesNotProceedIfModelIsInvalid()
        {
            Presenter.BindingModel.Name = null;
            Presenter.PersistChanges();
            Assert.IsFalse(RequestDispatcherStub.HasRequest<SaveUserGroupRequest>());
        }

Testing whether the presenter publishes the correct events is pretty easy as well:

        [TestMethod]
        public void PublishesUserGroupDeletedEventWhenResponseIsReturned()
        {
            Presenter.BindingModel.Id = Guid.NewGuid();
            RequestDispatcherStub.SetResponsesToReturn(new DeleteUserGroupResponse());
 
            Presenter.Delete();
            RequestDispatcherStub.ReturnResponses();
 
            Assert.AreEqual(Presenter.BindingModel.Id.Value, EventAggregatorStub.GetPublishedEvents<UserGroupDeletedEvent>()[0].UserGroupId);
        }

As you can see, we can really test a lot of code if we want to (you do want to test a lot of code don't you?). And it really is pretty easy to do so. The sample project of this series contains 47 tests for 2 presenters. I again encourage you to check out those tests to see how easily you can cover a lot of functionality.

MVP In Silverlight/WPF: Implementing The Details UserControl

5 commentsWritten on August 5th, 2010 by
Categories: MVP In Silverlight/WPF

Note: This post is part of a series. You can find the introduction and overview of the series here.

The second (and last) UserControl of this series and its accompanying sample looks like this:

Nothing fancy (it never is when i do the UI) and pretty much a typical edit screen, though there are very few fields to edit obviously. The DropDown shows the suitable parent User Groups for this User Group. These suitable parents are retrieved from the Service Layer and the 'logic' behind them is very simple: it can't be the selected User Group, and it can't be any User Group that is currently below it in the hierarchy. Other than that, anything goes.

The 3 buttons should be self-explanatory as well. If you press the Cancel button, the TextBox and the DropDown should be reset to their initial values, which are either empty values in case the user is creating a new User Group, or the original values of the currently selected User Group in our previous UserControl's TreeView. If you press the Delete button, the currently selected User Group needs to be deleted. The Delete button can obviously only be shown in case we're editing an existing User Group, and never if we're creating a new one since that wouldn't make sense. The Save button persists the changes, which means either updating the currently selected User Group or inserting the newly created one. The Delete button can't be shown when the user does not have the required permission to delete a User Group, and the Save button can't be shown if the user doesn't have the required permission to edit a User Group. However, if the user does have permission to create a new User Group (which is a separate permission from editing an existing one) then the Save button must be visible.

Alright, let's get started. I'm going to use a different style in this post than i used in the last one though. The last post was more of a step-by-step walk through of writing the actual code, but that leads to very long posts (and takes up a lot more of my time to write it), and i'd like to keep this one a bit shorter. So i'm just going to show the entire code of each class with some comments on it.

As usual i like to start off with the BindingModel. What exactly are we going to put into it? We'll obviously need some stuff from the User Group: its ID (though we won't display that), name and the parent User Group (if there is one). We'll also need a list of suitable parents. Remember that we also need to support the Cancel button, so we need to store the original values. This is what i came up with:

    public class UserGroupDetailBindingModel : BindingModel<UserGroupDetailBindingModel>
    {
        private string originalName;
        private Guid? originalId;
        private UserGroupDto originalSelectedParent;
 
        public ObservableCollection<UserGroupDto> SuitableParentUserGroups { get; private set; }
 
        private UserGroupDto selectedParentUserGroup;
 
        public UserGroupDto SelectedParentUserGroup
        {
            get { return selectedParentUserGroup; }
            set
            {
                selectedParentUserGroup = value;
                NotifyPropertyChanged(m => m.SelectedParentUserGroup);
            }
        }
 
        private Guid? id;
 
        public Guid? Id
        {
            get { return id; }
            set
            {
                id = value;
                NotifyPropertyChanged(m => m.Id);
                NotifyPropertyChanged(m => m.IsExistingUserGroup);
            }
        }
 
        public bool IsExistingUserGroup { get { return id.HasValue && id.Value != Guid.Empty; } }
 
        private string name;
 
        public string Name
        {
            get { return name; }
            set
            {
                name = value;
                NotifyPropertyChanged(m => m.Name);
            }
        }
 
        public UserGroupDetailBindingModel()
        {
            SuitableParentUserGroups = new ObservableCollection<UserGroupDto>();
            Clear();
            AddValidationFor(m => m.Name)
                .When(m => string.IsNullOrWhiteSpace(m.name))
                .WithMessage("name is a required field");
        }
 
        public void Clear()
        {
            SuitableParentUserGroups.Clear();
            SuitableParentUserGroups.Add(new UserGroupDto { Id = Guid.Empty, Name = "None" });
            SelectedParentUserGroup = SuitableParentUserGroups[0];
 
            originalId = Id = null;
            originalName = Name = null;
            originalSelectedParent = SelectedParentUserGroup;
        }
 
        public void Populate(IEnumerable<UserGroupDto> suitableParentUserGroups, UserGroupDto currentUserGroup = null)
        {
            foreach (var suitableParentUserGroup in suitableParentUserGroups)
            {
                SuitableParentUserGroups.Add(suitableParentUserGroup);
            }
 
            if (currentUserGroup != null)
            {
                originalName = Name = currentUserGroup.Name;
                originalId = Id = currentUserGroup.Id;
                originalSelectedParent = SelectedParentUserGroup;
 
                if (currentUserGroup.ParentId.HasValue)
                {
                    originalSelectedParent = SelectedParentUserGroup = SuitableParentUserGroups.First(u => u.Id == currentUserGroup.ParentId);
                }
            }
        }
 
        public void RevertToOriginalValues()
        {
            Name = originalName;
            Id = originalId;
            SelectedParentUserGroup = originalSelectedParent;
        }
    }

Looking back on this now, there are a couple of things that i don't really use. For one, the ID property raises the PropertyChanged event even though there's nothing that binds to it. I also have an IsExistingUserGroup property but i don't use it anywhere. Unfortunately, i only noticed this after releasing the sample so i'm not just gonna go back and change it now. Probably just a brainfart on my part. Anyways, the only interesting parts to note about this BindingModel is the simple validation that we define on the Name property (and which was actually already discussed in the post about the infrastructure bits) and the fact that we add a default 'empty' User Group to the SuitableParentUserGroups collection. Other than that, everything here should be very clear and straightforward by now so let's just move on to the presenter already:

    public class UserGroupDetailPresenter : Presenter<IUserGroupDetailsView, UserGroupDetailBindingModel>,
        IListenTo<UserGroupSelectedEvent>, IListenTo<UserGroupNeedsToBeCreatedEvent>
    {
        public UserGroupDetailPresenter(IUserGroupDetailsView view, IEventAggregator eventAggregator, IAsyncRequestDispatcherFactory requestDispatcherFactory)
            : base(view, eventAggregator, requestDispatcherFactory) {}
 
        public override void Initialize()
        {
            View.Hide();
            EventAggregator.Subscribe(this);
        }
 
        public void Handle(UserGroupNeedsToBeCreatedEvent receivedEvent)
        {
            View.PreventDeletion();
            LoadData();
        }
 
        public void Handle(UserGroupSelectedEvent receivedEvent)
        {
            View.EnableEverything();
            LoadData(receivedEvent.SelectedUserGroupId);
        }
 
        private void LoadData(Guid? userGroupId = null)
        {
            BindingModel.Clear();
 
            var requestDispatcher = RequestDispatcherFactory.CreateAsyncRequestDispatcher();
 
            if (userGroupId.HasValue)
            {
                requestDispatcher.Add(new CheckPermissionsRequest {PermissionsToCheck = new[] {Permissions.DeleteUserGroup, Permissions.EditUserGroup}});
                requestDispatcher.Add(new GetUserGroupRequest { UserGroupId = userGroupId.Value });
            }
            requestDispatcher.Add(new GetSuitableParentUserGroupsRequest {UserGroupId = userGroupId});
            requestDispatcher.ProcessRequests(ResponsesReceived, PublishRemoteException);
        }
 
        private void ResponsesReceived(ReceivedResponses receivedResponses)
        {
            if (receivedResponses.HasResponse<GetUserGroupResponse>())
            {
                BindingModel.Populate(receivedResponses.Get<GetSuitableParentUserGroupsResponse>().SuitableParentUserGroups,
                    receivedResponses.Get<GetUserGroupResponse>().UserGroup);
            }
            else
            {
                BindingModel.Populate(receivedResponses.Get<GetSuitableParentUserGroupsResponse>().SuitableParentUserGroups);
            }
 
            if (receivedResponses.HasResponse<CheckPermissionsResponse>())
            {
                var response = receivedResponses.Get<CheckPermissionsResponse>();
                if (!response.AuthorizationResults[Permissions.DeleteUserGroup]) View.PreventDeletion();
                if (!response.AuthorizationResults[Permissions.EditUserGroup]) View.PreventModification();
            }
 
            View.Show();
        }
 
        public void PersistChanges()
        {
            BindingModel.ValidateAll();
            if (BindingModel.HasErrors) return;
 
            var dispatcher = RequestDispatcherFactory.CreateAsyncRequestDispatcher();
            dispatcher.Add(new SaveUserGroupRequest
            {
                Id = BindingModel.Id,
                Name = BindingModel.Name,
                ParentId = BindingModel.SelectedParentUserGroup.Id != Guid.Empty ? BindingModel.SelectedParentUserGroup.Id : (Guid?)null
            });
            dispatcher.ProcessRequests(PersistChanges_ResponseReceived, PublishRemoteException);
        }
 
        private void PersistChanges_ResponseReceived(ReceivedResponses responses)
        {
            var response = responses.Get<SaveUserGroupResponse>();
 
            if (response.NewUserGroupId.HasValue)
            {
                BindingModel.Id = response.NewUserGroupId.Value;
            }
 
            EventAggregator.Publish(new UserGroupChangedEvent
            {
                Id = BindingModel.Id.Value,
                Name = BindingModel.Name,
                ParentId = BindingModel.SelectedParentUserGroup.Id != Guid.Empty ? BindingModel.SelectedParentUserGroup.Id : (Guid?)null,
                IsNew = response.NewUserGroupId.HasValue
            });
        }
 
        public void Delete()
        {
            var dispatcher = RequestDispatcherFactory.CreateAsyncRequestDispatcher();
            dispatcher.Add(new DeleteUserGroupRequest { UserGroupId = BindingModel.Id.Value });
            dispatcher.ProcessRequests(DeleteUserGroup_ResponseReceived, PublishRemoteException);
        }
 
        private void DeleteUserGroup_ResponseReceived(ReceivedResponses responses)
        {
            EventAggregator.Publish(new UserGroupDeletedEvent(BindingModel.Id.Value));
        }
 
        public void Cancel()
        {
            BindingModel.RevertToOriginalValues();
        }
    }

As you can see, this presenter doesn't retrieve any data in its Initialize method. In fact, it just hides the View and subscribes with the Event Aggregator. This UserControl only needs to be visible once the user has selected a User Group in the Overview UserControl, so the View remains hidden until we actually need to show something.

In the Handle(UserGroupNeedsToBeCreatedEvent) method, we first instruct the View to prevent the user from pressing the Delete button (since that wouldn't make sense during the creation of a new User Group) and we call the LoadData method. The Handle(UserGroupSelectedEvent) method first instructs the view to enable everything (all controls basically) and then calls the LoadData method with the ID of the currently selected User Group. If the userGroupId parameter is passed into the LoadData method, we'll not only retrieve the suitable parents, but also the details of the current User Group, as well as check whether our user has permission to delete and/or edit a User Group. And obviously, being the responsible programmers that we are, we send all 3 requests in the same roundtrip since there is no reason whatsoever not to do so.

In the ResponsesReceived method, we populate the model based on the data we've received from the Service Layer. We also tell the View to prevent deletion of the current User Group if the user doesn't have permisson to do so, and we also tell the View to prevent modification if necessary. Finally, we tell the View to show itself to the user.

The PersistChanges method is the one that will be called by the View when the Save button is clicked. If the BindingModel has validation errors, we simply return from the method. Since we use the INotifyDataErrorInfo interface in our BindingModel (as discussed in the Infrastructure Bits post), the View will automatically show the validation message anyway and we don't need to do anything. We could have also bound the Visibility property of the Save button to the HasErrors property of the BindingModel to prevent it from being visible as long as there are validation problems, but then we'd also need to keep the permissions into account. You could do it in various ways, and i just didn't go through the extra effort of actually doing so since this is after all just a silly sample. Anyways, if there are no validation errors, we send a request to the Service Layer to save the User Group's data.

In the PersistChanges_ResponsesReceived method, we update the Id property of the BindingModel if necessary, and we publish a UserGroupChangedEvent. As you've seen in the last post, that event will be handled by the Overview UserControl so it can update its TreeView. As you can see, the Delete method is pretty similar, so there's no need to explain it. And finally, the Cancel method simply calls the RevertToOriginalValues method on the BindingModel.

Now that we have our BindingModel and our Presenter, we can start working on our View. The XAML looks like this (again, i suck at XAML so this is probaby far from good XAML... if there is such a thing, that is):

<MVP:View x:Class="SilverlightMVP.Client.Views.UserGroupDetail"
   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   xmlns:MVP="clr-namespace:SilverlightMVP.Client.Infrastructure.MVP" >
 
    <Grid x:Name="LayoutRoot" Background="White" MinHeight="75" MaxHeight="75" MinWidth="455" >
 
        <Grid.ColumnDefinitions>
            <ColumnDefinition />
            <ColumnDefinition />
            <ColumnDefinition />
        </Grid.ColumnDefinitions>
 
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition />
            <RowDefinition />
        </Grid.RowDefinitions>
 
        <TextBlock Text="Name" Grid.Column="0" Grid.Row="0" />
        <TextBox x:Name="NameTextBox" Text="{Binding Path=Name, Mode=TwoWay, ValidatesOnExceptions=True, NotifyOnValidationError=True}"
                Grid.Column="1" Grid.Row="0" Grid.ColumnSpan="2" />
 
        <TextBlock Text="Parent" Grid.Column="0" Grid.Row="1" />
        <ComboBox x:Name="SuitableParentUserGroupsComboBox" ItemsSource="{Binding Path=SuitableParentUserGroups}" MinWidth="150"
                 DisplayMemberPath="Name" SelectedItem="{Binding Path=SelectedParentUserGroup, Mode=TwoWay}"
                 Grid.Column="1" Grid.Row="1" Grid.ColumnSpan="2" />
 
        <Button x:Name="DeleteButton" Content="Delete" Click="DeleteButton_Click" Grid.Column="0" Grid.Row="2" />
        <Button x:Name="CancelButton" Content="Cancel" Click="CancelButton_Click" Grid.Column="1" Grid.Row="2" />
        <Button x:Name="SaveButton" Content="Save" Click="SaveButton_Click" Grid.Column="2" Grid.Row="2" />
 
    </Grid>
</MVP:View>

And the View's code would be this:

    public interface IUserGroupDetailsView : IView
    {
        void PreventDeletion();
        void PreventModification();
        void EnableEverything();
    }
 
    public partial class UserGroupDetail : IUserGroupDetailsView
    {
        private readonly UserGroupDetailPresenter presenter;
 
        public UserGroupDetail()
        {
            InitializeComponent();
            presenter = CreateAndInitializePresenter<UserGroupDetailPresenter>();
        }
 
        public void EnableEverything()
        {
            DeleteButton.Visibility = Visibility.Visible;
            CancelButton.Visibility = Visibility.Visible;
            SaveButton.Visibility = Visibility.Visible;
            NameTextBox.IsEnabled = true;
            SuitableParentUserGroupsComboBox.IsEnabled = true;
        }
 
        public void PreventDeletion()
        {
            DeleteButton.Visibility = Visibility.Collapsed;
        }
 
        public void PreventModification()
        {
            NameTextBox.IsEnabled = false;
            SuitableParentUserGroupsComboBox.IsEnabled = false;
            CancelButton.Visibility = Visibility.Collapsed;
            SaveButton.Visibility = Visibility.Collapsed;
        }
 
        private void DeleteButton_Click(object sender, RoutedEventArgs e)
        {
            presenter.Delete();
        }
 
        private void CancelButton_Click(object sender, RoutedEventArgs e)
        {
            presenter.Cancel();
        }
 
        private void SaveButton_Click(object sender, RoutedEventArgs e)
        {
            presenter.PersistChanges();
        }
    }

And that's it.

I apologize to those of you who prefer the style of the previous post, but i'm sort of behind schedule and won't be able to write anything for the next 4 days, so i'm trying to get ahead enough of the posting schedule ;) . Though i hope you'll agree that the walk-through style of the previous post wasn't necessary anymore after going through a full implementation once.

Anyways, in the next post of the series, we'll look into the automated tests of both the BindingModel and the Presenters.

MVP In Silverlight/WPF: Implementing The Overview UserControl

7 commentsWritten on August 4th, 2010 by
Categories: MVP In Silverlight/WPF

Note: This post is part of a series. You can find the introduction and overview of the series here.

The first UserControl that we're going to implement looks like this:

There's a TreeView which shows a hierarcy of User Groups and there's a Button to create a new one. When this UserControl is first loaded, it needs to retrieve the User Group hierarchy and show it in the TreeView. When a user selects a User Group, an event should be published to notify anyone else who might be interested in the selection of a User Group (in our case, our Details UserControl which i'll cover in the next post). When the Button is pushed, another event is published to notify anyone who might be listening that we need a new User Group to be created. Again, this event will be handled by our Details UserControl.

So what exactly does this UserControl need to do? After all, showing some data and publishing a few events isn't really all that complex, right? Well, it also has to listen to some other events and it needs to update the data it's showing accordingly. First of all, if a new User Group is created, it will receive an event so it can add the new User Group to its TreeView. If a User Group is modified, it will also receive an event so it can deal with that. In the case of a modification, it might be as simple as updating the User Group's name, but it might also require removing the User Group from its current parent in the TreeView and attach it to anther parent. It might also become a root User Group. And finally, if a User Group is deleted, it will also receive an event so it can remove the User Group from the TreeView.

Now, some of you are probably thinking: why not just retrieve the User Group hierarchy again on every change and bind to that? Well, that would be the easiest way out, but that's not necessarily the best option. You already have all the information you need to update your TreeView, so there's no need to bother both the server to execute the request, and the user who will be waiting for the request to complete. You might be thinking "what's the big deal? a single service call doesn't take long enough to be noticed". And you might be right. A single service call will hardly be noticeable. Especially not while you're developing and testing your software on your (overpowered) development workstation. But resort to that approach too frequently, and all those 'single service calls' that all of your users will be executing will start to add up. Also, keep in mind that you're not developing a typical web application. You are not required to work stateless, so you're not required to reload everything you need all the time. In fact, it would be wise to take advantage of the state that your client can hold since it enables you to reduce server load, and to improve overall responsiveness (and thus, perceived performance) of the client. So we skip the lazy approach and we update the TreeView with the data that we already have.

Oh and obviously, we need to worry about permissions as well. So we need to make sure that a user can't create a new User Group if the user doesn't actually have the required permission to do that.

Right, let's get started on this, shall we?

First of all, we need to look at what kind of data we're going to get back from the Service Layer. The response to our GetAllUserGroupsRequest looks like this:

    public class GetAllUserGroupsResponse : Response
    {
        public UserGroupDto[] UserGroups { get; set; }
    }

(If you're unfamiliar with this kind of Request/Response Service Layer implementation, you can catch up here)

And the UserGroupDto class looks like this:

    public class UserGroupDto
    {
        public Guid Id { get; set; }
        public string Name { get; set; }
        public Guid? ParentId { get; set; }
    }

So basically, the data we're getting back from the Service Layer is a flattened (as in: non-hierarchical) list of UserGroupDto's. And that makes sense, since we can't expect our Service Layer to know that we want to visualize this in a TreeView... after all, the same Request could be used in other situations where a hierarchical representation isn't required. That does mean that we have some work to do though. We need to convert that flattened list of data in a hierarchical model that we can bind to our TreeView. So we could begin with the following simple HierarchicalUserGroupBindingModel:

    public class HierarchicalUserGroupBindingModel : BindingModel<HierarchicalUserGroupBindingModel>
    {
        public HierarchicalUserGroupBindingModel()
        {
            Children = new ObservableCollection<HierarchicalUserGroupBindingModel>();
        }
 
        public Guid Id { get; set; }
 
        private string name;
 
        public string Name
        {
            get { return name; }
            set
            {
                name = value;
                NotifyPropertyChanged(m => m.Name);
            }
        }
 
        public ObservableCollection<HierarchicalUserGroupBindingModel> Children { get; private set; }
    }

Nothing special here... just something simple which we can use to show a UserGroup with its children. Perfect for displaying a hierarchy, right? We can have multiple root User Groups though, so we need something that will contain these HierarchicalUserGroupBindingModel instances. We can start off with something like this:

    public class UserGroupsBindingModel : BindingModel<UserGroupsBindingModel>
    {
        public UserGroupsBindingModel()
        {
            UserGroups = new ObservableCollection<HierarchicalUserGroupBindingModel>();
        }
 
        public ObservableCollection<HierarchicalUserGroupBindingModel> UserGroups { get; private set; }
    }

This structure is sufficient for holding the data that our TreeView can bind to, but we're going to need to add some code to deal with all the changes that might occur. First, we need to be able to transform the flattened list of UserGroupDto's that we receive from the Service Layer to this hierarchical model that we'll be using here. So we add the following method to the UserGroupsBindingModel:

        public void PopulateFrom(IEnumerable<UserGroupDto> dtos)
        {
            var dictionary = dtos.ToDictionary(dto => dto.Id, dto => new HierarchicalUserGroupBindingModel { Id = dto.Id, Name = dto.Name });
 
            foreach (var userGroup in dictionary.Values)
            {
                var parentId = dtos.First(d => d.Id == userGroup.Id).ParentId;
 
                if (parentId.HasValue)
                {
                    dictionary[parentId.Value].Children.Add(userGroup);
                }
            }
 
            var rootIds = dtos.Where(d => d.ParentId == null).Select(d => d.Id);
            dictionary.Values.Where(u => rootIds.Contains(u.Id)).ToList().ForEach(u => UserGroups.Add(u));
        }

(not your typical way to transform a flattened list into a hierarchy, but i figured: why not go for something different for a change?)

Alright... we can already transform the list of UserGroupDtos that we'll receive from the Service Layer into a hierarchical structure that we can bind our TreeView too. But there are a few more things that we need to be able to support for this hierarchical structure. For starters, we need to be able to add a new User Group to this structure, so we add the following method to the UserGroupsBindingModel:

        public HierarchicalUserGroupBindingModel AddUserGroup(Guid id, string name, Guid? parentId = null)
        {
            var newUserGroup = new HierarchicalUserGroupBindingModel { Id = id, Name = name };
 
            if (!parentId.HasValue)
            {
                UserGroups.Add(newUserGroup);
            }
            else
            {
                FindGroupById(parentId.Value, UserGroups).Children.Add(newUserGroup);
            }
 
            return newUserGroup;
        }
 
        private static HierarchicalUserGroupBindingModel FindGroupById(Guid id, IEnumerable<HierarchicalUserGroupBindingModel> usergroups)
        {
            foreach (var usergroup in usergroups)
            {
                if (usergroup.Id == id) return usergroup;
                var childGroup = FindGroupById(id, usergroup.Children);
                if (childGroup != null) return childGroup;
            }
 
            return null;
        }

A User Group can't just be added, it can be removed too. So we need to add some more code to support that:

        public void RemoveUserGroup(Guid id)
        {
            if (UserGroups.Any(u => u.Id == id))
            {
                UserGroups.Remove(UserGroups.First(u => u.Id == id));
            }
            else
            {
                var parent = FindParentOfChildWithId(id, UserGroups);
                parent.Children.Remove(parent.Children.First(u => u.Id == id));
            }
        }
 
        private static HierarchicalUserGroupBindingModel FindParentOfChildWithId(Guid id, IEnumerable<HierarchicalUserGroupBindingModel> usergroups)
        {
            foreach (var usergroup in usergroups)
            {
                if (usergroup.Children.Any(u => u.Id == id)) return usergroup;
                var childGroup = FindParentOfChildWithId(id, usergroup.Children);
                if (childGroup != null) return childGroup;
            }
 
            return null;
        }

Finally, we also need to support changes to existing User Groups, so once again we add a new method to the UserGroupsBindingModel:

        public void UpdateUserGroup(Guid id, string name, Guid? parentId)
        {
            var group = FindGroupById(id, UserGroups);
            group.Name = name;
 
            var parent = FindParentOfChildWithId(id, UserGroups);
 
            if (parent == null)
            {
                if (parentId.HasValue)
                {
                    UserGroups.Remove(group);
                    FindGroupById(parentId.Value, UserGroups).Children.Add(group);
                }
            }
            else
            {
                if (parentId.HasValue && parent.Id != parentId.Value)
                {
                    parent.Children.Remove(group);
                    FindGroupById(parentId.Value, UserGroups).Children.Add(group);
                }
                else if (!parentId.HasValue)
                {
                    parent.Children.Remove(group);
                    UserGroups.Add(group);
                }
            }
        }

We already have quite a bit of code huh? But if you think about it, we already have everything we need to correctly show and update the data in our TreeView. And that is by far the hardest part of this UserControl. These BindingModels have a specific purpose and they only contain code to serve that purpose. Also, none of the methods that you can call on these 2 BindingModels can cause unexpected side effects (like making Service Layer calls for instance). The code we've written so far is very cohesive, and there is very little coupling to speak of.

Now we can start working on our Presenter. We first add a new class which looks like this:

    public class UserGroupsPresenter : Presenter<IUserGroupsView, UserGroupsBindingModel>
    {
        public UserGroupsPresenter(IUserGroupsView view, IAsyncRequestDispatcherFactory requestDispatcherFactory, IEventAggregator eventAggregator)
            : base(view, eventAggregator, requestDispatcherFactory) {}
    }

Notice that one of the generic type parameters that we pass to the base Presenter class is the IUserGroupsView interface. We don't have that interface yet so we'll need to create it first:

    public interface IUserGroupsView : IView
    {
    }

Now we can write the code we need to make sure that our UserGroupsPresenter will retrieve the User Groups from the Service Layer, and populate the UserGroupsBindingModel so the TreeView in our View could bind to it and display it. Let's add the following Initialize method to our Presenter:

        public override void Initialize()
        {
            var requestDispatcher = RequestDispatcherFactory.CreateAsyncRequestDispatcher();
            requestDispatcher.Add(new CheckPermissionsRequest { PermissionsToCheck = new[] { Permissions.CreateUserGroup } });
            requestDispatcher.Add(new GetAllUserGroupsRequest());
            requestDispatcher.ProcessRequests(ResponsesReceived, PublishRemoteException);
        }

We use Agatha's IAsyncRequestDispatcherFactory to create an IAsyncRequestDispatcher that we can use to send our requests to the Service Layer. Notice that we send 2 requests: one to retrieve the User Groups, and another one to see whether our user has permission to create a new User Group. Naturally, we make use of Agatha's service call batching capabilities because as responsible developers, we don't want to make more (expensive) remote calls than we need to.

Once we've received the responses from the Service Layer, the ResponsesReceived method will be called. If something went wrong, the PublishRemoteException method of the Presenter base class will be called instead. The ResponsesReceived method looks like this:

        private void ResponsesReceived(ReceivedResponses receivedResponses)
        {
            if (receivedResponses.HasResponse<GetAllUserGroupsResponse>())
            {
                BindingModel.PopulateFrom(receivedResponses.Get<GetAllUserGroupsResponse>().UserGroups);
                View.ExpandTreeView();
            }
 
            if (receivedResponses.HasResponse<CheckPermissionsResponse>() &&
                !receivedResponses.Get<CheckPermissionsResponse>().AuthorizationResults[Permissions.CreateUserGroup])
            {
                View.HideAddNewButton();
            }
        }

We populate the UserGroupsBindingModel with the data that we've gotten back in the GetAllUserGroupsResponse and then we tell the View to expand its TreeView. We also tell the View to hide the AddNewButton if the user doesn't have the required permission to create new User Groups. Obviously, we need to add these 2 method declarations to the IUserGroupsView:

    public interface IUserGroupsView : IView
    {
        void ExpandTreeView();
        void HideAddNewButton();
    }

Allow me to expand on the notion of a Presenter telling a View to do something. In many MVVM implementations, interaction from the ViewModel to the View is discouraged. Many people even seem to go out of their way to avoid any direct interaction from the ViewModel to the View and try to make everything work through data binding. While you can do that, there's no reason you really need to. There's nothing wrong a little bit of code in the View, as long as that code is only concerned with true View concerns. In this case, expanding a TreeView (which you can't do automatically in Silverlight... that's right, not even in the year 2010) or hiding a button seem like reasonable things to put in the View's code. We'll get to the actual implementations of those methods later on.

At this point, we've got everything we need to retrieve the data we need to show, and to actually show it. We're still not done though. We still need to publish some events when a user selects a User Group, or when the user clicks on the AddNewButton:

        public void DealWithSelectedUserGroup(HierarchicalUserGroupBindingModel selectedUserGroup)
        {
            EventAggregator.Publish(new UserGroupSelectedEvent(selectedUserGroup.Id));
        }
 
        public void PrepareUserGroupCreation()
        {
            EventAggregator.Publish(new UserGroupNeedsToBeCreatedEvent());
        }

Pretty straightforward, no? We basically just publish an event and whatever else is listening for those events needs to (and will, as you'll see in the next post) react to it.

But we also still need to handle some events as well... more specifically, we need to handle the situation where a User Group is deleted, and where a User Group is changed. There are 2 events that we need to handle:

    public class UserGroupChangedEvent : Event
    {
        public Guid Id { get; set; }
        public string Name { get; set; }
        public Guid? ParentId { get; set; }
        public bool IsNew { get; set; }
    }
 
    public class UserGroupDeletedEvent : Event
    {
        public UserGroupDeletedEvent(Guid userGroupId)
        {
            UserGroupId = userGroupId;
        }
 
        public Guid UserGroupId { get; private set; }
    }

The UserGroupChangedEvent is used for both modified User Groups, or newly created ones. I know, it'd be cleaner to have separate events for both situations but that would make my code in the Details UserControl uglier. So i chose to go with one event for both situations.

Before we can handle these events, we need to modify the declaration of our UserGroupsPresenter class:

    public class UserGroupsPresenter : Presenter<IUserGroupsView, UserGroupsBindingModel>,
        IListenTo<UserGroupChangedEvent>, IListenTo<UserGroupDeletedEvent>

Notice that we now explicitly declare that we are listening to these 2 events. Before we will be notified of these events, we still need to subscribe with the Event Aggregator though. So you'll need to add the following line of code to either the Initialize method, or the constructor:

            EventAggregator.Subscribe(this);

And now we can finally add the 2 event handlers:

        public void Handle(UserGroupChangedEvent receivedEvent)
        {
            if (receivedEvent.IsNew)
            {
                View.SelectItemInTreeView(BindingModel.AddUserGroup(receivedEvent.Id, receivedEvent.Name, receivedEvent.ParentId));
            }
            else
            {
                BindingModel.UpdateUserGroup(receivedEvent.Id, receivedEvent.Name, receivedEvent.ParentId);
            }
        }
 
        public void Handle(UserGroupDeletedEvent receivedEvent)
        {
            BindingModel.RemoveUserGroup(receivedEvent.UserGroupId);
        }

Again, pretty straightforward stuff. You'll notice that we've introduced a new method to the IUserGroupsView interface:

    public interface IUserGroupsView : IView
    {
        void ExpandTreeView();
        void HideAddNewButton();
        void SelectItemInTreeView(HierarchicalUserGroupBindingModel userGroupModel);
    }

And with that, our UserGroupsPresenter is done. All we need to do now, is the View. Lets start with the XAML. Gotta warn you though, i'm not good at XAML. I truly dislike it as a 'language' and i really hate having to edit XAML in Visual Studio which just seems much slower than it needs to be. So, keep that in mind if you spot XAML that isn't quite up to standard ;)

<MVP:View x:Class="SilverlightMVP.Client.Views.UserGroups"
       xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
       xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
       xmlns:Controls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls"
       xmlns:Windows="clr-namespace:System.Windows;assembly=System.Windows.Controls"
       xmlns:MVP="clr-namespace:SilverlightMVP.Client.Infrastructure.MVP">
 
    <Grid Background="White">
 
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
 
        <Controls:TreeView x:Name="UserGroupsTreeView" ItemsSource="{Binding UserGroups}"
                          SelectedItemChanged="UserGroupsTreeView_SelectedItemChanged" Grid.Row="0" >
            <Controls:TreeView.ItemTemplate>
                <Windows:HierarchicalDataTemplate ItemsSource="{Binding Children}">
                    <TextBlock Text="{Binding Path=Name}" />
                </Windows:HierarchicalDataTemplate>
            </Controls:TreeView.ItemTemplate>
        </Controls:TreeView>
 
        <Button x:Name="AddNewButton" Content="Create new usergroup" Click="AddNewButton_Click"
               Margin="5" Grid.Row="1" />
 
    </Grid>
</MVP:View>

Nothing special here. The only thing that might surprise people here is that i'm using regular, old-school event handlers to deal with user events. I don't really see the benefit in using the command pattern that so many MVVM users advocate. In a lot of MVVM implementations, those commands simply delegate to methods on the ViewModel, essentially turning them in glorified method calls with the only benefit (though i'd argue it's not actually a benefit) that they are defined in XAML and that you don't need to put those method calls in the code-behind. Again, there is no real problem with a little bit of code in the View and avoiding it at all costs is simply not necessary. In other MVVM implementations, they contain actual logic but i prefer to have that stuff in the Presenter, especially since that logic typically requires state which you already have in the Presenter anyway.

The code-behind of this View looks like this:

    public interface IUserGroupsView : IView
    {
        void ExpandTreeView();
        void HideAddNewButton();
        void SelectItemInTreeView(HierarchicalUserGroupBindingModel userGroupModel);
    }
 
    public partial class UserGroups : IUserGroupsView
    {
        private readonly UserGroupsPresenter presenter;
 
        public UserGroups()
        {
            InitializeComponent();
            presenter = CreateAndInitializePresenter<UserGroupsPresenter>();
        }
 
        public void SelectItemInTreeView(HierarchicalUserGroupBindingModel userGroupModel)
        {
            UserGroupsTreeView.SelectItem(userGroupModel);
        }
 
        public void ExpandTreeView()
        {
            for (int i = 0; i < UserGroupsTreeView.Items.Count; i++)
            {
                ExpandAllTreeViewItems((TreeViewItem)UserGroupsTreeView.ItemContainerGenerator.ContainerFromIndex(i));
            }
        }
 
        private void ExpandAllTreeViewItems(TreeViewItem treeViewItem)
        {
            if (!treeViewItem.IsExpanded)
            {
                treeViewItem.IsExpanded = true;
                treeViewItem.Dispatcher.BeginInvoke(() => ExpandAllTreeViewItems(treeViewItem));
            }
            else
            {
                for (int i = 0; i < treeViewItem.Items.Count; i++)
                {
                    var child = (TreeViewItem)treeViewItem.ItemContainerGenerator.ContainerFromIndex(i);
                    ExpandAllTreeViewItems(child);
                }
            }
        }
 
        public void HideAddNewButton()
        {
            AddNewButton.Visibility = Visibility.Collapsed;
        }
 
        private void UserGroupsTreeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
        {
            presenter.DealWithSelectedUserGroup((HierarchicalUserGroupBindingModel)e.NewValue);
        }
 
        private void AddNewButton_Click(object sender, RoutedEventArgs e)
        {
            presenter.PrepareUserGroupCreation();
        }
    }

As you can see, apart from writing code to expand all items in the TreeView (again, i can't for the life of me understand why something like this isn't available out of the box in a UI framework but hey, i guess that's just me) there's nothing special going on here. In fact, it's quite simple. As is the rest of the code we wrote.

MVP In Silverlight/WPF: The Sample

20 commentsWritten on August 3rd, 2010 by
Categories: MVP In Silverlight/WPF

Note: This post is part of a series. You can find the introduction and overview of the series here.

Coming up with a good sample project for this series wasn't easy. It had to be small enough to be easy to comprehend and look into, yet it also has to make it clear why i prefer the MVP approach over the MVVM approach, which isn't easy to do when you have a very simple sample. There has to be business logic, and it has to be encapsulated by a Service Layer. But i obviously wanted to avoid having to use a database and go through everything to get all of that working while still being easy to download and play around with it. The Service Layer has been implemented very quickly, and is not representative of a real Service Layer. It doesn't use a database, it holds its data statically in memory (and doesn't even care about thread-safety of this data either) and i didn't even write tests for any of it. It's just a simple Service Layer, implemented with a Request/Response Service Layer. It accepts Requests and returns Responses with DTO's (not entities obviously) to the client. That's it.

The client code has been written entirely using Test Driven Development. Apart from the Views, everything is tested and the tests are obviously also included in the downloadable Visual Studio solution. Some tests were written after a piece of code was written, but most of the tests were written before the actual code was written. I hope you go through the tests to see just how much UI logic you can actually cover quite easily. I also hope you'll notice that the large majority of tests is very short and focused, which would be harder to achieve when using MVVM. If you have questions regarding the implementation of the User Controls or their tests, it might be better to hold off on asking them until i've published the posts that cover writing the implementations and the actual tests. You can always ask questions if you want of course, but odds are high that i'm gonna cover the answer to your question in one of the future posts anyway.

One more important thing: the client in this sample is Silverlight, not WPF. You can apply all of these ideas to WPF programming as well obviously.

Now, what exactly is the sample project about? Here's a screenshot:

You're probably laughing pretty hard at my lousy UI skills (and rightfully so), but i'm sure you'll agree that the crappy looking UI is not relevant to the topic we're covering in this series ;)

That screenshot shows 2 UserControls. The implementation of both UserControls will be covered in-depth in the next 2 posts in this series, but for now, i'm just going to tell you what they're supposed to do.

The first UserControl looks like this:

It has a TreeView which shows a UserGroup hierarchy. When you select a UserGroup, its details must be displayed in the second UserControl where they can be edited. There's also a button to create a new UserGroup, whose details must also be provided in the second UserControl. Finally, when a UserGroup has been modified (or deleted) in the second UserControl, the contents of the TreeView must be updated correctly without simply fetching the entire hierarchy again.

The second UserControl looks like this:

In this UserControl, you can modify the name of the UserGroup, change its Parent, or just delete the UserGroup. If you've made changes, but haven't pushed the Save button yet, you can press the Cancel button and the values will be reverted to their original values. If you make a change here, the TreeView in the first UserControl needs to be updated to reflect that change (either an updated name, or a different parent).

In both UserControls, some of the actions that the user can perform are either enabled or disabled based on the permissions of the user. Right now, the permissions can only be changed in the code, but feel free to do so to see how that works.

Initially, i wanted to add a third UserControl where you can add/remove Users to the selected UserGroup. While you'll find some traces of this in the Service Layer code, there is nothing in the client to do this. Feel free to try to implement such a UserControl to see whether or not you like this whole approach with some hands-on coding.

I hope you'll find the sample to be small enough, but still have enough 'complexity' to show the benefits of using the MVP pattern over MVVM. Also, keep in mind that this sample is not perfect and that there are still some bugs in it. If you want to criticize, please focus on problems that are inherent to the usage of MVP instead of MVVM since that's what this is all about.

You can download the sample here.

In the next 2 posts, we'll cover the implementation of both UserControls in their entirety, and after that we'll focus on automated tests.