Table of Contents
- Introduction
- Background
- An Example
- Description of the MVC
- Benefits of the MVC
- Drawbacks
- How It Works / Dissecting the Sample Application
- Conclusions
- References
- History
There are many articles here on The Code Project that detail the working of the MVC pattern. So you may ask why we need another one? In this article, I try to present a simple and clear use of the MVC pattern.
The demo project uses ZedGraph for generating the chart (http://www.codeproject.com/csharp/zedgraph.asp) and an
implementation of the BackgroundWorker
found in .NET 2.0 ported for .NET 1.1 by Juval Lovy (http://www.idesign.net/).
So What is the MVC Pattern?
As the GoF book states:
"MVC decouples views and models by establishing a subscribe/notify protocol between them. A view must ensure that its appearance reflects the state of the model. Whenever the model's data changes, the model notifies views that depend on it. In response, each view gets an opportunity to update itself. This approach lets you attach multiple views to a model to provide different presentations. You can also create new views for a model without rewriting it."
An example of the use of this pattern would be a reporting system where the same data needs to be presented to the user in different ways. Like an Excel sheet may be presented as columns and rows or by using a chart. Another need of this pattern as I encountered recently was the use of the same data in a desktop application and in a GUI developed for touch-screen devices.
The example in this article shows some test data in a listview
and a chart representation using ZedGraph.
This design decouples the views from the model that generates the test data.
Most modern applications are separated into separate layers: presentation (GUI), business logic (how and why we do things), data layer (persistence).
The MVC pattern is separated similarly as a:
- Model – manipulates the raw data that the application uses (calculations, enforcing business rules, querying the database, etc). The data provided by the model is independent of the visual representation – so it can be used by any number of views without code redundancy!
- View – renders the model into a user interface (the visual representation of the data). The view is isolated from data operations.
- Controller – processes and responds to events, defines the way the user interface (the view) and the model reacts to user input. The user triggers the events that change the model which in turn notifies the registered views to update/refresh their data.
Because the model is decoupled from the view, it allows a great flexibility to implement the model using code reusability and modularity.
The inner working of the model can be changed any time without any effect on the view as long as the output remains the same.
Parallel development process for model and view!
- Adds more complexity to the development
- Not suitable for small applications
You could start by either developing the model or the view.
Let’s start by describing the model. I created an abstract
base class for all models as follows:
public abstract class BaseModel
{
public event EventHandler ModelChanged;
public event ProgressEventHandler ModelProgress;
#region protected members
protected BackgroundWorker backgroundWorker;
protected DataSet ds;
#endregion protected members
#region constructor
protected BaseModel()
{
ds = new DataSet();
backgroundWorker = new BackgroundWorker();
backgroundWorker.WorkerReportsProgress = false;
backgroundWorker.WorkerSupportsCancellation = true;
}
#endregion constructor
#region public abstract methods
public abstract void GenerateReport();
#endregion public abstract methods
#region public methods
public DataSet QueryModel()
{
return ds;
}
public void CancelBackgroundWorker()
{
backgroundWorker.CancelAsync();
}
#endregion public methods
#region event firing methods
protected void Fire_ModelChanged(object sender, RunWorkerCompletedEventArgs ea)
{
BackgroundWorker bw = sender as BackgroundWorker;
if (bw != null)
bw.RunWorkerCompleted -=
new RunWorkerCompletedEventHandler(Fire_ModelChanged);
if (ModelChanged != null)
ModelChanged(this, EventArgs.Empty);
}
protected void Fire_ModelProgress(object sender, int percent)
{
if (ModelProgress != null)
ModelProgress(this, percent);
}
#endregion event firing methods
}
The base class for models contains a BackgroundWorker
and a dataset
object. I use a modified BackgroundWorker
implementation of Juval Lovy. This way we can do the calculations in a separate thread and have a responsive user interface. The dataset simply stores the test data that the view uses.
The Model_Income
class derives from the BaseModel
and adds a table with two columns to the dataset
that is filled with test data in the overridden GenerateReport
method.
There are two events that the BaseModel
exposes:
ModelChanged
– fired when the model has finished generating the data needed by the view.ModelProgress
– fired every time the model wants to notify the view of the progress made in calculating the data.
The controller subscribes the views to these events exposed by the model.
Role of the Model: Generate the Data, Fire Events when Needed to Notify the View of its State.
The view is also derived from a base class BaseView
that in turn exposes one or more events and public
methods for calling from the controller.
public class BaseView : Form
{
public event EventHandler ViewClosed;
#region Windows Form Designer generated code
. . .
#endregion
#region constructor
public BaseView()
{
InitializeComponent();
this.Closed += new EventHandler(OnViewClosed);
}
#endregion constructor
#region form eventhandlers
private void OnViewClosed(object sender, EventArgs ea)
{
if (ViewClosed != null)
{
ViewClosed(this, ea);
}
}
private void butClose_Click(object sender, EventArgs e)
{
this.Close();
}
#endregion form eventhandlers
#region public methods
public void UpdateView(object sender, EventArgs ea)
{
BaseModel model = sender as BaseModel;
if (model == null)
return;
DataSet ds = model.QueryModel();
ContinueUpdateView(ref ds);
progressBar.Visible = false;
}
public void UpdateProgress(object sender, int percent)
{
progressBar.Visible = true;
if (percent >= 0 && percent <= 100)
{
progressBar.Value = percent;
}
else
{
progressBar.Value = 0;
}
}
#endregion public methods
#region virtual methods
protected virtual void ContinueUpdateView(ref DataSet ds)
{
}
#endregion virtual methods
}
The actual implementation of the View overrides the ContinueUpdateView
to visually show the data of the model.
Role of the View: Queries the Model for Data and Presents that Data to the User
What glues this whole thing together is the controller. The controller instantiates the model and the view and subscribes them for the events exposed by the other. There is also an abstract base class for different kinds of controllers:
public abstract class BaseController
{
protected abstract void SubscribeControllerToView();
protected abstract void SubsctribeModelToView();
}
As the code shows, each controller has to make sure that the view’s events are catched and propagated by the controller to the view (SubscribeControllerToView
) and vice versa the model events catched and propagated to the view (SubscribeModelToView
).
For instantiating the views (the reports in this case), we just need to instantiate the proper controller like:
RptController controller = new RptController(RptControllerType.List);
The controller knows by the parameter passed in to the constructor as to which view and model to use to create the report.
I think if the MVC pattern is used properly, it can speed up development time and also simplify the code complexity by separating the UI layer from the data layer.
Please keep in mind that this article is the first that I have ever written, so if some of the sentences seem to be long and unclear, it is my fault.;)
- 8th January, 2007: Initial post