Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Free Learning
Arrow right icon
Arrow up icon
GO TO TOP
MVVM Survival Guide for Enterprise Architectures in Silverlight and WPF

You're reading from   MVVM Survival Guide for Enterprise Architectures in Silverlight and WPF If you're using Silverlight and WPF, then employing the MVVM pattern can make a powerful difference to your projects, reducing code and bugs in one. This book is an invaluable resource for serious developers.

Arrow left icon
Product type Paperback
Published in Aug 2012
Publisher Packt
ISBN-13 9781849683425
Length 490 pages
Edition 1st Edition
Languages
Arrow right icon
Toc

Table of Contents (21) Chapters Close

MVVM Survival Guide for Enterprise Architectures in Silverlight and WPF
Credits
Foreword
About the Authors
About the Reviewer
www.PacktPub.com
Preface
1. Presentation Patterns 2. Introduction to MVVM FREE CHAPTER 3. Northwind – Foundations 4. Northwind—Services and Persistence Ignorance 5. Northwind—Commands and User Inputs 6. Northwind—Hierarchical View Model and IoC 7. Dialogs and MVVM 8. Workflow-based MVVM Applications 9. Validation 10. Using Non-MVVM Third-party Controls 11. MVVM Application Performance MVVM Frameworks
Binding at a Glance Index

Monolithic Project Billing sample


Let's go ahead and walk through a simple implementation in WPF of the Project Billing application that was introduced at the beginning of this chapter. We will create the UI using a monolithic style.

Note

This will be a WPF application but we are not using RAD (Rapid Application Development) support available in Visual Studio, XAML or WPF project templates as it better demonstrates the monolithic style. If you are not familiar with writing code only WPF applications in this style and want to learn more then see Applications = Code + Markup: A Guide to the Microsoft Windows Presentation Foundation, by Charles Petzold.

Start by creating a solution and then adding a new Console Application project named ProjectBilling.Monolithic to your solution, as shown in the following screenshot:

Note

We will convert this console application to a Windows application later in this section but it's not necessary to do so as you can run a WPF application from a console application. Full details are coming later in this section.

Now add a reference to the PresentationFramework, PresentationCore, System.Xaml, and WindowsBase assemblies, as shown in the following screenshot:

Note

The previous screenshot only shows adding a reference to PresentationFramework. Repeat this process for PresentationCore, System.Xaml, and WindowsBase as well.

Now add a project reference to ProjectBilling.DataAccess, as shown in the following screenshot:

Next, delete Program.cs and add a new class named ProjectsView and add the following code to that file.

Note

Using data service means that technically we are not implementing a monolith as we are introducing a data access layer. This is done to keep the code as short as possible. Keep in mind that a purely monolithic application would not have a separate data access layer. The variation of monolithic design that we are implementing here is commonly referred to as autonomous view.

ProjectsView

The heart of this application is the ProjectsView class. Let's start by making this class a window and bringing in the namespaces we need.

using System;
using System.Windows;
using System.Windows.Controls;
using ProjectBilling.DataAccess;
using System.Windows.Media;

namespace ProjectBilling.UI.Monolithic
{
    sealed class ProjectsView : Window
    {
 
    }
}

This class now derives from System.Windows.Window, which is what allows it to be displayed as a WPF application. Add a main function to ProjectsView as follows:

[STAThread]
static void Main(string[] args)
{
    ProjectsView mainWindow
        = new ProjectsView();
    new Application().Run(mainWindow);
}

The main function is given the STAThread attribute—which makes it run in a single threaded apartment—which is a requirement of WPF and for interoperability with COM (Component Object Model). The main function simply creates a ProjectsView and then passes it to System.Windows.Application.Run(), which initializes WPF, starts a message loop, and then displays ProjectsView as the application's main window.

Initialization

Most of the work of the application will be done by the ProjectsView constructor and field initializers. Add the following fields to the class:

private static readonly Thickness _margin 
    = new Thickness(5);
private readonly ComboBox _projectsComboBox 
    = new ComboBox() { Margin = _margin };
private readonly TextBox _estimateTextBox 
    = new TextBox() 
        { IsEnabled = false, Margin = _margin };
private readonly TextBox _actualTextBox 
    = new TextBox()
        { IsEnabled = false, Margin = _margin };
private readonly Button _updateButton = new Button()
    { 
        IsEnabled = false, 
        Content = "Update",
        Margin = _margin
    };

Here we've created the Project combobox, Estimated Cost and Actual Cost textboxes in addition to the Update button.

Next let's add a constructor with the following code. We'll start by setting the Title and size of the MonolithicProjectBillingWindow instance. We will then call two helper methods that will be covered shortly and also add an event handler for the updateButton.Click event.

Note

This event handler will allow the code to be notified of user input via .NET's built-in support for the Observer pattern that is implemented by .NET events.

public ProjectsView()
{
    Title = "Project";
    Width = 250;
    MinWidth = 250;
    Height = 180;
    MinHeight = 180;
 
    LoadProjects();
 
    AddControlsToWindow();
 
    _updateButton.Click += updateButton_Click;
}

Note

See the Helpers section for methods that are called but not yet defined such as LoadProjects() and AddControlsToWindow().

Event handlers

Most of the rest of the functionality of the application is contained within the event handlers:

  • The following code will create projectsComboBox_SelectionChanged(), which is an event handler for the projectsComboBox.SelectionChanged event that we will wire up in the LoadProjects()method that was called from the constructor. This code first determines if an item is selected by casting the sender to a comboBox, making sure it isn't null and also that an item is selected.

    private void projectsListBox_SelectionChanged(
        object sender, SelectionChangedEventArgs e)
    {
        ComboBox comboBox = sender as ComboBox;
     
        // If there is a selected item
        if (comboBox != null && comboBox.SelectedIndex > -1)
        {
            UpdateDetails();
        }
        else
        {
            DisableDetails();
        }
    }
  • If there is an item selected in projectsComboBox then the UpdateDetails() helper method is called; if no item is selected then the DisableDetails() helper method is called.

  • updateButton.Click() is shown in the following code:

    private void updateButton_Click(object sender,
        RoutedEventArgs e)
    {
        Project selectedProject
            = _projectsComboBox.SelectedItem
            as Project;
        if (selectedProject != null)
        {
            selectedProject.Estimate =
                double.Parse(_estimateTextBox.Text);
            if (!string.IsNullOrEmpty(
                _actualTextBox.Text))
            {
                selectedProject.Actual
                    = double.Parse(
                        _actualTextBox.Text);
            }
            SetEstimateColor(selectedProject);
        }
    }

    updateButton.Click() will fire when the user clicks on the Update button and determine if an item is selected. If an item is selected, it will update the details controls with the details of the selected item. The values to populate the details controls will be fetched from the properties of the details controls which we are currently using for view state. Next updateButton.Click() will call the SetEstimateColor() helper function to update the color of the estimateTextBox (view state) based on whether the estimated cost is higher or lower than the actual cost (view logic).

    Note

    _actualTextBox is checked for null or empty as it starts out in an empty state and could be empty that state if the user updates only the Estimated Cost but not actual. This validation was provided to keep the application running down the happy path while all other validation have been left out to keep the code short.

Helpers

