What is PixelDragonsMVC.NET?
PixelDragonsMVC.NET is an open source, easy-to-use MVC framework for use with ASP.NET 2.0. It has built-in support for NHibernate and Log4Net and was produced by Pixel Dragons Ltd. to make creating ASP.NET 2.0 applications as easy as possible for us and you. We've created a project on CodePlex.
From Wikipedia: "Model-view-controller (MVC) is an architectural pattern used in software engineering. In complex computer applications that present lots of data to the user, one often wishes to separate data (model) and user interface (view) concerns, so that changes to the user interface do not affect the data handling, and that the data can be reorganized without changing the user interface. The model-view-controller solves this problem by decoupling data access and business logic from data presentation and user interaction, by introducing an intermediate component: the controller." See Wikipedia for more information.
The ZIP file above includes a sample application and the full source code of the MVC framework (linked using project references). Follow these instructions:
- Download the above ZIP file to your computer and extract all files.
- In Visual Studio 2005, open the solution \PixelDragons.MVCSample\PixelDragons.MVCSample.sln.
- Create a new blank SQL Server database.
- In the sample application folder is a sub-folder named "SQL Scripts." Run each script in this folder in order (lowest version number first) in your newly created database.
- Open the web.config file inside the sample application folder and change the "hibernate.connection.connection_string" setting to match your connection string for your newly created database.
We have also put our sample project online for you to see.
We've tried various MVC frameworks with our ASP.NET applications, but there were always a few things about them that we didn't like. So when the opportunity came up with a new project, we decided to create our own and make it available to everyone. In this article, we talk about NHibernate and assume that the reader has some knowledge of the framework. If you are new to NHibernate, check this out first.
Goals of this project
We found that there were a lot of unnecessary XML configuration in existing MVC frameworks. We always tried to name our models, view and controllers in a similar way to aid maintenance and make our code understandable and consistent. So we thought it would be cool if the MVC could work out on its own what controller/action/view to use based on the requested URL.
Multiple methods (actions) per controller
We've seen controllers in some of the Java MVCs that support multiple "actions" inside a controller class. This means that the number of class files is reduced and easier to manage, especially for large projects. A controller can then contain all related methods for a particular entity or section of the application.
Pass request data via parameters
In one particular framework, data that is posted or on the querystring can be accessed in a controller by creating public properties for each one. So for each piece of data, we had to create a private field and a get/set public property. That's quite a bit of typing if there is a lot of data. We thought it would be better to allow data to be passed to an action method via its parameters. The data should be converted automatically to the correct type, including HttpPostedFile.
Built-in support for NHibernate
We've started using NHibernate in our projects and we love it. We wanted the MVC to take care of the session and transactions and provide a generic DAO class that we could use or derive from. We wanted to be able to specify a regular expression and if the action method name matches, the MVC should automatically start a transaction for you and commit/rollback as required at the end of the request.
Shared UI source for AJAX and server rendering
With various frameworks, we've been doing various things with Ajax and we've found that a lot of the time we had separate UI code for the same thing depending on whether the server or client was doing the rendering. So we wanted to support a way to use the same UI code that could be used on the server-side or client-side. You can see this in the sample application when paging through contacts.
Built-in support for Log4Net
We also wanted the MVC to automatically create and give the controller access to a Log4Net logger, just to try and do as much of the work as possible. With all these things, it means that everything is available in the controller without any extra work. We can just focus on the application.
How it works
For a given request, here are the steps the MVC goes through to make it all work. Code showing what is happening at a given step is included where it is of interest:
Step 1: Browser requests an ASHX file
For example, in the sample application, take a look at default.aspx. It is the start page for the project and redirects to home.ashx.
Step 2: PixelDragonsMVC.NET processes the request
Take a look in the web.config file and you will see that PixelDragonsMVC.NET is setup to handle all requests for ASHX files using an HttpHandler. You can change this to any file extension. You just need to change web.config and configure IIS to use ASP.NET to process these files and un-tick the "Check that file exists" option. ASHX is set up like this by default.
<remove verb="*" path="*.ashx"/>
<add verb="*" path="*.ashx"
type="PixelDragons.MVC.MVCHandler, PixelDragons.MVC" />
You also need to add the config sections for PixelDragonsMVC.NET:
" PixelDragons.MVC" />
<add name="PixelDragons.MVCSample.Domain" />
<add regex="^Save.*" type="NewTransaction" />
<add regex="^Delete.*" type="NewTransaction" />
Step 3: Set up NHibernate
PixelDragonsMVC.NET creates a
TransactionManager ready for use with the controllers. NHibernate is initialised and a session is created for this request. The web.config of the sample application shows how to setup NHibernate by adding the config sections. When the session starts, NHibernate will look for entities in the assemblies listed in the
entityAssemblies list of the
mvc section and load them up. More details about
autoTransactions is below.
value="server=[YOUR SERVER HERE];" +
"database=[YOUR DATABASE HERE];uid=[YOUR USER ID];" +
"pwd=[YOUR PASSWORD HERE];"/>
Step 4: Get controller and action name from request
Next, PixelDragonsMVC.NET works out what the controller and action names are based on the requested file. It looks for the following patterns to match:
It creates a
Command object that holds this information ready for use later.
Step 5: Transactions
PixelDragonsMVC.NET can automatically start an NHibernate transaction based on the action name found in step 4. You can set this up in the web.config (see the
autoTransactions section) by specifying a regular expression to match with. You can use the
TransactionManager in your controllers to start a transaction manually, but you will be responsible for rolling it back if you need to. Currently, the only type supported is
<add regex="^Save.*" type="NewTransaction" />
<add regex="^Delete.*" type="NewTransaction" />
Step 6: Instantiate the controller
Now that PixelDragonsMVC.NET has the controller name, it tries to create a controller object. First it looks at the
controllerPattern setting in the web.config and replaces [ControllerName] with the controller name found in step 4. If there is no class by that name, PixelDragonsMVC.NET looks in the mapping file (mvc.config) for a controller mapping.
If there is no mapping for the controller, then PixelDragonsMVC.NET creates the default controller as specified in web.config (
defaultController). Note: Controllers need to implement
IController, there is a base
Controller class provided that you can derive from (see sample) to make creating controllers as easy as possible. When a controller is instantiated, it is given access to various important objects including
PersistenceManager makes it easy to create a DAO for any entity type that exists in assemblies listed in the
entityAssemblies section of the web.config. The generic DAO can be derived from to create your own DAO.
GenericDao<Contact> contactDao = this.PersistenceManager.CreateDao<Contact>();
List<Contact> contacts = contactDao.ListAll();
Step 7: Call the action
Next PixelDragonsMVC.NET calls the
BeforeAction() method inside the controller. Here you can throw an
ActionException to skip the rest of the action calls and override the view to show. This is good if you want to perform some checks before continuing, as in the sample application. Then PixelDragonsMVC.NET calls the method in the controller with the matching action name detected in step 4 (the action). If there is no action name in step 4, then
DefaultAction() is called. For example, if the requested file is
users-list.ashx, the method
List inside the controller
UsersController is called.
If the action method inside the controller class has parameters, PixelDragonsMVC.NET uses reflection to get a list of these and then searches the request for data that has been posted (or on the querystring) by matching the names. It then converts the data to the correct type before passing it into the action method. For example, if the requested file was
users-save.ashx, the method
Save(string name, string userId, string password) inside the controller
UsersController is called. PixelDragonsMVC.NET would look in the request for data named
password, and convert it to the correct type as required. Then it would pass it in when calling the action method.
If the parameter can't be found in the request, the default value for the parameter is used, i.e. object = null, int = 0, etc. The action can then set the model and view to display. Here is an example action that takes data from the request and sets the view and model. Here is what the
Save action might look like:
public void Save(string name, string userId, string password)
GenericDao<User> userDao = this.PersistenceManager.CreateDao<User>();
User user = userDao.Create();
user.Name = name;
user.UserId = userId;
user.Password = password;
user = userDao.Store(user);
Session["LoggedInUserUid"] = user.UserUid;
this.ViewName = "saveSuccess";
this.Model = user;
AfterAction() is called.
Step 8: Get the view
If the action sets a view name, PixelDragonsMVC.NET will look it up in the mapping file for that controller/action. From the mapping, the view can either be rendered directly or redirected to. For the above example, PixelDragonsMVC.NET will render UI/contacts/form.aspx.
<view id="contact-form" path="UI/contacts/form.aspx" />
<view name="saveSuccess" type="render" ref="contact-form" />
If no view is found matching the name set by the action, then PixelDragonsMVC.NET looks in the shared area. If no view is found in the shared area or the action doesn't set a view name, the default view is rendered. The default view is defined in web.config as
viewWithNoActionPattern depending on if there is a specific action name or not. [ControllerName] and [ActionName] are replaced to create a path to an ASPX file. This is the view file to render and pass the model to.
Step 9: Complete transaction
If a transaction was created, it is either committed or rolled back depending on if an exception was thrown other than
Step 10: Render the view and model
If the view is to be rendered, the ASPX file is executed on the server and returned to the browser. The model that is set by the controller/action is available to the view for rendering. This keeps the business logic separate from the UI. If the view is a redirect, the browser is redirected to the specified URL. An ASPX view might contain code like this:
User user = (User)Context.Items["model"];
<input type="text" name="name" id="name" value="<%=user.Name%>" />
Step 11: Finish
The NHibernate session is closed.
Feedback and future development
This article is a quick overview of how the MVC works. We probably need to create some better documentation and code references, but it's something for you to have a go with at least. As we develop the project, we'll try and improve this. If you have any questions, comments or suggestions, please let us know. We are using this MVC framework in a new project and so will be improving it as and when we find the need. We'll write another article soon with a step-by-step guide of how to create an application that uses our MVC. You can see our other stuff here.
- v0.3 - Fixed some bugs, minor refactoring and added more error checking. Initial release on CodeProject.com
- v0.2 - Added support for NHibernate and Log4Net
- v0.1 - Initial release