![]() |
Desktop Development »
Smart Client »
General
Advanced
License: The Code Project Open License (CPOL)
Presentation Model in ActionBy Yiyi SunUse the Presentation Model pattern in ASP.NET Web site, Windows Forms and WPF |
C# (C# 3.0), .NET (.NET 3.5), ASP.NET, Win32, Visual Studio (VS2008), WPF, Architect
|
|
Advanced Search |
|
|
|
||||||||||||||||
The Model-View-Controller (MVC) is the famous design pattern that separates the presentation and the domain/data access. Further more MVC also splits presentation logic into view and controller.
Presentation Model and Model View Presenter (MVP) are variations on model-view-controller (MVC) design pattern where the controller becomes Presentation Model and Presenter and has a different way to organize state, logic and references.
Model View Presenter has two flavors, named Supervising Controller and Passive View. They share the same key features.
Presentation Model is a pattern that pulls presentation behavior from a view. It has a centralized place to store state/data and centralized event source which is completely independent to the views used for display.
In real life projects, I prefer using the Presentation Model. This article is going to show the design journey of designing a use case of managing the Customer business entity and shows how the Presentation Model fits into ASP.NET Web site, Windows Form application and WPF application.
Let’s start with analyzing the requirements of a fictional business entity, Customer management application.
Many business management processes can be abstracted as the following stories:
Our story is about to manage Customer, the sample business entity. The use case can be described as below.
Search and view customer information
Users search customers and view details of selected customer.
User launches the application
The application can be designed as an ASP.NET Web site, a Windows Form application or a WPF application. This article will show all three application forms using the Presentation Model. To begin the design, first have a look at some user interface options on the Web site.
On a Web site, usually there are three options to implement this customer management.
This option is to display the customer list with a hyperlink on each row. When the hyperlink is clicked , a new popup window shows up with customer details in it.
While the first option is a commonly used pattern in many earlier Web applications, more recent Web applications show the details in the same window to avoid popup.
Similar to Option 2, there won't be popup, further more this option does not hide the list while showing the details either. It shows the list and details together.
The user experience designer usually decides which option is to be used in final products. The decision is based on users’ feedback and UI layout and look and feel designs.
The decision could be changed in the middle of the project. E.g. initially option 1 was chosen. Then users reported they blocked popup, so it has to be option 2. Finally, since the layout and font make the screen have quite a big space, designer may come to ask if we can display the list and details at the same time. It then becomes option 3.
Architectural design should foresee the potential requirement changes and have minimum impacts on code in case of the changes. Another word is to say the code should be reusable as much as possible.
Good news is that we can use user controls. User control is a great technology to achieve reusability. It is known as application building blocks. It created a customer list user control and a customer details user control. All three options can be built upon them.
In option 1, a page hosts customer list user control. A popup page hosts customer details user control.
In option 2, a page hosts customer list user control and customer details user control. When a customer is selected, hide the customer list user control and show the customer details user control.
In option 3, a page hosts customer list user control and customer details user control. When a customer is selected, show the customer details user control. Sometimes it may need to scroll the screen to the top of the customer details area.
Good news again is that no matter how screen layout or window arrangement changes, the data model behind the scene is actually the same. It is a customer list and a selected customer.
We need a class to hold the customer list and a selected customer. If the customer list changed in case the user typed in search text, it sends out an event saying customer list changed. If user selected a customer, it sends out an event saying selected customer changed.
User controls connect to this class and pull the customer list or selected customer to data bind to UI elements, such as grid view or form view.
User controls also listen to the events this class sends out. If the customer list changed, re-bind the grid view. Or if the selected customer changed, re-bind the form view.
Very naturally, the Presentation Model pattern comes into the picture. The class described above is exactly the Presentation Model, we name it CustomerPresentationModel.
The CustomerPresentationModel class has properties to hold the customer list and selected customer data.
public interface ICustomerPresentation : INotifyPropertyChanged
{
IEnumerable<Customer> Items { get; set; }
Customer SelectedItem { get; set; }
int ItemCount { get; set; }
}
In order to send out an event, we can define a custom event handler. But since Windows Forms data bind and WPF data bind rely on INotifyPropertyChanged interface, we use this interface.
The CustomerPresentationModel class is a subclass of the generic Presentation Model class, PresentationModel<T>.
public class CustomerPresentationModel :
PresentationModel<Customer>, ICustomerPresentation
{
//...
}
public abstract class PresentationModel<T> : INotifyPropertyChanged
{
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
protected int itemCount;
public int ItemCount
{
get { return itemCount; }
set { itemCount = value; NotifyPropertyChanged("ItemCount"); }
}
protected IEnumerable items;
public virtual IEnumerable Items
{
get { return GetItems(); }
set { items = value; NotifyPropertyChanged("Items"); }
}
protected abstract IEnumerable GetItems();
private T selectedItem;
public virtual T SelectedItem
{
get { return selectedItem; }
set { selectedItem = value; NotifyPropertyChanged("SelectedItem"); }
}
protected void Reset()
{
Items = null;
SelectedItem = default(T);
}
}
PresentationModel<T> class is a base class using generics. With this class, we can apply the Presentation Model design pattern to all kinds of business entities, such as Order, Products, etc. in the future.
For now, we will focus only on the CustomerPresentationModel and on using it to build three types of applications.
The steps to build the ASP.NET Web site are:
|
|
|
These are straight forward steps, but there are some points of interests.
ASP.NET Web site can be stateless meaning that objects are typically created and destroyed to serve each request. In this case, due to the fact that Presentation Model is accessed by multiple user controls, it is expected to be created at the beginning of the request and live until the end of the request. But while ASP.NET Framework provides application scope storage and session storage to store custom objects, unlike JSP, it does not have a request scope storage. So we will store the CustomerPresentationModel in the session.
public static CustomerPresentationModel Instance
{
get
{
CustomerPresentationModel instance = HttpContext.Current.Session["_c_"]
as CustomerPresentationModel;
if (instance == null)
{
instance = new CustomerPresentationModel();
HttpContext.Current.Session["_c_"] = instance;
}
return instance;
}
}
ASP.NET has a great declarative databinding model against plain .NET objects. It is done through the ASP.NET ObjectDataSource control which connects the data-bound controls such as the GridView, FormView, or DetailsView controls to objects.
<asp:GridView .... DataSourceID="CustomerListDataSource" DataKeyNames="Id">
<Columns>
...
</Columns>
</asp:GridView>
<asp:ObjectDataSource ... DataObjectTypeName="Demo.DataModel.Customer"
TypeName="CustomerPresentationModel" SelectMethod="GetCustomerList"
OnObjectCreating="CustomerListDataSource_ObjectCreating" />
ObjectDataSource exposes a TypeName property that specifies an object type (class name) for performing data operations. In our case it is the CustomerPresentationModel.
By default, ObjectDataSource will instantiate CustomerPresentationModel instance through a default constructor (no arguments), but we already have CustomerPresentationModel object in the session. Fortunately, we can handle the ObjectCreating event to assign CustomerPresentationModel object from session to the ObjectInstance property of ObjectDataSource.
protected void CustomerDataSource_ObjectCreating
(object sender, ObjectDataSourceEventArgs e)
{
e.ObjectInstance = CustomerPresentationModel.Instance;
}
When data-bound controls need data, ObjectDataSource calls into the CustomerPresentationModel instance’s method GetCustomerList (declared in the ObjectDataSource’s SelectMethod property). That method should return any Object or IEnumerable list, collection, or array.
CustomerPresentationModel exposes the customer list and selected customer as properties of, not method, so here we need a little wrapping in order to work with the ObjectDataSource.
public IEnumerable<Customer> GetCustomerList()
{
return Items;
}
public Customer GetCustomer()
{
return SelectedItem;
}
The user controls listen to Presentation Model’s events. The customer list user control is monitoring the “items changed” event to re-bind the grid view. The customer details user control is similarly monitoring the “selected item changed” event. This is not only to refresh the form view, but also to hide itself if the selected item in the CustomerPresentationModel is null.
In CustomerEdit.ascx.cs,
void pm_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "SelectedItem")
{
if (pm.GetCustomer() != null)
{
CustomerFormView.DataBind();
Visible = true;
}
else
{
Visible = false;
}
}
}
Event driven makes option 2 and option 3 very easy to implement. In option 2, the logic is if there is no customer selected, show the list and hide the details. In option 3, no extra logic is needed.
When user searches the customer list, CustomerPresentationModel sends out event “items changed” event to notify customer list user control to refresh accordingly.
In Customer2.aspx.cs,
void pm_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "SelectedItem")
{
this.CustomerList1.Visible = pm.GetCustomer() == null;
}
}
The benefit of this event driven mechanism is that there potentially could be more than one user controls to be notified and refreshed. And it also provides a solution to synchronize state of different user controls.
Looking into the code of CustomerList.ascx.cs and CustomerEdit.ascx.cs, you can find repeated code, like:
CustomerPresentationModel pm;
protected void Page_Load(object sender, EventArgs e)
{
pm = CustomerPresentationModel.Instance;
System.Diagnostics.Debug.Assert(pm != null);
if (pm != null)
{
pm.PropertyChanged += new PropertyChangedEventHandler(pm_PropertyChanged);
}
}
protected void Page_Unload(object sender, EventArgs e)
{
System.Diagnostics.Debug.Assert(pm != null);
if (pm != null)
{
pm.PropertyChanged -= new PropertyChangedEventHandler(pm_PropertyChanged);
}
}
void pm_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
}
If there is a Dependency Injection Framework, then the above code can be simplified to be something look like this:
[PresentationMode(Scope= Session)]
CustomerPresentationModel pm;
[PropertyChangedEventSubscription]
void pm_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
}
I am looking for a light weight Dependency Injection Framework that can be used in a Web application.
We have created the ASP.NET Web site. It’s time to create a Windows Forms application.
The steps used in the ASP.NET Web site apply here which includes:
|
|
|
In Windows Forms application, CustomPresentationModel can live as a singleton. The data binding is also nice and easy by creating BindingSource in Visual Studio.
But it is important to remember that the Windows Forms data binding is driven off of change notification. That means the Windows Forms will only update a user interface element when the data source notifies that the data has changed (by providing a notification event).
In our case, the data source is the CustomPresentationModel instance. When customer list is reloaded, it sends out two PropertyChanged events. One has changed property name of Items, the other one has changed property name of ItemCount.
The label bound to the ItemCount property updated automatically, but the grid does not seems to pick up Items changed event. Why?
In the case of simple property to property binding, the data source needs to provide property change notification by either providing a "PropertyName changed event for the property or by implementing the INotifyPropertyChanged interface.
When the data source is a list, the data source needs to provide the list change notification that is used to notify user interface elements when an item has been added, removed or deleted from the list via the IBindingList interface.
That is to say to be fully integrated with Windows Forms data binding, the rule is to implement the INotifyPropertyChanged interface on your business entity class and use the IBindingList interface for your business entity collections.
I like data binding, but am hesitating to do it to pollute my entities and entity collections. I would rather refresh the grid view by the code using an anonymous method.
private void CustomerList_Load(object sender, EventArgs e)
{
customerPresentationModelBindingSource.DataSource =
CustomerPresentationModel.Instance;
customerBindingSource.DataSource =
CustomerPresentationModel.Instance.Items;
CustomerPresentationModel.Instance.PropertyChanged +=
delegate(object s, PropertyChangedEventArgs ev)
{
if (ev.PropertyName == "Items")
customerBindingSource.DataSource =
CustomerPresentationModel.Instance.Items;
};
}
}
Within the Composite UI Application Block, there are Work Items that act like a dependency injection container and Event Broker that provides a many-to-many, loosely coupled event system mechanism. This application block is a perfect platform to build applications using the Presentation Model pattern.
As expected, the steps again are the same as those have been used in the ASP.NET Web site and the Windows Forms application.
|
Here, CustomPresentationModel lives as a singleton the same way as in the Windows Forms application.
The list view bound to the customer list updates accordingly to the Items changed event. Besides this, there are some other great features in WPF data binding.
In WPF, we can directly connect to the objects without helper such as the ObjectDataSource in ASP.NET and BindingSource in Windows Forms. Usually I define the a static resource referencing the CustomPresentationModel instance.
<UserControl x:Class="Demo.WpfApp.CustomerEdit"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Width="550"
xmlns:local="clr-namespace:Demo.WpfApp">
<UserControl.Resources>
<ObjectDataProvider x:Key="controller"
ObjectType="{x:Type local:CustomerPresentationModel}"
MethodName="get_Instance" />
</UserControl.Resources>
It is also possible to bind data to UI element’s DataContext property and allow the child element to inherit the data source information and simplify the binding syntax. This is well demonstrated in the CustomerEdit.asmx user control where the top level grid’s DataContext was bound to the selected customer object. Elements inside the grid then bind to the customer object’s properties just using path.
<UserControl x:Class="Demo.WpfApp.CustomerEdit"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Width="550"
xmlns:local="clr-namespace:Demo.WpfApp">
<UserControl.Resources>
<ObjectDataProvider x:Key="controller"
ObjectType="{x:Type local:CustomerPresentationModel}"
MethodName="get_Instance" />
<SolidColorBrush x:Key="LabelForegroundBrush" Color="#FFFFFFFF"/>
</UserControl.Resources>
<Grid DataContext="{Binding Source={StaticResource controller},
Path=SelectedItem}" Background="#FF595959">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Label Grid.Row="0" Foreground="{DynamicResource LabelForegroundBrush}">
First Name</Label>
<TextBox Grid.Row="0" Grid.Column="1" Width="150"
HorizontalAlignment="Left" VerticalAlignment="Center"
Text="{Binding Path=FirstName}">
</TextBox>
<Label Grid.Row="0" Grid.Column="2"
Foreground="{DynamicResource LabelForegroundBrush}">Last Name</Label>
<TextBox Grid.Row="0" Grid.Column="3" Width="150"
HorizontalAlignment="Left" VerticalAlignment="Center"
Text="{Binding Path=LastName}"/>
<Label Grid.Row="1" Foreground="{DynamicResource LabelForegroundBrush}">Address</Label>
<TextBox Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="3"
HorizontalAlignment="Stretch" VerticalAlignment="Center"
Text="{Binding Path=Address}"/>
<Label Grid.Row="2" Foreground="{DynamicResource LabelForegroundBrush}">Comments</Label>
<TextBox Grid.Row="2" Grid.Column="1" Grid.ColumnSpan="3"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
MinHeight="50"
Text="{Binding Path=Comments}" TextWrapping="Wrap" />
</Grid>
</UserControl>
Some thoughts on WPF. WPF is supposed to bring application "differentiated user experience" or "differentiated UI". In our case, it is possible upon the “selected item” changed event, active storyboard or timeline that mimic 3D effects or page flip effect in the bottom area of our application.
What does differentiated UI mean to LOB applications? Probably not just flipping the pages. I am waiting to see how the WPF composite client application block will define that.
So far we have created using the Presentation Model in ASP.NET Web site, a Windows Forms application and a WPF application. Although the Customer class used here is fictional, the design practice of the Web site UI options actually was a true story. We did not realize the Presentation Model is in action until we found the following references later on.
Looks like since the Presentation Model originated from Smalltalk, Java world, FLEX world as well as .NET world are experimenting and using this pattern. This encouraged us to keep exploring more on this track.
Microsoft Web Client Software Factory and Microsoft Smart Client Software Factory are using MVP pattern. There are several things that are not as good as the Presentation Model. E.g. every view, usually a user control requires a presenter class addition to existing code behind class / code beside class. I feel it increases code maintenance difficulties. Not as clear as the Presentation Model demonstrated above.
Microsoft Smart Client Software Factory is based on the Composite UI Application Block (CAB) which provides many great features, such as Dependency Injection (IoC) Framework, event broker. To build application using CAB and the Presentation Model is easy and fun.
In the WPF would, the WPF team has been prompting the Model-View-ViewModel pattern. Essentially Model-View-ViewModel is a Presentation Model. They are two labels for the same thing.
In the coming WPF composite client application block, it is very interesting to see how the patterns & practices team from Microsoft will use the Presentation Model pattern or MVP. How will the Dependency Injection work and how to extend the WPF command/event handling to AOP style?
As demonstrated in this article, the Presentation Model is proven to be an effective pattern in practice. Although ASP.NET, Windows Forms and WPF are total different technologies, they can share the same Presentation Model class. The pattern brings the value of decoupling data model and presentation. It indeed can also integrate with and take advantages of many .NET technologies, such as user control and data binding.
Hope you start to like the Presentation Model after reading this article, if not yet. To make it more convincing, I have another article showing how to use the Presentation Model in SharePoint.
| You must Sign In to use this message board. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
General
News
Question
Answer
Joke
Rant
Admin
|
PermaLink |
Privacy |
Terms of Use
Last Updated: 3 Feb 2008 Editor: Deeksha Shenoy |
Copyright 2008 by Yiyi Sun Everything else Copyright © CodeProject, 1999-2009 Web10 | Advertise on the Code Project |