These private helper methods will add the remaining functionality:

  • Add the LoadProjects() method, as shown in the following code:

    private void LoadProjects()
    {
        foreach (Project project
            in new DataServiceStub().GetProjects())
        {
            _projectsComboBox.Items.Add(project);
        }
        _projectsComboBox.DisplayMemberPath = "Name";
        _projectsComboBox.SelectionChanged
            += new SelectionChangedEventHandler(
                projectsListBox_SelectionChanged);
    }
  • The LoadProjects() method will do the following:

    • Fetch the projects to populate the projectsComboBox with data retrieved from persisted state by instantiating a new DataService and then calling GetProjects()

    • The results of GetProjects() are iterated over and added to _projectsComboBox for display

    • Set the DisplayMemeberPath to "Name" to use the Project.Name property for the displayed text for each project in the _projectsComboBox.Items collection

    • Wire up an event handler for the projectsComboBox.SelectionChanged event allowing us to update the details view when the user changes the selected project

  • Add the AddControlsToWindow() method with the following code:

    private void AddControlsToWindow()
    {
        UniformGrid grid = new UniformGrid()
            { Columns = 2 };
        grid.Children.Add(new Label() 
            { Content = "Project:" });
        grid.Children.Add(_projectsComboBox);
        Label label = new Label() 
            { Content = "Estimated Cost:" };
        grid.Children.Add(label);
        grid.Children.Add(_estimateTextBox);
        label = new Label() 
            { Content = "Actual Cost:"};
        grid.Children.Add(label);
        grid.Children.Add(_actualTextBox);
        grid.Children.Add(_updateButton);
        Content = grid;
    }
  • The previous code will do the following:

    • Create a new UniformGrid

    • Configure the controls we will be using and then add the controls to the grid

    • Set the grid as the content of the window for display

  • Add the GetGrid() method to ProjectsView as follows:

    private Grid GetGrid()
    {
         Grid grid = new Grid();
         grid.ColumnDefinitions
            .Add(new ColumnDefinition());
         grid.ColumnDefinitions
            .Add(new ColumnDefinition());
         grid.RowDefinitions
            .Add(new RowDefinition());
         grid.RowDefinitions
            .Add(new RowDefinition());
         grid.RowDefinitions
            .Add(new RowDefinition());
         grid.RowDefinitions
            .Add(new RowDefinition());
        return grid;
    }
  • This code creates a 2x3 Grid that is used to create a basic form layout.

    Note

    We are not trying to make this form pretty but are instead trying to focus on the presentation patterns. One of the big benefits of MVVM is that it will allows us to give our view XAML to a designer and have them make it look nice without having the need to involve the developer. We will look at this approach in detail later in this book in Chapter 7, Dialogs and MVVM.

  • Add the UpdateDetails() method as follows:

    private void UpdateDetails()
    {
        Project selectedProject
            = _projectsComboBox.SelectedItem
            as Project;
     
        _estimateTextBox.IsEnabled = true;
        _estimateTextBox.Text
            = selectedProject.Estimate.ToString();
        _actualTextBox.IsEnabled = true;
        _actualTextBox.Text
            = (selectedProject.Actual == 0)
                    ? ""
                    : selectedProject.Actual.ToString();
        SetEstimateColor(selectedProject);
        _updateButton.IsEnabled = true;
    }
  • The UpdateDetails() method simply transfers data from the projectsComboBox.SelectedItem (or master) to the details controls and then updates the estimateTextBox by calling SetEstimateColor().

  • Add a DisableDetails() method as follows:

    private void DisableDetails()
    {
      _estimateTextBox.IsEnabled = false;
      _actualTextBox.IsEnabled = false;
      _updateButton.IsEnabled = false;
    }
  • The DisableDetails() method sets the details controls IsEnabled to false along with the update button.

  • Add SetEstimateColor() as follows:

    private void SetEstimateColor(Project selectedProject)
    {
        if (selectedProject.Actual == 0)
        {
            this.estimateTextBox.Foreground 
                = _actualTextBox.Foreground;
        }
        else if (selectedProject.Actual 
            <= selectedProject.Estimate)
        {
            this.estimateTextBox.Foreground 
                = Brushes.Green;
        }
        else
        {
            this.estimateTextBox.Foreground 
                = Brushes.Red;
        }
    }
  • The SetEstimateColor() method will be called by both event handlers to update the color of Estimated Cost (view state) by examining the Actual Cost and Estimated Cost.

Running the sample

Right-click on the ProjectBilling.Monolithic project and select Properties. Next, set the Output type to Windows Application as shown in the following screenshot:

Tip

If you leave the Project type as Console Application then a Console Window will be displayed while your WPF application runs. This can be useful for debugging as you can write debug messages to the console and easily kill the application using Ctrl + C when debugging.

Finally set ProjectBilling.Monolithic as the startup project by right-clicking on it and selecting Set as StartUp project. Now run the application by hitting F5.

You should now an application as shown in The Project Billing sample application section at the beginning of this chapter.

Takeaways

This code gets the job done, so what's the problem and why is there the need to restructure it?

Poor testability

This code has poor testability as the entire code is tightly coupled to the view and requires the view to fire the events that drive the logic of application. You could change the access modifiers of the methods of ProjectsView to public the help alleviate the situation but then you weaken the design from the encapsulation and design by contract perspectives.

Note

Encapsulation and design by contract are basic principles of Object-oriented design that are covered extensively on the Web. Please look up for them if you are already not familiar with them.

Poor extensibility and code reuse

If the users wanted a command line or web-interface, all of the code would need to be rewritten. Also, supporting multiple synchronized ProjectView is not possible under this design and would require at a minimum refactoring out a model.

Note

We will demonstrate how adding SoC allows for creating multiple synchronized vs of the model when we get to the MVC section.

Rapid application development

Microsoft puts a lot of development effort into creating Rapid Application Development (or RAD) tools that allow developers to simply drag-and-drop controls onto the IDE's design surface and then allow for configuring the controls' data needs mostly through the IDE's designer. The designer then creates monolithic code to get the job done. These tools make the problems of monolithic design worse by encouraging that style of design and by making it easier to do.

RAD Project Billing sample

This section will walk through rewriting the Project Billing application using RAD tools in Visual Studio.

Start by adding a new WPF Application project to your solution called ProjectBilling.RAD. This project template creates two files for you, App.xaml and MainWindow.xaml.

Next add a project reference to ProjectBilling.DataAccess.

Open MainWindow.xaml in Cider (the WPF designer) by double-clicking on MainWindow.xaml in the Solution Explorer. If they're not already expanded, expand the Toolbox window and the Data Sources window. You should have Visual Studio set up as shown in the following screenshot:

The first step is to add an Object Data Source to connect to DataService.GetProjects(). To do this start by clicking on Add New Data Source in the Data Sources window, as shown in the following screenshot:

You will now be presented with a dialog that will allow you to specify an Object Data Source, as shown in the following screenshot:

You will now be given the option to select the object that will be your data source. Select the Project class as shown in the following screenshot:

Next, select ComboBox from the Name drop-down menu, as shown in the following screenshot. This will change the type of generated control to be a combobox for the Name property.

Now drag the Name column onto the designer surface so that Visual Studio can generate some code to create a ComboBox which will be ready to be bound by an IList<Product>.

Change the width of the window to 250 by clicking on the MainWindow and setting the width value in the properties. You should now see something similar to what is shown in the following screenshot:

Looking at the XAML in the previous screenshot you will see that some code was generated for you. The important parts are highlighted as follows.

Note

It is assumed that you are familiar with the basics of WPF's data binding as full details fall outside of the scope of this book. However, see Appendix B, Binding at a glance, and/or see Data Binding (WPF) on MSDN (http://msdn.microsoft.com/en-us/library/ms750612.aspx).

<Window x:Class="RadProjectBilling.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="250" 
        mc:Ignorable="d" 
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
        xmlns:my="clr-namespace: ProjectBilling.DataAccess;assembly=ProjectBilling.DataAccess" 
        Loaded="Window_Loaded">
    <Window.Resources>
        <CollectionViewSource x:Key="projectViewSource" d:DesignSource="{d:DesignInstance my:Project, CreateList=True}" />
    </Window.Resources>
    <Grid>
        <Grid DataContext="{StaticResource projectViewSource}"
              HorizontalAlignment="Left"
              Margin="12,12,0,0" Name="grid1"
              VerticalAlignment="Top">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto" />
                <ColumnDefinition Width="Auto" />
            </Grid.ColumnDefinitions>
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto" />
            </Grid.RowDefinitions>
            <Label Content="Name:" Grid.Column="0" 
                   Grid.Row="0" HorizontalAlignment="Left" 
                   Margin="3" VerticalAlignment="Center" />
            <ComboBox DisplayMemberPath="Name" Grid.Column="1" 
                      Grid.Row="0" Height="23" 
                      HorizontalAlignment="Left" 
                      ItemsSource="{Binding}" Margin="3" 
                      Name="nameComboBox" 
                      VerticalAlignment="Center" Width="120">
                <ComboBox.ItemsPanel>
                    <ItemsPanelTemplate>
                        <VirtualizingStackPanel />
                    </ItemsPanelTemplate>
                </ComboBox.ItemsPanel>
            </ComboBox>
        </Grid>
    </Grid>
