Showing posts with label MVC. Show all posts
Showing posts with label MVC. Show all posts

Monday, 19 May 2008

MVC Architecting Silverlight Applications Part 3 - Testing the ViewModel

In Part 2 of this series, I built a small class used to model our Login page - LoginViewModel.

In this post, I will demonstrate how to unit test this class to show that it behaves as expected. This is pretty essential as we want to be sure the class works properly before handing it over to our designer to build a view for!

I will be using TestDriven.NET's new Silverlight unit testing support which I blogged about earlier. This is unbelievably simple - just download and install the project template and then create a new Silveright NUnit Project called Silversocial.Client.Modules.Test

Building some test doubles


Before we can start unit testing our LoginViewModel, we need to fake out the dependencies. Given that there currently exists no mocking framework for Silverlight ( are you listening Ayende? ;-> ) we will manually build some stubs for our dependencies and then reuse them in our tests.

It is quite easy to way manually to build reusable stubs for an interface of your choosing. I simply follow these rules:
  • Use "explicit interface implementations" for all members

  • Implement properties as normal properties with a backing field (or as automatic properties if you'd prefer)

  • Implement methods by delegating the entire method body to a public event handler with the same signature and name as the method. Set the event handler to empty delegate to avoid null checks

  • Implement events by delegating the add/remove to a public event handler with the same signature as the event. Set the event handler to empty delegate to avoid null checks


Here is the code for the stub for the IAppShell interface.

using Silverstone;

 

namespace Silversocial.Client.Modules.Tests.Stubs

{

    public class AppShellStub : IAppShell

    {

        private User user;

        private IView view;

 

        public User User

        {

            get { return this.user; }

        }

 

        public IView View

        {

            get { return this.view; }

        }

 

        User IAppShell.User

        {

            get { return this.user; }

            set { this.user = value; }

        }

 

        void IShell.SetView(IView view)

        {

            this.view = view;

        }

    }

}


And now the stub for the ILoginDataProvider, also following the guidelines set above:

using System;

 

namespace Silversocial.Client.Modules.Tests.Stubs

{

    public class LoginDataProviderStub : ILoginDataProvider

    {

        private EventHandler<ValidateUserCompletedEventArgs> validateUserCompleted = delegate { };

 

        public Action<User> ValidateUser = delegate { };

 

        public void RaiseValidateUserCompleted(ValidateUserCompletedEventArgs args)

        {

            this.validateUserCompleted(this, args);

        }

 

        event EventHandler<ValidateUserCompletedEventArgs> ILoginDataProvider.ValidateUserCompleted

        {

            add { this.validateUserCompleted += value; }

            remove { this.validateUserCompleted -= value; }

        }

 

        void ILoginDataProvider.ValidateUser(User user)

        {

            this.ValidateUser(user);

        }

    }

}


We will create a "ViewStubBase" which implements the IView interface itself. This will form the base class for all other stubs of the views.

using System;

using Silverstone;

 

namespace Silversocial.Client.Modules.Tests.Stubs

{

    public abstract class ViewStubBase : IView

    {

        public Action OnLoad = delegate { };

        public Action OnUnload = delegate { };

 

        void IView.OnLoad()

        {

            this.OnLoad();

        }

 

        void IView.OnUnload()

        {

            this.OnLoad();

        }

    }

}


And now the stub for the ILoginView interface. As you can see we just implement the LoginUnsuccessful method and call a public event handler which does nothing by default. The allows the test to listen to the method being called if it wants to, and can for example validate if it is called or not.

using System;

 

namespace Silversocial.Client.Modules.Tests.Stubs

{

    public class LoginViewStub : ViewStubBase, ILoginView

    {

        public Action LoginUnsuccessful = delegate { };

 

        void ILoginView.LoginUnsuccessful()

        {

            this.LoginUnsuccessful();

        }

    }

}


The other views are just empty classes for now. The first:

namespace Silversocial.Client.Modules.Tests.Stubs

{

    public class FriendListStub : ViewStubBase, IFriendListView

    {

    }

}


And the other...

namespace Silversocial.Client.Modules.Tests.Stubs

{

    public class RegisterStub : ViewStubBase, IRegisterView

    {

    }

}


The test fixture



Finally it's time to implement the test fixture, just using the standard NUnit syntax for defining the tests and setup method. The SetUp creates all the stubs, so they can be used by each test.

Note I am using the "AAA" pattern which was recently coined by Ayende for RhinoMocks 3.5 - I really like it! This is the way I have written most of my tests in the past, and it's nice to have a formal name for the stages undergone (especially when it's an alliterism)

