Why Model-View-Controller? Why Even Bother?
The Model-View-Controller architecture has been around for decades, and is still a proven method for developing agile and forward-moving applications. It has many benefits, including:
- Separation of:
- Application Logic (Controller)
- Data (Model)
- User-interface (View)
- The ability to swap/replace views and models.
- Ever write a Windows application that you wanted to also have a web interface for?
- Ever want to change how your data is stored (i.e. flat file/database/XML).
- Properly delegates responsibilities to different areas of the application.
- Your user-interface shouldn't contain application logic.
- Your model shouldn't contain application logic.
- Your user-interface shouldn't directly modify data.
- Etc.
- Promotes dependency injection (a.k.a. dependency inversion).
There are many more benefits than this list contains. MVC also introduces a fair amount of complexity into your application, so beware of using it on smaller applications where the benefits of MVC don't outweigh this complexity.
DDay.MVC
DDay.MVC
is a library designed to help developers take advantage of these benefits without having to code many of the specifics by hand each time. The DDay.MVC
library manages your Models, Views, and Controllers and wires them together.
The following diagram displays the application structure used in DDay.MVC
:
DDay.MVC
also provides some rule enforcement features, which can keep the developer from breaking some of the basic rules of MVC. As shown in this diagram, the model should not be able to directly access the view. When setup correctly, DDay.MVC
enforces this rule.
The Rules
Some rules can be drawn from the above diagram:
- The GUI must never directly interact with the Controller.
- The GUI must never make changes to the Model (i.e. save/delete data); this must flow through the Controller.
- The GUI can only "take action" through the View.
- The GUI should not perform important application logic itself - this is left to the Controller. It should only perform logic directly related to interacting with the user via the GUI.
- The GUI should make "requests" to the Controller through the View. The Controller may fulfill these requests, or choose to do whatever it sees fit, including deny the request.
- The GUI may perform read-only queries on the Model in order to display the information it requires to properly interact with the user.
- The GUI may detect changes to data by using events exposed by the Model and View.
- The View must never "take action" on the Model.
- The Model must never "take action" on the View.
The list goes on, but what we have is sufficient for our purposes. Following these rules is important, as it maintains the overall integrity of your application. It also ensures that you can properly enhance your application without danger of backing yourself into a corner. This is where DDay.MVC
comes in.
Using the Code
From here on, I'll be referencing the code contained in the MVC example included in this article.
Much of this code would be difficult to cover in a single article. At a minimum, most people would be falling asleep by the end. So, in the interest of keeping it "sweet and simple", I'll glaze over the main points, and let you investigate the rest of the code for yourself.
On a side note, when you're implementing this on your own, you'll want to get the latest version of DDay.MVC
from SourceForge.net.
Project Purposes
The first thing you'll probably notice when you open the example is all of the different projects included. Each project has a specific purpose, which if understood may help you better understand the overall structure of the application:
- Application.Console - A console version of the application
- Application.WinForms - A Windows Forms version of the application
- BusinessObjects - Contains the business object classes (i.e.
Customer
) - Controller - Contains the controllers for the application. This is where important application logic takes place.
- Model - Contains model definitions for the application. Notice there are no concrete models here.
- Model.DB - Contains a (fake) database model for the application. This contains concrete models that save/retrieve information from a database.
- View - Contains view definitions for the application. Some concrete views are fairly generic and can be placed here (i.e.
CustomerView
). Other views are specific to the GUI implementation, and are placed in other assemblies (i.e. MainView
). - View.Console - Contains concrete views (i.e.
MainView
) that are specific to the Console application. - View.WinForms - Contains concrete views (i.e.
MainView
) that are specific to the WinForms application, along with GUI elements used by the WinForms application (i.e. MainForm
, AddNewCustomerForm
, etc.).
Dependency Injection
If you look at the BusinessObjects
project, one of the first things you probably notice is nearly every class has a corresponding interface. If you're familiar with this practice, you probably have a good understanding of its importance. If you're not, I suggest reading this article by Martin Fowler that demonstrates how dependency injection can be very helpful.
You'll also notice in Model
and Model.DB
that the interfaces and corresponding concrete classes are placed in separate assemblies. This isn't necessary, but can be helpful in organizing your code and figuring out your assembly dependencies. It also helps keep your application modular.
MVCManager
Another thing you'll probably notice quickly is the MVCManager
. The MVCManager
is the bread-and-butter of DDay.MVC
. It contains all of your models and views, and can be used to retrieve them whenever you need them.
For example, the code uses a model called CustomerModel
to store and retrieve customers. If you wanted to retrieve a list of customers, you would simply do the following:
IMVCManager mvcManager = MVCManager.Current;
ICustomerModelReadonly customerModel =
mvcManager.GetModel<ICustomerModelReadonly>(MVCNames.Customer);
long[] customerIDs = customerModel.GetAllCustomers();
List<ICustomer> customers = new List<ICustomer>();
foreach (long id in customerIDs)
customers.Add(customerModel.GetCustomerByID(id));
The MVCManager
also enforces MVC rules, as noted previously.
Event Wireups
If you're checking out the CustomerController
, you'll notice methods that wireup and teardown events. These are called automatically whenever Views/Models change, and ensure that the lines of communication between the Controller and the Views/Models are setup properly.
In the WireupXXX()
methods, you'll want to subscribe to any events fired from the Model or View that you're interested in handling. Any requests to the Controller from the View should be handled in this manner. Conversely, you'll want to unsubscribe to events in the TeardownXXX()
methods. This ensures there are no event handlers that are mistakenly fired (which has caused me many headaches in the past).
Feedback
If you'd like to post bugs/feature requests, please use the Sourceforge Project Page. I appreciate any help that others are willing to give.
Additional Notes
This article was written in February of 2009, on version 0.84 of DDay.MVC
. Things may have changed significantly since then, so make sure you check the Sourceforge project for updates as any bugs you run into may have already been fixed in the latest version.