</Window>

At the top of the file, there is an event handler added for the Window.Loaded event which is set to Window_Loaded.As you will see soon, Window_Loaded was created in the code behind. Next, a new CollectionViewSource named projectViewSource was added and set to reference to the DataLayer.Project class.

Note

A CollectionViewSource class wraps a data source and allows you to navigate and display the collection based on sort, filter, and group quires.

The grid, grid1, then had its DataContext set to projectViewSource and a ComboBox called nameComboBox was added with its ItemsSource bound to its DataContext with the following code.

ItemsSource="{Binding}"

Specifying Binding with no path in a binding expression will cause the binding target to be bound to the combobox's DataContext property.

Note

We will be covering bindings and DataContext in more depth later in this book.

DataContext is an inherited DependencyProperty and inherited DependencyProperties will have their values propagated from parents to children in the Visual Tree and in this case will result in the DataContext that was set on grid1 being propagated to all of its children including nameComboBox.

Note

For more information on DependencyProperties see Dependency Properties Overview on MSDN (http://msdn.microsoft.com/en-us/library/ms752914.aspx) and for more information on the Visual Tree see Trees in WPF on MSDN (http://msdn.microsoft.com/en-us/library/ms753391.aspx).

If we look in the code behind, MainWindow.xaml.cs, we'll see that projectViewSource has been initialized in Window_Loaded().

private void Window_Loaded(object sender, RoutedEventArgs e)
{

    System.Windows.Data.CollectionViewSource projectViewSource 
        = ((System.Windows.Data.CollectionViewSource)
        (this.FindResource("projectViewSource"));
    // Load data by setting the 
    // CollectionViewSource.Source property:
    // projectViewSource.Source = [generic data source]
}

There is some commented out code created for you.

// projectViewSource.Source = [generic data source]

By uncommenting the previous line of code, you can easily set the data source to the collection returned by DataServiceStub.GetProjects, as shown in the following code.

Note

You will need to add a using statement for ProjectBilling.DataAccess to the top of the file.

private void Window_Loaded(object sender, RoutedEventArgs e)
{

    System.Windows.Data.CollectionViewSource projectViewSource 
        = ((System.Windows.Data.CollectionViewSource)
        (this.FindResource("projectViewSource"));
    // Load data by setting the 
    // CollectionViewSource.Source property:
    projectViewSource.Source=new DataServiceStub().GetProjects(); 

}

Now if we run the application, we will see that the Name combobox is populated with data shown in the following screenshot:

Next we need to add the details controls. The first step is to click on the drop-down menu next to the Project data source in the DataSources window and change its type to Details, as shown in the following screenshot:

Now we will generate the details controls as shown in the following screenshot:

Perform the following steps:

  1. Drag the Project data source to the MainWindow on the cider design surface as shown in the previous screenshot. This will create a mini form with the controls for displaying the details.

  2. Drag the form that was created by clicking on the drag handles for the grid and move it below the name, as shown in the previous screenshot.

  3. Next, clean up the form by removing the labels, textboxes, and rows that are associated with the Id and Name labels.

Now run ProjectBilling.RAD and you should have a working master/details view, as shown in the following screenshot:

As you can see, it's easy to set up a working master/details form using these tools. It'd need some tweaking to be exactly the same as the monolithic one but I'm sure you get the idea of how this works compared to the monolithic style.

To finish off the application, add a button and change its Content to Update, Name to UpdateButton, IsEnabled to false, and then double-click on the button to create an event handler called UpdateButton_Click().

Add the following code to updateButton_Click():

private void UpdateButton_Click(object sender, 
    RoutedEventArgs e)
{
    Project selectedProject
        = this.nameComboBox.SelectedItem
        as Project;
    if (selectedProject != null)
    {		        selectedProject.Estimate =
            double.Parse(this.estimateTextBox.Text);
        if (!string.IsNullOrEmpty(
            this.actualTextBox.Text))
        {
            selectedProject.Actual
                = double.Parse(
                    this.actualTextBox.Text);
        }
        SetEstimateColor(selectedProject);
    }
}

This is almost exactly the same code we saw in the previous monolithic example and works exactly the same way.

Next, double-click on the Project combobox to add a SelectionChanged event handler. This will take you from the designer to the newly created event handler. Add the following code to this event handler along with the related SetEstimateColor() method.

Note

You will need to include the System.Windows.Controls and System.Windows.Media namespaces.

private void nameComboBox_SelectionChanged(object sender, 
    SelectionChangedEventArgs e)
{
    ComboBox comboBox = sender as ComboBox;
 
    // If there is a selected item
    if (comboBox != null && comboBox.SelectedIndex > -1)
    {
         Project selectedProject
            = comboBox.SelectedItem as Project;
 
        SetEstimateColor(selectedProject);
        this.UpdateButton.IsEnabled = true;
    }
    else
    {
        this.estimateTextBox.IsEnabled = false;
        this.actualTextBox.IsEnabled = false;
        this.UpdateButton.IsEnabled = false;
    }
}
 
private void SetEstimateColor(Project selectedProject)
{
    if (selectedProject.Actual == 0)
    {
        this.estimateTextBox.Foreground
            = Brushes.Black;
    }
    else if (selectedProject.Actual
        <= selectedProject.Estimate)
    {
        this.estimateTextBox.Foreground
            = Brushes.Green;
    }
    else
    {
        this.estimateTextBox.Foreground
            = Brushes.Red;
    }
 }

The previous code is similar to the monolithic code, except shorter. A lot of the code that was used to update the UI before is now not necessary and has been specified as a part of the XAML.

<Window.Resources>
    <CollectionViewSource x:Key="projectViewSource"
            d:DesignSource="{d:DesignInstance my:Project, 
                                CreateList=True}" />
</Window.Resources>

The projectViewSource is now doing the work we were manually doing before to move data in and out of our details controls, and that is accomplished through the bindings that have been created for us on the details controls.

<TextBox Grid.Column="1" Grid.Row="0" Height="23"
        HorizontalAlignment="Left" Margin="3"
        Name="actualTextBox"
        Text="{Binding Path=Actual, Mode=TwoWay, 
                ValidatesOnExceptions=true, 
                NotifyOnValidationError=true}"
        VerticalAlignment="Center" Width="120" />

Each details control will have a binding configured, as shown previously, to allow for two-way communication with the binding source, which in this case is the Project.Actual that is exposed from the projectViewSource CollectionViewSource class.

Takeaways

Looking at the code we just created, we see a situation that is slightly better than with pure monolithic design. The use of a CollectionViewSource reduced the amount of code that was created and that would need to be maintained and tested. However, the ease with which these controls allow for creating monolithic designs makes them an overall negative for those who care about design. We still have all the problems of monolithic code here that result from tight coupling and poor separation of concerns. However, we now have the additional problem of Visual Studio encouraging that type of design and we still can't easily support multiple dynamic views of our session state.

MVC

As a result of the problems caused by monolithic design, there has been a movement that started in the 70s towards presentational patterns or "Model View" patterns that provide better SoC and better testability. All this began in 1979 when MVC (Model View Controller) was described by Trygve Reenskaug while he was working on Smalltalk at Xerox PARC. Presentation patterns are notoriously flexible and this flexibility is part of what makes them difficult to master. Because of this there have been numerous versions of the MVC pattern; it's out of the scope of this book to cover all the various types of MVCs. What is important to understand is what these MVC patterns generally looked like and what problems they had which led to MVP. The basic structure of MVC is shown in the following diagram:

Note

Over the years MVC has taken many forms and it has evolved to where it is now common for the controllers to have a larger scope than just one widget, and under this newer style you'd more likely have one controller per form or user control instead of per widget. The sample used in this book makes use of the more modern style with one controller per window.

We will now cover the responsibilities of each of the components mentioned earlier and as part of that discussion you will see where the components introduced in the Monolithic section of this chapter fit into the MVC paradigm.

View

The view is responsible for displaying data and collecting user input. The view gets its data from the model including notifications that data has been updated and needs to be refreshed. These notifications are implemented using an observer pattern.

Note

In .NET, events are an implementation of the observer pattern.

When the user interacts with the view through gestures, the view is responsible for collecting those gestures and forwarding them along to the controller for processing.

Controller

The controller is responsible for taking user input and communicating it to the model for processing.

Note

The controller doesn't have to pass user input directly to the model and in many cases will instead communicate input gestures to a service layer or business logic layer for processing, which will then update the existing model or return a new model depending on the architecture. These details will be covered more extensively in the section titled Layered Design later in this chapter.

The main benefit that the controller provides is the ability to remove as much logic as possible into an external component that can be tested using automated tests.

Note

There are many variations of MVC that we will not be covering where the controller is responsible for collecting input from the user including Model 2, which is the pattern that ASP.NET MVC is based on.

Model

In MVC, the model is the in-memory representation of the data that was retrieved from the persistence store (session state). The model is also responsible for notifying the view of changes in state which is generally done with an observer pattern. Abstracting the model in this way allows for easily sharing session state among views, as we will discover shortly in the MVC Project Billing sample section.

Layered design

The design shown in the previous screenshot is an over-simplification of what is generally done in enterprise applications. Enterprise applications are generally separated into three logical layers as shown in the following diagram:

Layering an application in this way provides many benefits including the ability to scale more easily by deploying different layers to different servers and the ability to swap out layers with alternate implementations making the design extensible to change. A full discussion of layered design is outside of the scope of this book. If you'd like to learn more about layered enterprise design then see Chapter 5, Northwind—Commands and User Inputs, and Layered Application Guidelines from Microsoft Application Architecture Guide, 2nd Edition which is freely available online as part of MSDN (http://msdn.microsoft.com/en-us/library/ff650706.aspx).

The layers

The common three-layer design shown in the previous screenshot consists of the following layers:

Presentation layer

The presentation layer is responsible for

  • Displaying data

  • Providing feedback to the user

  • Collecting user input which is passed along to the business logic layer for processing

Separating the presentation in this way provides the benefits of being able to change the UI or provide a second UI without having to duplicate the code in the lower layers if for example you need to provide a thick client, thin client and a command-line version of your application.

Business layer

The business layer or application layer is where the core functionality of the system lives. This logic is called the business logic or domain logic and is applied to the raw data that is fetched from the data access layer for processing. Having the business logic in its own layer allows for scalability as the business logic can be hosted separately from the other layers and allows for extensibility as it provides the flexibility to support multiple types of UIs and multiple types of data stores if needed.

Data layer

The data layer is responsible for pulling data from and pushing data to a data store like a database, service or XML file. Having the data access layer provides the benefit of allowing for change in the data store without having to change code in higher layers.

MVC with layered design

Layered design may seem like a similar idea to MVC and it does have some similar ideas but they are not the same. However, they are generally used together in enterprise architecture, as shown in the following diagram:

As you can see from the previous screenshot, using MVC with layered design results in having the view and controller as part of the presentation layer and the model as part of the business logic layer.

There are various approaches to how the model and business logic can be structured. Martin Fowler describes the most common approaches on his blog and in his book Patterns of Enterprise Application Architecture. The patterns Martin describes include the following:

  • Transaction script: This approach organizes business logic in procedures where each procedure handles a single request from the presentation. Under this design you have one large facade that exposes your business logic through its methods.

  • Domain model: This approach organizes domain logic into an object model of the domain that incorporates both behavior and data. Under this design you have an object graph that mirrors your domain objects and each of these domain or business objects could provide methods for fetching or manipulating data.

  • Table module: Under this design you'd have a model that mirrors the database tables instead of the domain objects.

We have barely scratched the surface here because there are so many ways of organizing business logic and the model. Covering all of the options available for the business layer and model is outside of the scope of this book. If you are interested in learning more see Patterns of Enterprise Application Architecture by Martin Fowler.

MVC Project Billing sample

The increased SoC that comes from implementing MVC will allow us to implement a slightly better version of Project Billing which will support multiple views of the same data with dynamic updates, as shown in the following screenshot:

The classes involved in our MVC design are shown in the following screenshot:

As you can see in the previous screenshot we will have:

  • ProjectsView that keeps a reference to an IProjectsController interface via the ProjectsView.controller field and will keep a reference to an IProjectsModel interface via the ProjectsView.model field

  • ProjectsController class that implements IProjectsController

  • ProjectsModel that implements IProjectsModel

ProjectsView will use its reference to IProjectController to communicate user gestures to the controller by calling ProjectsView.controller.Update(). Internally this will call ProjectsController.model.UpdateProject().

Note

ProjectsView could call ProjectsView.model.UpdateProject() directly, but then the view logic would not be easily testable.

ProjectsView uses its reference to IProjectModel so that it can observe the IProjectsModel.ProjectUpdated event that will be raised after a call to ProjectModel.UpdateProject() finishes updating the model to provide dynamic synchronization of view state with session state (or model data) across all the ProjectsView instances. This design will make it so that when a user clicks on the Update button, all views that are currently open and viewing the same project will get the update and display the new data.

Let's start by creating a new WPF Application project called MvcProjectBilling. Add a project reference to the ProjectBilling.DataAccess.

Model

Add a new class to ProjectBilling.MVC called ProjectsModel andput the following code in it:

using System;
using System.Collections.Generic;
using System.Linq;
using ProjectBilling.DataAccess;

namespace ProjectBilling.Business.MVC
{
    public interface IProjectsModel
    {
        IEnumerable<Project> Projects { get; set; }
        event EventHandler<ProjectEventArgs> ProjectUpdated;
        void UpdateProject(Project project);
    }

    public class ProjectsModel : IProjectsModel
    {
        public IEnumerable<Project> Projects { get; set; }

        public event EventHandler<ProjectEventArgs>
            ProjectUpdated = delegate { };

        public ProjectsModel()
        {
            Projects = new DataServiceStub().GetProjects();
        }

        private void RaiseProjectUpdated(Project project)
        {
            ProjectUpdated(this, 
                new ProjectEventArgs(project));
        }

        public void UpdateProject(Project project)
        {
            Project selectedProject
                = Projects.Where(p => p.ID == project.ID)
                      .FirstOrDefault() as Project;
            selectedProject.Name = project.Name;
            selectedProject.Estimate = project.Estimate;
            selectedProject.Actual = project.Actual;
            RaiseProjectUpdated(selectedProject);
        }
    }

    public class ProjectEventArgs : EventArgs
    {

        public Project Project { get; set; }

        public ProjectEventArgs(Project project)
        {
            Project = project;
        }
    }
}

This code creates a model for consumption by our view. The view uses the observer pattern to get its updates from the model and the controller passes along user input from the view to the model using an IProjectsModel reference.

We have implemented ProjectsModel based on the IProjectModel so that our design is more extensible and allows using dependency injection. Dependency Injection will allow for increased testability as a fake object (mock or stub) can now be provided during unit tests.

The IProjectsModel interface is shown as follows:

    public interface IProjectsModel
    {
        IEnumerable<Project> Projects { get; set; }
        event EventHandler<ProjectEventArgs> ProjectUpdated;
        void UpdateProject(Project project);
    }

IProjectsModel defines the following contract:

  • Projects: It is a collection of projects that was loaded from persisted state

  • ProjectUpdated: It is an event for notifying when a project has its data updated

  • UpdateProject(): It is a method for submitting a project to be updated

The ProjectModel class implements the IProjectModelInterface and uses IDataServices.GetProjects() to fetch the data from our persistence service stub.

Note

The following code is the preferred way of defining events and it allows you to avoid having to check for a null event before raising the event. The code is more concise than the more common null checking pattern and also thread safe.

public event EventHandler<ProjectEventArgs>

ProjectUpdated = delegate { };

Controller

Add a class to the ProjectsBilling.MVC project called ProjectsController and add the code as follows:

using System;
using ProjectBilling.Business.MVC;
using ProjectBilling.DataAccess;
using System.Windows;

namespace ProjectBilling.UI.MVC
{
    public interface IProjectsController
    {
        void ShowProjectsView(Window owner);
        void Update(Project project);
    }

    public class ProjectsController : IProjectsController
    {
        private readonly IProjectsModel _model;

        public ProjectsController(IProjectsModel projectModel)
        {
            if (projectModel == null)
                throw new ArgumentNullException(
                    "projectModel");
            _model = projectModel;
        }

        public void ShowProjectsView(Window owner)
        {
            ProjectsView view 
                = new ProjectsView(this, _model);
            view.Owner = owner;
            view.Show();
        }

        public void Update(Project project)
        {
            _model.UpdateProject(project);
        }
    }
}

We've implemented the controller based on an interface to again take advantage of the benefits of dependency injection. The interface defines the following contract:

  • ShowProjectsView(): It is a method that allows for displaying a ProjectsView to the user

  • Update(): It is a method that allows for updating a project that delegates the updating of the project to the model

    Note

    It was common in older versions of MVC to have the controller responsible for determining the next view and then to display the view. We demonstrated that in the previous code with ShowProjectsView(). This is not a responsibility that always is taken on by the controller in MVC.

View

Add a new window to ProjectBilling.MVC called ProjectsView and add the following code to ProjectView.xaml:

<Window x:Class="ProjectBilling.UI.MVC.ProjectsView"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Projects" MinHeight="180" Height="180"
        MinWidth="250" Width="250" Padding="5"
        FocusManager.FocusedElement
            ="{Binding ElementName=ProjectsComboBox}">
    <UniformGrid Columns="2">
        <Label Content="Project:" />
        <ComboBox Name="ProjectsComboBox" Margin="5"
                SelectionChanged
                    ="ProjectsComboBox_SelectionChanged" />
        <Label Content="Estimated Cost:" />
        <TextBox Name="EstimatedTextBox" Margin="5" 
                 IsEnabled="False" />
        <Label Content="Actual Cost:" />
        <TextBox Name="ActualTextBox" Margin="5" 
                 IsEnabled="False" />
        <Button Name="UpdateButton" Content="Update"
                Margin="5" IsEnabled="False" 
                Click="UpdateButton_Click" />
    </UniformGrid>
</Window>

This XAML creates a simple master/details form, like the one shown in the screenshot at the beginning of the MVC Project Billing sample section.

Note

Coverage of the basics of XAML is outside the scope of this book.

Next, add the following code to ProjectsView.xaml.cs. Start by adding the fields that will hold a reference to the model and controller.

using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using ProjectBilling.Business.MVC;
using ProjectBilling.DataAccess;

namespace ProjectBilling.UI.MVC
{
    public partial class ProjectsView : Window
    {
        private readonly IProjectsModel _model;
        private readonly IProjectsController _controller 
            = null;
        private const int NONE_SELECTED = -1;
    }
}

Initialization

Add the constructor as llows:

public ProjectsView(
    IProjectsController projectsController,
    IProjectsModel projectsModel)
{
    InitializeComponent();
    _controller
        = projectsController;
    _model = projectsModel;
    _model.ProjectUpdated
        += model_ProjectUpdated;
    ProjectsComboBox.ItemsSource 
        = _model.Projects;
    ProjectsComboBox.DisplayMemberPath 
        = "Name";
    ProjectsComboBox.SelectedValuePath 
        = "ID";
}

This constructor allows for dependency injection by taking an interface for the model and controller as parameters. As previously mentioned, this allows for more isolated unit tests as fake objects (mocks or stubs) can be passed in for testing. The constructor:

  1. Wires up the model and controller.

  2. Subscribes to the _model.ProjectUpdated event.

  3. Sets the projectsComboBox.ItemSource to this.Model.Projects and sets the DisplayMemberPath and SelectedValuePath so that they resolve to Project.Name and Project.ID respectively.

Event handlers

Now we will add some event handlers:

  • The following model_ProjectUpdated code will execute when the ProjectsModel.ProjectUpdated event fires:

    void model_ProjectUpdated(object sender,
        ProjectEventArgs e)
    {
        int selectedProjectId = GetSelectedProjectId();
    
        if (selectedProjectId > NONE_SELECTED)
        {
            if (selectedProjectId == e.Project.ID)
            {
                UpdateDetails(e.Project);
            }
        }
    }
  • If the project that was updated is currently displayed in the details of this view, then this code will update the details with the project's new data. This allows for multiple synchronized views of the same data.

  • The ProjectsComboBox_SelectionChanged event handler fires when the user changes the selected project in the ProjectsComboBox. This event gets the selected project and then updates the details controls with the newly selected project's data and then calls UpdateEstimateColor() to set estimateTextBox.Foreground to the appropriate color based on the view logic.

    private void ProjectsComboBox_SelectionChanged(
        object sender, SelectionChangedEventArgs e)
    {
        Project project = GetSelectedProject();
        if (project != null)
        {
            EstimatedTextBox.Text
                = project.Estimate.ToString();
            EstimatedTextBox.IsEnabled = true;
            ActualTextBox.Text
                = project.Actual.ToString();
            ActualTextBox.IsEnabled = true;
            UpdateButton.IsEnabled = true;
            UpdateEstimatedColor();
        }
    }
  • The UpdateButton_Click event handler fires when a user clicks on the UpdateButton. This event handler simply creates a new Project populated with the details data and then passes that project to the controller for processing.

    private void UpdateButton_Click(object sender, 
        RoutedEventArgs e)
    {
        Project project = new Project()
        {
            ID = (int)ProjectsComboBox.SelectedValue,
            Name = ProjectsComboBox.Text,
            Estimate = GetDouble(
                EstimatedTextBox.Text),
            Actual = GetDouble(ActualTextBox.Text)
        };
        _controller.Udate(project); 
    }
Helpers

Add the code that follows as the private helper methods that are called from the event handlers:

  • The UpdateEstimateColor function will look at the values of the details controls and update the EstimateTextBox.Foreground to the appropriate color based on the view logic.

    private void UpdateEstimatedColor()
    {
        double actual
            = GetDouble(ActualTextBox.Text);
        double estimated
            = GetDouble(EstimatedTextBox.Text);
        if (actual == 0)
        {
            EstimatedTextBox.Foreground
                = ActualTextBox.Foreground;
        }
        else if (actual > estimated)
        {
            EstimatedTextBox.Foreground
                = Brushes.Red;
        }
        else
        {
            EstimatedTextBox.Foreground
                = Brushes.Green;
        }
    }
  • The UpdateDetails function takes a project and updates the details controls including calling UpdateEstimateColor() to update the color of estimateTextBox.Foreground.

    private void UpdateDetails(Project project)
    {
        EstimatedTextBox.Text
            = project.Estimate.ToString();
        ActualTextBox.Text
            = project.Actual.ToString();
        UpdateEstimatedColor();
    }
  • Next add the following methods which are self explanatory.

    private double GetDouble(string text)
    {
        return string.IsNullOrEmpty(text) ?
            0 : double.Parse(text);
    }
    
    private Project GetSelectedProject()
    {
        return ProjectsComboBox.SelectedItem
            as Project;
    }
    
    private int GetSelectedProjectId()
    {
        Project project = GetSelectedProject();
        return (project == null)
            ? NONE_SELECTED : project.ID;
    }
MainWindow
  • Update MainWindow.xaml as shown in the following code:

    <Window x:Class="ProjectBilling.UI.MVC.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            Title="Shell" Height="150" Width="150"
            MinHeight="200" MinWidth="200"
            FocusManager.FocusedElement
                ="{Binding ElementName=ShowProjectsButton}">
        <StackPanel>
            <Button Content="Update Projects" 
                    Name="ShowProjectsButton" Margin="5"
                    Click="ShowProjectsButton_Click" />
        </StackPanel>
    </Window>
  • This will create a window with one button that says ShowProjects. Double-click on ShowProjects in cider to create an event handler and then add the following code to MainWindow.xaml.cs.

    using System.Windows;
    using ProjectBilling.Business.MVC;
    
    namespace ProjectBilling.UI.MVC
    {
        /// <summary>
        /// Interaction logic for MainWindow.xaml
        /// </summary>
        public partial class MainWindow : Window
        {
            private IProjectsController _controller;
    
            public MainWindow()
            {
                InitializeComponent();
                _controller 
                    = new ProjectsController(new ProjectsModel());
            }
    
            private void ShowProjectsButton_Click(object sender, 
                RoutedEventArgs e)
            {
                _controller.ShowProjectsView(this);
            }
        }
    }
  • This code will serve as the main window of the application and will show a new ProjectsView each time the Show Projects button is clicked by calling IProjectsController.ShowProjectsView().

How it works

Run the application now. You will see a window like the one shown in the following screenshot:

Each time the Show Projects button is clicked, a ProjectsView will be displayed as shown in the following screenshot:

What's interesting about this architecture is that it's now easy to have updates propagate across views as you can see by following these steps:

  1. Select the Jones account in each view.

  2. Change the Actual Cost to be 1700 in one of the windows.

  3. Click on the Update button in the same window that you changed the Actual Cost in.

You will now see that both open ProjectsView windows update and this is because the model has been abstracted away and the views observes the model. When an update occurs, the windows get their updates in the form of events (the observer pattern). You can also set each window with a different project and then try updating a project in one window. Next, verify that the changes display in the second window when you select the updated project.

Takeaways

MVC makes several improvements over the monolithic approach:

  • The increased SoC created by abstracting out a model allowed for easily supporting multiple synchronized views of the same data.

  • The controller abstraction allows for increased testability of view interactions (gestures).

However, this design also has the following issues:

  • The view logic and the view state are both still tightly coupled in the view leaving them difficult to test or share.

  • If we wanted to do a thin client for the Web in Silverlight, we'd only be able to reuse the model and not the controller and we'd have to duplicate the view logic and view state.

  • The final less obvious issue with this design deals with memory leaks. Details of the memory leaks follow. You can add code to fix the memory leak situation but I've found on the projects that I've worked on that this is often not done and requires higher maintenance than designs that don't rely on events. Having to go through this extra effort required by .NET events makes MVC less desirable than a pattern like MVVM that doesn't require the use of .NET events.

Memory leaks

In the MVC Project Billing Sample the view observes the Model using .NET events, which are an implementation of the observer pattern. One thing to watch out for when using .NET events is memory leaks. This is because unfortunately in .NET, events only support using strong references and not weak references. The issue here is that when an observer object (view in our example) subscribes to an event on a subject object (model in our example), the subject keeps a reference in the form of a delegate (or function pointer) to the observer. In .NET, memory management is handled by the garbage collector and the garbage collector will not collect any object as long as another object has a strong reference to it. This means that our view subscribing to our model's events will cause those models to hold strong references to the views. These strong references will prevent the garbage collector from collecting the views causing the views to leak.

To see this for yourself, update the previous example as follows. Let's start by adding a button to MainWindow.xaml:

<Button Content="GC Collect"
        Name="GCCollectButton" Margin="5"
        Click="GCCollectButton_Click" />

Next add gcCollectButton_Click to MainWindow.xaml.cs as follows:

private void GCCollectButton_Click(object sender, 
    RoutedEventArgs e)
{
    GC.Collect();
    GC.WaitForPendingFinalizers();
    GC.Collect();
}

Note

You will need to add the System namespace to use GC.

This code will call GC.Collect() twice and GC.WaitForPendingFinalizers() once. The .NET garbage collector is non-deterministic so there are no guarantees about when it will collect but I find this combination works pretty well at getting it to collect.

Now let's add a finalizer to ProjectsView.xaml.cs as follows.

Note

Finalizers are called when an object is collected by the garbage collector. Full coverage of .NET memory management is out of the scope of this book. See C# Via CLR by Jeffery Richter for more details.

~ProjectsView()
{
    MessageBox.Show("ProjectsView collected");
}

Now when a ProjectsView instance is collected by the garbage collector, a message box will pop up and we will know that it was collected.

Go ahead, run the application and follow these steps:

  1. Click on the GC Collect button as shown in the previous screenshot.

  2. Open a few ProjectsViews by clicking on the Show Projects button and then close them.

  3. Click on the GC Collect button, in fact click it a few times. Try all you want, you will not be able to get the finalizers to execute from the views that you created because the ProjectsModel instance is holding a reference to them.

You will never see the ProjectsView collected message box displayed under this design. If you used a memory profiler, you'd see that after each window is closed the memory used by the application doesn't decrease.

To fix this situation add the following code to ProjectsView.xaml.cs and then re-run the application repeating the steps listed previously.

protected override void OnClosed(EventArgs e)
{
    base.OnClosed(e);
    _model.ProjectUpdated -= model_ProjectUpdated;
}

Now you will see that when you click on GC Collect the finalizers will execute. This isn't a lot of code to correct this situation but developers do tend to get this wrong from time to time and it can make the code higher maintenance than a design that doesn't require .NET events.

Note

Microsoft recommends using the weak event pattern to deal with this situation (http://msdn.microsoft.com/en-us/library/aa970850.aspx). However, I prefer the WeakEvent class found in CLR via C# by Jeffery Richter because it's a much lower maintenance approach than the weak event pattern and Richter's WeakEvent classes are used almost exactly like regular CLR events, so they require very little training. Please check Richter's blog for the latest version of this code which contains bug fixes to the published version.. These topics will not be covered in this book but feel free to dig deeper on your own.

MVP

MVP or Model View Presenter is a pattern that first appeared at IBM and then emerged more prominently at Taligent in the 1990's. MVP was a derivative of MVC that took a slightly different approach. Under MVP, the view is no longer required to observe the model.

Note

Martin Fowler officially retired the MVP pattern on his blog and replaced it with two variations, Passive View and Supervising Controller. Passive view is what is shown in the following screenshot. Under Supervising Controller, the view still observes the model via an observer but with a much more limited scope than under MVC. For full details see Martin's blog (http://martinfowler.com/eaaDev/ModelViewPresenter.html).

The following diagram shows the basic structure of MVP:

Note

It's more common for presenters to have a larger scope than a single UI widget and to instead have one presenter per form or user control.

As you can see in the previous diagram, the presenter has taken the place of the controller in the triad and is responsible for moving user input from the view to the model as well as being responsible for updating the view about changes that occur in the model. The presenter communicates with the view through an interface which allows for increased testability as the model can be replaced by a fake object (mock or stub) for unit tests. The following diagram shows MVP in a layered architecture:

MVP Project Billing sample

We will create an application with the classes shown in the following screenshot:

As you can see our view now consists of a class, ProjectsView that implements an interface, IProjectsView. We will be implementing the Passive View version of MVP and so IProjectsView contains everything needed to update the view and to communicate user gestures into the presenter. This allows for maximum test coverage under the MVP paradigm.

The presenter, ProjectsPresenter, takes an IProjectsView and an IProjectsModel as constructor arguments and keeps references to them in ProjectsPresenter.view and ProjectsPresenter.model respectively as shown previously.

This design requires that communications go through the presenter. For example, when the user clicks on the updateButton, this will cause the IProjectView.ProjectUpdated event to be raised, which will call the ProjectsPresenter.view_ProjectUpdated() event handler. ProjectsPresenter.view_ProjectUpdated will in turn call ProjectsPresenter.model.UpdateProject() which will update the model data and then raise the IProjectsModel.ProjectUpdated event to notify presenters that the model has been updated. The Presenters will then call IView.UpdateProject(), which will take care of updating the view state and applying view logic to set the color of estimateTextBox if necessary. This will allow for dynamic updates to session data across multiple views just like in MVC. However, this design allows for better test coverage than under MVC because the view is passive and we've moved the view logic and view state out of the view and into the presenter. We've also removed direct communication between the view and model and all of the communication is handled through the presenter.

Note

Having no interaction between the model and view is a detail that is specific to the passive view version of MVP. In supervising controller the model and view can still communicate directly.

Let's start by adding a new WPF Application project called ProjectBilling.MVP to the solution and then add a project reference to the ProjectBilling.DataAccess project.

Model

Add a class called ProjectsModel and add the following code:

using System;
using System.Collections.Generic;
using System.Linq;
using ProjectBilling.DataAccess;

namespace ProjectBilling.Business
{
    public class ProjectEventArgs : EventArgs
    {
        public Project Project { get; set; }
        public ProjectEventArgs(Project project)
        {
            Project = project;
        }
    }

    public interface IProjectsModel
    {
        void UpdateProject(Project project);
        IEnumerable<Project> GetProjects();
        Project GetProject(int Id);
        event EventHandler<ProjectEventArgs> ProjectUpdated;
    }

    public class ProjectsModel : IProjectsModel
    {

        private IEnumerable<Project> projects = null;

        public event EventHandler<ProjectEventArgs> 
            ProjectUpdated = delegate { };

        public ProjectsModel()
        {
            projects = new DataServiceStub().GetProjects();
        }

        public void UpdateProject(Project project)
        {
            ProjectUpdated(this, 
                new ProjectEventArgs(project));
        }

        public IEnumerable<Project> GetProjects()
        {
            return projects;
        }


        public Project GetProject(int Id)
        {
            return projects.Where(p => p.ID == Id)
                .First() as Project;
        }
    }
}

Our ProjectsModel implements the IProjectsModel interface for better testability via dependency injection and implements the following contrct.

  • UpdateProject(): This method allows for updating a project across the session state. This design could be extended to support updates across persisted state, but covering that is outside the scope of this book.

  • GetProjects(): This method will return all projects in the session state.

  • GetProject(): This method is given a project ID and will return a project from session state.

  • ProjectUpdated: This event will fire when a project has been updated in the session state.

The ProjectEventArgs class is for our events to use to communicate which project has changed.

View

Add a window called ProjectsView and add the following code to ProjectsView.xaml:

<Window x:Class="ProjectBilling.UI.MVP.ProjectsView"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Projects" MinHeight="180" Height="180"
        MinWidth="250" Width="250" Padding="5"
        FocusManager.FocusedElement
            ="{Binding ElementName=ProjectsComboBox}">
    <UniformGrid Columns="2">
        <Label Content="Project:" />
        <ComboBox Name="ProjectsComboBox" Margin="5"
                SelectionChanged
                    ="ProjectsComboBox_SelectionChanged" />
        <Label Content="Estimated Cost:" />
        <TextBox Name="EstimatedTextBox" Margin="5" 
                 IsEnabled="False" />
        <Label Content="Actual Cost:" />
        <TextBox Name="ActualTextBox" Margin="5" 
                 IsEnabled="False" />
        <Button Name="UpdateButton" Content="Update"
                Margin="5" IsEnabled="False" 
                Click="UpdateButton_Click" />
    </UniformGrid>
</Window>

With the exception of the namespace declaration, this XAML file is exactly the same as that found in the MVC ProjectsView.xaml.

Next, add the following code to the ProjectsView.xaml.cs file.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using ProjectBilling.Business;
using ProjectBilling.DataAccess;

namespace ProjectBilling.UI.MVP
{
    public interface IProjectsView
    {
        int NONE_SELECTED { get; }
        int SelectedProjectId { get; }
        void UpdateProject(Project project);
        void LoadProjects(IEnumerable<Project> projects);
        void UpdateDetails(Project project);
        void EnableControls(bool isEnabled);
        void SetEstimatedColor(Color? color);
        event EventHandler<ProjectEventArgs> ProjectUpdated;
        event EventHandler<ProjectEventArgs> DetailsUpdated;
        event EventHandler SelectionChanged;
    }

    /// <summary>
    /// Interaction logic for ProjectsView.xaml
    /// </summary>
    public partial class ProjectsView : Window, IProjectsView
    {

        public int NONE_SELECTED { get { return -1; } }
            public event EventHandler<ProjectEventArgs> 
            ProjectUpdated = delegate { };
        public int SelectedProjectId { get; private set; }

        public event EventHandler SelectionChanged
            = delegate { };
        public event EventHandler<ProjectEventArgs>
            DetailsUpdated = delegate { };

        public ProjectsView()
        {
            InitializeComponent();
            SelectedProjectId = NONE_SELECTED;
        }

    }
}

This code creates the IProjectsView interface, the fields and the events that are needed by ProjectsView. ProjectsView will implement the IProjectView and the contract described as follows:

  • NONE_SELECTED: This read-only property returns a constant that is used to determine if SelectedProjectID currently has a selection

  • SelectedProjectId: This read-only property returns the currently selected project ID

  • UpdateProject(): This function allows to update a project in the view state and will take care of updating the details view if needed

  • LoadProjects(): This function allows for populating the projectsComboBox for the first time

  • UpdateDetails(): This function allows for updating the details view

  • EnableControls(): This function allows for setting the details controls and the IsEnabled property of updateButton to enable and disable these controls

  • SetEstimatedColor(): This function allows for setting the text color of estimatedTextBox

  • ProjectUpdated: This event will notify the presenter that the user clicked on the updateButton

  • DetailsUpdated: This event will notify the presenter that the details have changed so that the presenter can update the text color of estimatedColor

  • SelectionChanged: This event will notify the presenter that the current selection has changed in the projectsComboBox

Event handlers

Now let's add the event handls that we will need:

  • The UpdateButton_Click function will fire when a user clicks on UpdateButton and will create a new project, populate it with the details control data, and then raise the IProjectsView.ProjectUpdated event passing the new project to the constructor of the new ProjectEventArgs that is being passed with the event.

    private void UpdateButton_Click(object sender,
        RoutedEventArgs e)
    {
        Project project = new Project();
        project.Estimate =
            GetDouble(EstimatedTextBox.Text);
        project.Actual =
            GetDouble(ActualTextBox.Text);
        project.ID =
            int.Parse(
                ProjectsComboBox.SelectedValue.
                    ToString());
        ProjectUpdated(this, 
            new ProjectEventArgs(project));
    }
  • The ProjectsComboBox_SelectionChanged() function will fire when the selection changes in the ProjectsComboBox and it simply raises the IProjectsView.SelectionChanged event to notify the presenter so that it can update the view as needed.

    private void ProjectsComboBox_SelectionChanged(
        object sender, SelectionChangedEventArgs e)
    {
        SelectedProjectId
            = (ProjectsComboBox.SelectedValue == null)
                    ? NONE_SELECTED
                    : int.Parse(
                        ProjectsComboBox.SelectedValue.
                            ToString());
        SelectionChanged(this,
            new EventArgs());
    }
Public methods

Add te following public methods:

  • The UpdateProject function allows for updating a project in the view state by first finding the project in the ProjectsComboBox.ItemsSource using a little Linq. It then updates the project and if it's the project that is currently selected, it calls UpdateDetails() to update the details controls.

    public void UpdateProject(Project project)
    {
        // Null checks excluded
        IEnumerable<Project> projects =
            ProjectsComboBox.ItemsSource as
                IEnumerable<Project>;
        Project projectToUpdate =
            projects.Where(p => p.ID == project.ID)
                .First();
        projectToUpdate.Estimate = project.Estimate;
        projectToUpdate.Actual = project.Actual;
        if (project.ID == SelectedProjectId)
            UpdateDetails(project);
    }
  • The LoadProjects function allows for loading a collection of Projects as the ItemsSource for projectsComboBox.

    public void LoadProjects(IEnumerable<Project> projects)
    {
        ProjectsComboBox.ItemsSource = projects;
        ProjectsComboBox.DisplayMemberPath = "Name";
        ProjectsComboBox.SelectedValuePath = "ID";
    }
    
  • The EnableControls function allows for setting the IsEnabled state of the details controls and updateButton.

    public void EnableControls(bool isEnabled)
    {
        EstimatedTextBox.IsEnabled = isEnabled;
        ActualTextBox.IsEnabled = isEnabled;
        UpdateButton.IsEnabled = isEnabled;
    }
    
  • The SetEstimatedColor function takes a color and will update the estimateTextBox.Foreground color to be the passed in color.

    public void SetEstimatedColor(Color? color)
    {
        EstimatedTextBox.Foreground
            = (color == null)
                    ? ActualTextBox.Foreground
                    : new SolidColorBrush((Color)color);
    }

    Note

    Note that this function doesn't contain view logic and that it's the presenter's responsibility to calculate the correct color.

  • The UpdateDetails function will update the details controls with the data contained in the project that is passed in.

    public void UpdateDetails(Project project)
    {
        EstimatedTextBox.Text
            = project.Estimate.ToString();
        ActualTextBox.Text
            = project.Actual.ToString();
        DetailsUpdated(this,
            new ProjectEventArgs(project));
    }
Helpers

The model_ProjectUpdated function will get a double from textpassed in taking care of null/empty checks.

private double GetDouble(string text)
{
    return string.IsNullOrEmpty(text)
        ? 0 : double.Parse(text);
}

Presenter

Add a class called ProjetsPresenter and add the following code to it:

using System;
using System.Windows.Media;
using ProjectBilling.Business;
using ProjectBilling.DataAccess;

namespace ProjectBilling.UI.MVP
{
    public class ProjectsPresenter
    {
        private readonly IProjectsView _view = null;
        private readonly IProjectsModel _model = null;

        public ProjectsPresenter(IProjectsView projectsView, 
            IProjectsModel projectsModel)
        {
            _view = projectsView;
            _view.ProjectUpdated += view_ProjectUpdated;
            _view.SelectionChanged 
                += view_SelectionChanged;
            _view.DetailsUpdated += view_DetailsUpdated;
            _model = projectsModel;
            _model.ProjectUpdated += model_ProjectUpdated;
            _view.LoadProjects(
                _model.GetProjects());
        }
 
    }
}

As you can see the presenter takes IProjectsView and IProjectsModel as constructor arguments and then subscribes to various events and the loads projects into the view from the model with the following code:

this.view.LoadProjects(
    this.model.GetProjects());
Event handlers
  • The view_DetailsUpdated function is called in response to the IProjectsView.DetailsUpdated event and simply calls SetEstimateColor() to update the color of the estimateTextBox.Foreground. This allows the view logic to be easily tested.

    private void view_DetailsUpdated(object sender, 
        ProjectEventArgs e)
    {
        SetEstimatedColor(e.Project);
    }
  • view_SelectionChanged will be called in response to the IProjectsView.SelectionChanged event firing performs the view logic of updating the details controls after a the user changes the selected project. Again, this design allows this view logic to be easily tested.

    private void view_SelectionChanged(object sender, 
        EventArgs e)
    {
        int selectedId = _view.SelectedProjectId;
        if (selectedId > _view.NONE_SELECTED)
        {
            Project project =
                _model.GetProject(selectedId);
            _view.EnableControls(true);
            _view.UpdateDetails(project);
            SetEstimatedColor(project);
        }
        else
        {
            _view.EnableControls(false);
        }
    }
  • model_ProjectUpdated will be called in response to the IProjectsModel.ProjectUpdated event firing and will allow for propagating the changes to session state made in one view to the other views.

    private void model_ProjectUpdated(object sender,
        ProjectEventArgs e)
    {
        _view.UpdateProject(e.Project);
    }
  • The view_ProjectUpdated function will fire in response to the IProjectsView.ProjectsUpdated event and will notify the model so that it can update the project in the session state and also calls SetEstimatedColor() to perform the view logic for updating the color of estimateTextBox.Foreground if needed.

    private void view_ProjectUpdated(object sender, 
        ProjectEventArgs e)
    {
        _model.UpdateProject(e.Project);
        SetEstimatedColor(e.Project);
    }
Helpers

Now add the following helper method:

  • The SetEstimateColor performs the view logic needed to set estimateColor.Foreground to the appropriate color and then calls IProjectsView.SetEstimatedColor() to apply the needed color again allowing for the view logic to be easily tested.

    private void SetEstimatedColor(Project project)
    {
        if (project.ID == _view.SelectedProjectId)
        {
            if (project.Actual <= 0)
            {
                _view.SetEstimatedColor(null);
            }
            else if (project.Actual 
                        > project.Estimate)
            {
                _view.SetEstimatedColor(Colors.Red);
            }
            else
            {
                _view.SetEstimatedColor(Colors.Green);
            }
        }
    }
    

Main window

Add the following code to MainWindow.xaml:

<Window x:Class="ProjectBilling.UI.MVP.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Shell" Height="150" Width="200"
        MinHeight="150" MinWidth="200"
        FocusManager.FocusedElement
            ="{Binding ElementName=ShowProjectsButton}">
    <StackPanel>
        <Button Content="Show Projects"
                Name="ShowProjectsButton" Margin="5"
                Click="ShowProjectsButton_Click" />
    </StackPanel>
</Window>

With the exception of the namespace declaration, this XAML code is exactly the same as the one found in the MVC MainWindow.xaml.

Next, add the following code to MainWindow.xaml.cs:

using System.Windows;
using ProjectBilling.Business;

namespace ProjectBilling.UI.MVP
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        private IProjectsModel _model = null;

        public MainWindow()
        {
            InitializeComponent();
            _model = new ProjectsModel();
        }

        private void ShowProjectsButton_Click(object sender,
            RoutedEventArgs e)
        {
            ProjectsView view = new ProjectsView();
            ProjectsPresenter presenter 
                = new ProjectsPresenter(view, _model);
            view.Owner = this;
            view.Show();
        }
    }
}

