Introduction
This article demonstrates using a model view presenter (MVP) pattern in a sample program. I analyzed some articles on MVP and found that each article is different from others in some way or the other. This might be due to the availability of variants and approach to implement this pattern. This article does not focus on details, rather it just explains the program flow. For more details, please refer to the link below:
(Microsoft MSDN site has many useful articles on MVP.)
Note: The implementation of the pattern is based on Unity application block. It is used for IoC (Inversion of Control) and Dependency injection needs of the application. There are other ways to achieve the same.
Basics
Model View Presenter is a design pattern and considered to be a derivative of the Model-view-controller. It aims to facilitate test-driven development and helps separate the responsibilities for the visual display and the event handling behaviour of the system into different classes.
A view class manages the controls on the form, and it forwards events to a presenter class. The presenter contains the logic to respond to the events, and in turn, manipulates the state of the view. The presenter class uses the model (business entitles) to determine how to respond to the events.
When the model is updated, the view also has to be updated to reflect the changes. There are several approaches to do this. The following two approaches are widely used:
- Passive View
- Supervising Controller
Figure-1: Supervising Controller
In Passive View, the presenter updates the view to reflect changes in the model. In Supervising Controller, the view interacts directly with the model to perform simple data-binding that can be defined declaratively, without presenter intervention. The attached sample example uses the Supervising Controller variant.
Prerequisite
This example uses WPF and Unity Application Block (Enterprise Library 4.1) as IoC container.
- WPF – Presentation technology
- Unity IoC Container – This container can be used to provide dependency injection.
- Basic knowledge of Data Binding technique
Program Flow
A very simple example is used here for demonstration purpose. However code can become very complex when heavy controls are used with data binding.
The form in our example contains a textbox
to hold a customer name and a button AddCustomer
to add a new one. The project contains two folders Common and Customer. Common folder contains base classes and Customer folder has the Model
, View
and Presenter
classes. These represent the Customer
.
Creating and Initializing Unity IoC container
Class ContainerAccessor
is used to create the Unity Dependency Injection Container. We can configure IoC container to read type mappings (Interface and class implementing the interface mapping) from configuration file. As a sample case, I have declared two types in configuration file under <unity>section.
<unity>
<containers>
<container>
<types>
<type
type="CustomerInfoObserver.ICustomerDataSource, CustomerInfoObserver"
mapTo="CustomerInfoObserver.CustomerDataSource, CustomerInfoObserver"/>
<type
type="CustomerInfoObserver.ILogger, CustomerInfoObserver"
mapTo="CustomerInfoObserver.Logger, CustomerInfoObserver"/>
</types>
</container>
</containers>
</unity>
Similarly we can register any services in the configuration file to be used horizontally from across the application. For example, Microsoft Enterprise Library, Data Access Service, etc. Container will resolve / create instances automatically for defined types and inject dependent objects that have been requested by them through constructor.
The following code snippet creates the instance of container and configures it by reading unity section from configuration file.
_container = new UnityContainer();
UnityConfigurationSection section =
(UnityConfigurationSection)ConfigurationManager.GetSection("unity");
section.Containers.Default.Configure(_container);
Unity IoC and Model-View-Presenter
Base Class as a Generic type for All Views – BaseView
In general, view gets the instance of presenter
class and invokes methods to delegate the work. Since taking the instance of presenter
in every view is common, we will write it in the base class called BaseView
. BaseView
is defined as generic type and receives two parameters, one is view
type and another one is presenter
type. The base class uses IoC container to resolve the presenter
instance and injects required services to it.
public class BaseView<TView, TPresenter> : System.Windows.Window
where TPresenter : BasePresenter<TView>
where TView : class
{
public TPresenter Presenter { get; set; }
public BaseView()
{
ContainerAccessor containerAccessor = new ContainerAccessor();
UnityContainer container = containerAccessor.GetContainer();
if (container == null) throw new InvalidOperationException
("Cannot find UnityContainer");
Presenter = container.Resolve<TPresenter>();
Presenter.View = this as TView;
}
}
The base class inherits System.Windows.Window
that enables all derived classes from BaseView
to be a window.
Derived View Class – CustomerView
The WPF window (view) derives from BaseView
base class, which is doing all the work to inject the Presenter
into the View
using Unity. While implementing the mandatory interface ICustomerView
, CustomerView
is also specifying which presenter is to be used through generic parameters.
In Model-View-Presenter pattern, presenter
will always use interface to communicate with view
.
public partial class CustomerView
: BaseView<icustomerview, />, ICustomerView
You will also have to change the XAML code associated with CustomerView
class as below. This modified code removes the inheritance from System.Windows.Window
and has it derived from CustomerInfoObserver:BaseView
. See the changes required in xmlns namespace.
<CustomerInfoObserver:BaseView x:Class="CustomerInfoObserver.CustomerView"
x:TypeArguments="CustomerInfoObserver:ICustomerView,
CustomerInfoObserver:CustomerPresenter"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:CustomerInfoObserver="clr-namespace:CustomerInfoObserver"
Title="Window1" Height="255" Width="341"
>
The Window/View receives the Click Event of the AddCustomer
button and passes the request to the Presenter
. In this case I am delegating requests to the presenter
. I could have just as easily had the Presenter
class subscribe to various View Events.
private void btnAddCustomer_Click(object sender, RoutedEventArgs e)
{
Presenter.OnAddCustomer(txtCustomerName.Text);
}
Base Class as a Generic type for All Presenters – BasePresenter
Being the base class for every presenter
exposes view through property named View.
Derived Presenter Class – CustomerPresenter
This class handles all the requests received from CustomerView
(view). On initialization, presenter
passes model object to the view by calling interface method SetCustomerModelAsDataContext()
.
View.SetCustomerModelAsDataContext(_customerModel);
CustomerView
class sets this model as DataContext
for the entire window, so all child UI elements will use the same DataContext
for binding. The syntax for data binding is given below. The code is available in CustomerView
XAML file.
Text="{Binding Path=CustomerName, Mode=TwoWay, UpdateSourceTrigger=LostFocus}
Using data binding in MVP saves much coding effort and facilitates synchronization between Data Model and View.
Model Class – CustomerModel
Class CustomerModel
contains business data and that is exposed through properties. Since we have used data binding feature and associated windows element (textbox
- txtCustomerName
) with CustomerModel
property (CustomerName
), change made to the txtCustomerName textbox
will immediately reflect into CustomerName
property. In order to reflect the change back to the screen, CustomerModel
class has to implement INotifyPropertyChanged
interface. INotifyPropertyChanged
interface will notify the view that Model value has been changed and can update the UI.
INotifyPropertyChanged
interface is also implemented by BusinessEntity
class in Embassy.
Note: The code is provided with the attached zip file.
History
- 25th December, 2008: Initial version