Here are the tests:

using NUnit.Framework;

using Silversocial.Client.Modules.Tests.Stubs;

using Silverstone;

 

namespace Silversocial.Client.Modules.Tests

{

    [TestFixture]

    public class LoginViewModelTester

    {

        private LoginViewStub loginViewStub;

        private AppShellStub shellStub;

        private FriendListStub friendListStub;

        private RegisterStub registerStub;

        private LoginDataProviderStub dataProviderStub;

        private LoginViewModel viewModel;

 

        [SetUp]

        public void SetUp()

        {

            this.shellStub = new AppShellStub();

            this.friendListStub = new FriendListStub();

            this.registerStub = new RegisterStub();

            this.dataProviderStub = new LoginDataProviderStub();

            this.viewModel = new LoginViewModel(shellStub, dataProviderStub, friendListStub, registerStub);

 

            this.loginViewStub = new LoginViewStub();

            ((IViewModel) this.viewModel).SetView(this.loginViewStub);

        }

 

        [Test]

        public void Login_WithoutUsernameAndPassword_CannotExecute()

        {

            Assert.IsFalse(this.viewModel.Login.CanExecute(null));

        }

 

        [Test]

        public void Login_WithUsernameAndPassword_CanExecute()

        {

            // Arrange

            this.viewModel.User.Username = "abc";

            this.viewModel.User.Password = "abc";

 

            // Assert

            Assert.IsTrue(this.viewModel.Login.CanExecute(null));

        }

 

        [Test]

        public void Login_BeforeCompleted_IsLoggingInIsTrue()

        {

            // Act

            this.viewModel.Login.Execute(null);

 

            // Assert

            Assert.IsTrue(this.viewModel.IsLoggingIn);

        }

 

        [Test]

        public void Login_AfterCompleted_IsLoggingInIsFalse()

        {

            // Act

            this.viewModel.Login.Execute(null);

            this.dataProviderStub.RaiseValidateUserCompleted(new ValidateUserCompletedEventArgs(false));

 

            // Assert

            Assert.IsFalse(this.viewModel.IsLoggingIn);

        }

 

        [Test]

        public void Login_AfterCompleted_RaisesCanExecuteChangedEvent()

        {

            // Arrange

            bool canExecuteChangedCalled = false;

            this.viewModel.Login.CanExecuteChanged +=

                delegate { canExecuteChangedCalled = true; };

 

            // Act

            this.dataProviderStub.RaiseValidateUserCompleted(new ValidateUserCompletedEventArgs(false));

 

            // Assert

            Assert.IsTrue(canExecuteChangedCalled);

        }

 

        [Test]

        public void Login_WhilstLoggingIn_CannotExecute()

        {

            // Arrange

            this.viewModel.User.Username = "abc";

            this.viewModel.User.Password = "abc";

            this.viewModel.IsLoggingIn = true;

 

            // Assert

            Assert.IsFalse(this.viewModel.Login.CanExecute(null));

        }

 

        [Test]

        public void Login_Always_CallsValidateUserOnDataProvider_WithCorrectUser()