The constructor creates a model that will be shared across all views. The model is used to initialize and update session state.

ShowProjectsButton_Click() will be called when the ShowProjectsButton is clicked and it will instantiate a new view and pass the new view instance as an argument to the constructor for a new ProjectsPresenter instance along with a reference to MainWindow.model. ShowProjectsButton_Click will then show the view by calling view.Show().

How it works

Running the application you will see that it works the same as our previous MVC application.

Takeaways

MVP represents a big improvement over MVC in a few ways:

  • It provides testable view state and view logic by moving them into the presenter allowing the view logic to be easily tested.

  • It decouples the view from the model by requiring communication to go through the presenter. Unlike MVC, MVP allows for reuse of the view logic and this is achieved by moving the logic into a presenter and having the presenter communicate with the view through an interface. Now if you wanted to implement a Silverlight version of this application, you would only need to create a view in Silverlight that implements IProjectsView and could reuse IProjectsPresenter and IProjectsModel.

However there are still a few issues as follows:

  • We still use a lot of events, and as shown in the Memory Leaks section previously, events can cause memory leaks and end up causing code to be higher maintenance than a design that doesn't require events.

  • There is still a lot of untested code in the view.

These short comings are all motivators for MVVM or presentation model and we will look at how MVVM addresses each of these issues in the next chapter.

lock icon The rest of the chapter is locked
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at $19.99/month. Cancel anytime
Banner background image