Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

PixelDragonsMVC.NET - An open source MVC framework

0.00/5 (No votes)
29 Jun 2007 5  
An MVC framework with built-in NHibernate support that makes creating ASP.NET 2 applications easier by minimizing code and configuration

Screenshot - pixeldragonsmvc1.gif

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.

Sample application

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.

Background

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

Minimal configuration

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.

<httpHandlers>
    <remove verb="*" path="*.ashx"/>
    <add verb="*" path="*.ashx" 
        type="PixelDragons.MVC.MVCHandler, PixelDragons.MVC" />
</httpHandlers>

You also need to add the config sections for PixelDragonsMVC.NET:

<section name="mvc" 
    type="PixelDragons.MVC.Configuration.MVCConfigurationHandler," + 
        " PixelDragons.MVC" />
<mvc mappingFile="mvc.config"
    controllerPattern=
        "PixelDragons.MVCSample.Controllers.[ControllerName]Controller"
    viewWithActionPattern="UI/views/[ControllerName]/[ActionName].aspx"
    viewWithNoActionPattern="UI/views/[ControllerName].aspx"
    defaultController="PixelDragons.MVC.Controllers.Controller">

    <entityAssemblies>
        <add name="PixelDragons.MVCSample.Domain" />
    </entityAssemblies>

    <autoTransactions>
        <add regex="^Save.*" type="NewTransaction" />
        <add regex="^Delete.*" type="NewTransaction" />
    </autoTransactions>
</mvc>

Step 3: Set up NHibernate

PixelDragonsMVC.NET creates a SessionManager and 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.

<section name="nhibernate" 
    type="System.Configuration.NameValueSectionHandler, 
    System, Version=1.0.3300.0,Culture=neutral, 
    PublicKeyToken=b77a5c561934e089"/>
<nhibernate>
    <add key="hibernate.connection.provider" 
        value="NHibernate.Connection.DriverConnectionProvider"/>;
    <add key="hibernate.dialect" 
        value="NHibernate.Dialect.MsSql2000Dialect"/>
    <add key="hibernate.connection.driver_class" 
        value="NHibernate.Driver.SqlClientDriver"/>
    <add key="hibernate.connection.connection_string" 
        value="server=[YOUR SERVER HERE];" + 
            "database=[YOUR DATABASE HERE];uid=[YOUR USER ID];" + 
            "pwd=[YOUR PASSWORD HERE];"/>
</nhibernate>

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:

  • .../[ControllerName].ashx
  • .../[ControllerName]-[ActionName].ashx

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 NewTransaction.

<autoTransactions>
    <add regex="^Save.*" type="NewTransaction" />
    <add regex="^Delete.*" type="NewTransaction" />
</autoTransactions>

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.

<controller name="different" 
    class="MVCSample.Controllers.Different.AnotherController" />

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 HttpContext, Server, Request, Response, SessionManager, TransactionManager and PersistenceManager. The 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 name, userId and 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;
}

Lastly, 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.

<views>
    <view id="contact-form" path="UI/contacts/form.aspx" />
</views>

<controller name="users">
    <action name="save">
        <view name="saveSuccess" type="render" ref="contact-form" />
    </action>
</controller>

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 viewWithActionPattern or 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 ActionException.

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.

History

  • 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

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here