        {

            // Arrange

            User userSentToDataProvider = null;

            this.dataProviderStub.ValidateUser +=

                u => userSentToDataProvider = u;

 

            // Act

            this.viewModel.Login.Execute(null);

 

            // Assert

            Assert.AreEqual(this.viewModel.User, userSentToDataProvider);

        }

 

        [Test]

        public void Login_WithInvalidDetails_CallsLoginUnsuccessfulOnView()

        {

            // Arrange

            this.dataProviderStub.ValidateUser +=

                u => this.dataProviderStub.RaiseValidateUserCompleted(new ValidateUserCompletedEventArgs(false));

            bool loginUnsuccessfulCalledOnView = false;

            this.loginViewStub.LoginUnsuccessful +=

                () => loginUnsuccessfulCalledOnView = true;

 

            // Act

            this.viewModel.Login.Execute(null);

 

            // Assert

            Assert.IsTrue(loginUnsuccessfulCalledOnView);

        }

 

        [Test]

        public void Login_WithValidDetails_SetsFriendListViewOnShell()

        {

            // Arrange

            this.dataProviderStub.ValidateUser +=

                u => this.dataProviderStub.RaiseValidateUserCompleted(new ValidateUserCompletedEventArgs(true));

 

            // Act

            this.viewModel.Login.Execute(null);

 

            // Assert

            Assert.AreEqual(this.friendListStub, this.shellStub.View);

        }

 

        [Test]

        public void Login_WithValidDetails_SetsUserOnShell()

        {

            // Arrange

            this.dataProviderStub.ValidateUser +=

                u => this.dataProviderStub.RaiseValidateUserCompleted(new ValidateUserCompletedEventArgs(true));

 

            // Act

            this.viewModel.Login.Execute(null);

 

            // Assert

            Assert.AreEqual(this.viewModel.User, this.shellStub.User);

        }

 

        [Test]

        public void Register_Always_SetsRegisterViewOnShell()

        {

            // Act

            this.viewModel.Register.Execute(null);

 

            // Assert

            Assert.AreEqual(this.registerStub, this.shellStub.View);           

        }

    }

}


And here you can see them running in the Resharper test runner - they all pass!



Now I can confidently pass my ViewModel to my designer to build a view against...

Or can I? Without a data provider, he's going to have problems creating and testing his view. This will be the subject of the next post.

Sunday, 18 May 2008

MVC Architecting Silverlight Applications Part 2 - Building a ViewModel

In Part 1 of this series I talked about and described the Model-View-ViewModel (MVVM) pattern.

In this post I will describe the application I wish to build to demonstrate this pattern to you, and at the same time demonstrate usage of the Silverstone framework.

A "social networking" application


In picking my sample app I wanted to choose something fairly simple. I decided on a small social networking app, which would basically allow the following use cases:
  1. Register - A user can enter their email address and choose a password. The user must confirm their password. Password must be between 4 and 10 characters. After registering, the user proceeds to use case 3

  2. Login - An existing user can enter their email address and password to login. If they enter the wrong details the system should inform them that their details were incorrect. After logging in, the user proceeds to use case 3

  3. View Friends - Displays a list of friends for the currently logged in user.
And that's it! Despite being fairly minimal (and not that useful without being able to add friends or contact them!) that's all I am going to build for now I think.

Creating a solution


We will name this application "Silversocial" (not very imaginative I know, but it's only a demo app!)

You will require Visual Studio 2008, the Silverlight Tools for Visual Studio, and a copy of Silverstone.dll.

Start off by creating a new project of type Silverlight Application named Silversocial.Client.Views. Choose to create a new solution and call it Silversocial



Choose to add a new Web to the solution for hosting the Silverlight content, and call it Silversocial_WebHost (I choose a web application because I prefer them). The solution will be created containing a Silverlight project and a Web application project.



Out of interest, if you right click on the Web Application project properties, you will see a new tab called "Silverlight links" and you will see that the Silverlight application you just created has been added for you automatically. All this really means is that when you build the Silverlight application, the compiled .xap file will be copied into the Web application's /ClientBin folder.



The shell


The shell in a Silverstone application is the central view component which is responsible for managing the other views in the application. Hence, the IShell interface contains just one method:

void SetView(IView view)

Typically an application will implement the shell as the main UI component containing any navigation and common elements for every page (much like ASP.NET's master page). The application will create its own derived IShell interface and expose any elements required by the individual pages through that interface.

So, given the requirements specified earlier, our application's shell only requires an extra property - the currently logged in User. This would allow the shell's view to display the user and allow the other pages to retrieve it when necessary.

To begin, we will create another Silverlight class project called Silversocial.Client.Modules to house the rest of our application's interfaces and concrete implementations. Obviously in a larger application you would consider splitting your code into multiple assemblies grouped by role.

First, the User class which stores the username and password:

using System.ComponentModel;

 

namespace Silversocial.Client.Modules

{

    public class User : INotifyPropertyChanged

    {

        private string username;

        private string password;

 

        public event PropertyChangedEventHandler PropertyChanged = (s,p) => {};

 

        public string Username

        {

            get { return this.username; }

            set

            {

                this.username = value;

                this.PropertyChanged(this, new PropertyChangedEventArgs("Username"));

            }

        }

 

        public string Password

        {

            get { return this.password; }

            set

            {

                this.password = value;

                this.PropertyChanged(this, new PropertyChangedEventArgs("Password"));

            }

        }

    }

}


And now the shell interface itself:

using Silverstone;

 

namespace Silversocial.Client.Modules

{

    public interface IAppShell : IShell

    {

        User User { get; set; }

    }

}


Creating the first ViewModel


For now, we will not worry about building a concrete View. Instead, we will start with our ViewModel. The page we will build is the Login page, and we will create a new class called LoginViewModel for it.

ILoginView will be the interface which our View will have to implement, and has a single method as follows:

using Silverstone;

 

namespace Silversocial.Client.ViewModels

{

    public interface ILoginView : IView

    {

        void LoginUnsuccessful();

    }

}


The contract states that the LoginUnsuccessful() method will be called by the ViewModel if the login is unsuccessful.

On the other side of the equation, our ViewModel will require some way of actually validating the user. In the real world application we will expose a service for doing this, but for now, let's stub it out with the following interface:

using System;

 

namespace Silversocial.Client.Modules

{

    public class ValidateUserCompletedEventArgs : EventArgs

    {

        public ValidateUserCompletedEventArgs(bool successful)

        {

            this.Successful = successful;

        }

 

        public bool Successful { get; set; }

    }

 

    public interface ILoginDataProvider

    {

        event EventHandler<ValidateUserCompletedEventArgs> ValidateUserCompleted;

        void ValidateUser(User user);

    }

}


(Note that this interface is implementing an asynchronous pattern - raising the ValidateUserCompleted event when the method has completed. This ties in well with the fact that Ajax requests are asynchronous, and that Silverlight's WCF ChannelFactory implementation only supports asynchronous requests.)

The View Model is shown below. The main features are:
  • All dependencies passed through as interfaces to the constructor. This includes other views, the data provider, and the shell.

  • Property exposed for the User, which the View can bind to.

  • Property exposed for whether we are in the middle of a login attempt, which the View can bind to.

  • Exposes a Login command which will attempt to use the data provider to validate the user. If successful the callback will set the user on the shell and change to the FriendListView. If unsuccessful the callback will call the LoginUnsuccessful() method on the view.

  • Exposes a Register command which will simply change views to the RegisterView.

using Silverstone;

 

namespace Silversocial.Client.Modules

{

    /// <summary>

    ///    ViewModel for the login page

    /// </summary>

    public class LoginViewModel : ViewModelBase<ILoginView>

    {

        // Dependencies

        private readonly IAppShell shell;

        private readonly ILoginDataProvider dataProvider;

        private readonly IFriendListView friendListView; // This is just an empty view interface for now

        private readonly IRegisterView registerView; // This is just an empty view interface for now

 

        // Commands

        private readonly ICommand login;

        private readonly ICommand register;

 

        // Data Fields

        private readonly User user = new User();

        private bool isLoggingIn;

 

        public LoginViewModel(IAppShell shell, ILoginDataProvider dataProvider, IFriendListView gameChooserView, IRegisterView registerView)

        {

            this.shell = shell;

            this.registerView = registerView;

            this.friendListView = gameChooserView;

            this.dataProvider = dataProvider;

            this.login = new LoginCommand(this);

            this.register = new RegisterCommand(this);

        }

 

        /// <summary>

        ///    Gets the user being logged in

        /// </summary>

        public User User

        {

            get { return this.user; }

        }

 

        /// <summary>

        ///    Gets or sets whether the user is currently being logged in

        /// </summary>

        public bool IsLoggingIn

        {

            get { return isLoggingIn; }

            set

            {

                isLoggingIn = value;

                this.RaisePropertyChanged("IsLoggingIn");

            }

        }

 

        /// <summary>

        ///    Gets the command used to login the user

        /// </summary>

        public ICommand Login

        {

            get { return this.login; }

        }

 

        /// <summary>

        ///    Gets the command used to register a new user

        /// </summary>

        public ICommand Register

        {

            get { return register; }

        }

 

        private class LoginCommand : CommandBase<LoginViewModel>

        {

            public LoginCommand(LoginViewModel viewModel) : base(viewModel)

            {

                this.ViewModel.dataProvider.ValidateUserCompleted += this.HandleValidateUserCompleted;

            }

 

            public override bool CanExecute(object parameter)

            {

                // Used to inform the command framework that we can only login once the user

                // has entered a username and password and we are not already in the middle of a login

                return !string.IsNullOrEmpty(this.ViewModel.User.Username)

                    && !string.IsNullOrEmpty(this.ViewModel.User.Password)

                    && !this.ViewModel.IsLoggingIn;

            }

 

            public override void Execute(object parameter)

            {

                // Call the Async method on the data provider to validate the user.  The ViewModel will

                // handle the response to this method

                this.ViewModel.dataProvider.ValidateUser(this.ViewModel.User);

 

                // Record that we are currently in the middle of a login attempt

                this.ViewModel.IsLoggingIn = true;

            }

 

            private void HandleValidateUserCompleted(object sender, ValidateUserCompletedEventArgs e)

            {

                // Record that we have finished attempting to log in

                this.ViewModel.IsLoggingIn = false;

 

                if (e.Successful)

                {

                    this.ViewModel.shell.User = this.ViewModel.User;

                    this.ViewModel.shell.SetView(this.ViewModel.friendListView);

                }

                else

                {

                    this.ViewModel.View.LoginUnsuccessful();

                }

 

                // Raise the "CanExecuteChanged" method

                this.RaiseCanExecuteChanged();

            }

 

        }

 

        private class RegisterCommand : CommandBase<LoginViewModel>

        {

            public RegisterCommand(LoginViewModel viewModel) : base(viewModel)

            {

            }

 

            public override void Execute(object parameter)

            {

                // Simply use the shell to navigate to the register view when this command is executed

                this.ViewModel.shell.SetView(this.ViewModel.registerView);

            }

        }

    }

}

The next post

In the next post I will talk about unit testing the view model, and will start to wire things together so our designer can build a View for all this.

Tuesday, 22 April 2008

MVC Architecting Silverlight Applications Part 1 - M-V-VM

In my previous post I mentioned that I had used the Model-View-ViewModel approach to building WPF applications.

In this series of blog posts I will describe this pattern, and show you how to build a small sample application using it within the context of Silverlight. I have folded a bunch of base classes and helper classes into my open source Silverstone framework and these will be used extensively.

What is Model-View-ViewModel

I am assuming that those of you reading this post are familiar with the Model View Controller pattern and have probably used it in some form of ASP.NET or WinForms application. If not then you might want to check out Martin Fowler's article on GUI Architectures for an in depth technical appraisal, or this page on the MSDN site for more information.

The Model-View-ViewModel (or MVVM) pattern is a form of MVC specifically catered for scenarios where you have a very strong data binding ability within the framework, and also one where you want to give your designers the freedom to design the view completely themselves.

Let's explain the three components now:

Model - The Models are the classes which represent the domain on which the application is processing/interacting with. I often refer to these as DataModels, as they are modelling the data in the application, and usually contain some kind of business rules and validation logic.

View - The Views are simply the classes which the render the graphical elements to the user and allow him to interact with the application. They are ideally implemented in 100% markup, although in practise there will end up being some code there too!

ViewModel - The ViewModels are the new kids in town for those of you familiar with traditional MVC/MVP. They essentially take the part of the controller or presenter, and are named as such because they "model the view". Each ViewModel is therefore built for a specific View, and contains all the data and behaviour logic which that View requires to interact with the data models.

Why "ViewModel"?

In an MVC application, the Controller will typically respond to an event when notified by the view, such as a "Save" button being clicked. It will usually then interact with the model in some way, e.g. reading some values from the view and applying them to the model. Finally it will notify the view of something. For example, it may tell it to display a "your model was saved" message to the user, or alternatively to display a validation warning if something went wrong.

The ViewModel has the same responsibility as the controller, but it handles that responsibility in a different way. Firstly, it makes use of the rich binding provided by the platform by exposing data to the View and raising property changed notifications when that data changes. It also expects the view to use two way binding to push changes direcly back to those properties. Only the data which is required for that specific view is exposed. This will generally include the models which the user needs to view or edit as well as controlling properties which tell the view which state it is in. For example the ViewModel may expose a boolean property which tells the view that it is in the process of saving the DataModel.

At the same time, the ViewModel exposes Commands for the View to execute. It does so in such a way that the commands can be bound directly to UI elements (such as buttons). It is the commands which are responsible for handling user interactions. For example , a save command may be exposed which saves whichever changes users have made to the data models. See my previous blog post for more information on Commands in Silverlight

In Silverlight and WPF terms, the important point to take home is that the ViewModel is the DataContext of the View. This is the key enabler of all the data binding I have been referring to!

And that's really it for now. I hope this makes sense a summary for the architecture pattern we are trying to follow. If you have any questions please let me know.

In the next part of this series, I will begin walking through how to develop a small application using this pattern under Silverlight.

Have a great day!

Neil

Wednesday, 12 March 2008

Prism - New CAB/MVC style framework for building WPF applications

About a year ago Microsoft's patterns and practices team released Acropolis as a beta - a UI framework for building WPF applications using a MVC style approach. I played around with it but to be honest I didn't feel it was advanced enough for building fully fledged business applications, but a very good start.

For the National Express project we gradually applied the MVVM (Model-View-ViewModel) pattern, which we fully crafted by hand. Obviously it was a learning experience but it worked well and fitted in well with WPF. Also as we crafted it by hand we had full control of pretty much everything going on in the application and could refactor as we pleased.

Well the Acropolis framework development project was halted about six months ago, after much user feedback. The patterns and practices team have been working on a new CAB style framework, codenamed Prism (not to be confused with Mozilla's Prism project, which integrates web applications with the desktop) . Briefly looking at it, it does remind me of the existing composite application frameworks developed by that team - namely Web Client Software Factory for ASP.NET MVP applications, and the Composite Application Blocks for building WinForms applications.

I have to say I like the approach. This feels very familar for those of us that have used either of these two frameworks, which can only be a good thing and helpful for those architecting WPF applications who have come from a WebForms/WinForms background. It will help in increasing the adoption of WPF applications for the business/enterprise.

The team have released a reference implementation - a stock trader application. Download it here and read the blog post which provides more details about the reference implementation.