Click here to Skip to main content
15,895,557 members
Articles / Web Development / ASP.NET

Custom MVC Framework

Rate me:
Please Sign up or sign in to vote.
5.00/5 (3 votes)
12 Aug 2013CPOL9 min read 23.3K   142   11  
In this article I show how I turned an ASP.NET Web Forms application into an MVC Framework.

Introduction

Image 1

This article is intended to provide an educational insight on how an MVC framework can be developed from a .NET web application. It outlines some of the key factors to consider when developing an MVC framework and some of the challenges I faced.

Routing in .NET Web Forms has been possible with the introduction of the System.Web.Routing namespace. This routing engine has allowed developers to decouple incoming HTTP requests from its physical file, allowing cleaner URLs to be used. It is this namespace that has allowed me to experiment with the idea of converting a web forms application into a custom MVC framework.

Routing

The first challenge was to route an incoming HTTP request to a handler and for the handler to start the MVC dispatch process. The routing is done from the Application_Start() method located in Global.asax, which is fired the first time the application starts. Listing 1.1 below shows an excerpt of the Global.asax.

Listing 1.1
C#
.........
void Application_Start(object sender, EventArgs e)
{
	RouteTable.Routes.Add(new Route("{*url}", new RouteHandler()));
}

As you can see in the above code, a single route has been added to the RouteTable collection. The route {*url}  indicates that every HTTP request should be handled by the RouteHandler class. The RouteHandler implements the IRouteHandler interface and in doing so it implements the GetHttpHandler method. This method must return an instance of IHttpHandler that knows how to actually, process the HTTP request. Listing 1.2 below shows an excerpt of the GetHttpHandler method of the RouteHandler class.  

Listing 1.2
C#
.........
 
public IHttpHandler GetHttpHandler(RequestContext requestContext)
{
	var page = new Mvc.MvcHandler();
	return page as IHttpHandler; 
}

Notice that a new instance of MvcHandler is being created and returned. Now that I had a way to handle the request, the next step was to decide on how I wanted to configure the framework for usage, For example, how would I tell the framework about the type of template engine I wanted to use. I though about putting these configurable items into the Web.config file under the appSettings section but after further consideration, I decided I would create a Bootstrap class, that would be located in the root of the application.

Bootstrapping 

I needed a way for my application to be configured without having to use the Web.config file. I decided I would use a Bootstrap class, that would be instantiated from the MvcHandler class that would always be present in the MVC application. You can think of it as the main entry point for the application. Let's take a look at the Bootstrap class supplied in the sample application.

Listing 1.3
C#
using System;
using System.Web;
using Mvc;
using Mvc.Route;
 
public class Bootstrap
{
    public void Load(HttpContext context)
    {
        Mvc.Application app = new Mvc.Application();
        app.Module.Add("admin");
 
        app.Route.Add(new StaticRoute("training.html", "default", "index", "training"));
        app.Route.Add(new StaticRoute("prices.html", "default", "index", "prices"));
        app.Route.Add(new StaticRoute("about.html", "default", "index", "about"));
        app.Route.Add(new StaticRoute("contactus.html", "default", "index", "contactus"));
 
        app.TemplateEngine = new Mvc.View.TemplateView(context.Server.MapPath("Application/"));
        app.DebugController = false;
        app.Dispatch(context);
    }
}

Listing 1.3 above shows the complete Bootstrap class from the sample application. Notice that it has a Load() method that accepts a single argument. The Load method is called from the MvcHandler class, which as mentioned is responsible for instantiating the Bootstrap class. The MvcHandler class passes its HttpContext object to the Load method and allowing me to use the Request and Response objects store within the context in the Bootstrap class.

Now that I had a point of entry, I was able to begin loading the application and I could configure it as needed.

Application

With the bootstrapping phase complete, I had to make a few decisions on how I wanted my MVC framework to function and what key features I wanted to implement. For the first version, I wanted to keep it simple so I decided on the following primary features.

  • Static/Dynamic Routing  
  • Templating
  • Configuration per module
  • Custom Request object

Static/Dynamic Routing

In some cases, I may want to route a URL and point it to a controller/action that I chose. For example, I may want to route the following URL http://www.domain.com/aboutus.html so that it maps to an Index controller with an Aboutus action. At the very least I needed to have three types of routes. One that routed static URLs where the path/query matched exactly to a route I specified.

Example

http://www.domain.com/aboutus.html

Route: match "aboutus.html" on success map to Index controller, About action.  

I also wanted a dynamic route so I could match part of the URL path/query. The match would be based on a regular expression. Any URL that matched the pattern would be mapped to a predefined controller and an action.

And finally, I wanted a route that could dynamically extract the controller/action names from the URL. I also wanted the routing to be flexible so that other route types could be added in later versions. In order to achieve this, I created the following abstract Route class shown in Listing 1.4:

Listing 1.4
C#
using System;
using Mvc.Http;
 
namespace Mvc.Route
{
    public abstract class Route
    {
        private HttpRequest _request = null;
 
        public HttpRequest Request
        {
            get { return this._request; }
            set { this._request = value; }
        }
 
        public abstract bool ExecuteRoute();
    }
}

The abstract Route class above defines an abstract method ExecuteRoute. When implemented by a derived class, it must return a boolean value indicating weather the route was successful. It does not need to know if a Controller/Action class/method exists. For example, I can create a custom route and set the ModuleName, ControllerName, and ActonName properties for the HttpRequest _request (a custom HttpRequest object, more on this later) object in the ExecuteRoute() method then return true as shown in the example below. 

C#
public override bool ExecuteRoute()
{
    this.Request.Module = "Default";
    this.Request.Controller = "Index";
    this.Request.Action = "Index";
    return true;
}

Because this route returns true, my MVC framework will always try and load an Index class with an Index method. The benefit to this approach is that, I have control over how I want to route URLs. If I was developing a Blog, I may want to route all URLs that start with /Blog to a predefined Controller. 

Templating

Creating a templating engine in it's self is not a small project. The quickest templating engine to develop is a template that replaces placeholder variables with items stored in a collection. This is nothing new and the technique has been used in various applications such as email templates. The draw back to this approach is that I would not be able to use C# code within my templates so control statements and loops were out of the question. 

After doing further research I realized various templating engines exists. This meant that I didn't have to tie a template engine into my MVC framework, rather the developer could chose the template engine they wanted to use. In order to provide this flexibility, I needed a template engine abstract class. This class when inherited would provide all the methods needed for my MVC framework to understand how to load and pass variables to the View. Listing 1.5 below shows an excerpt of the TemplateEngine class found in the MVC framework.

Listing 1.5
C#
public abstract class TemplateEngine
{
    private Mvc.Http.HttpRequest _request;
    private System.Web.HttpResponse _response;
    private string _module;
    private string _controller;
    private string _action;
    private Dictionary<string,> _viewData = new Dictionary<string,object>();

    public Mvc.Http.HttpRequest Request
    {
        get { return this._request; }
        set { this._request = value; }
    }

    public System.Web.HttpResponse Response
    {
        get { return this._response; }
        set { this._response = value; }
    }

    public string Module
    {
        get { return this._module; }
        set { this._module = value; }
    }

    public string Controller
    {
        get { return this._controller; }
        set { this._controller = value; }
    }

    public string Action
    {
        get { return this._action; }
        set { this._action = value; }
    }

    public Dictionary<string,object> ViewData
    {
        get { return this._viewData; }
        set { this._viewData = value; }
    }

    public abstract string Render();
}

There are only a few public properties that need to be set. ViewData is a key/value collection that gets passed to the TemplateEngine. Most of the time this data will get passed from within the action method. Also notice there is an abstract method Render(). This method must return an output string to send to the browser. For example, if I were to use a third party template engine, I would create a custom template engine class that inherited from the abstract TemplateEngine class and I would implement the code needed to render the third party template engine in the Render method so that it returns the View output. 

Dispatching A Controller

So far, I've discussed a few key components that are required by the MVC framework. It's now time to explain the Controller Dispatch process. So let's start from the BootStrapping phase. At the time of bootstrapping an Mvc.Application instance is created. Once the Mvc.Application instance has be configured, the Dispatch() method of the Mvc.Application class must be called. It takes a single argument which is an instance of HttpConext. When the Dispatch method is called, the following occurs.

A new instance of Mvc.HttpRequest is created.

C#
Http.HttpRequest mvcRequest = new Http.HttpRequest(context);

It is not obvious from the code above but the HttpRequest class is actually a custom class located in the Mvc.Http namespace. The reason why it's a custom class, is so that it can provide additional properties such as IsPost, IsGet to determine the HTTP request method as well as methods to get post and query values.

Next, a route is determined by iterating over the route collection. Notice in the sample code below that an instance of UriRoute is being added to the route collection. This is the default route, which gets added at the end of the collection therefore it is the last route to be examined and this route always returns true. It returns true because, if the module/controller/action names are not present in the URL, then default module/controller/action names are used. 

C#
this.Route.Add(new UriRoute(this._modules));

foreach(Mvc.Route.Route route in this.Route)
{
    route.Request = mvcRequest;
    if (route.ExecuteRoute())
    {
        break;
    }
}

After a route has been determined, the mvcRequest object will hold the module/controller/action names. Using these names, it is possible to dynamically create an instance of a class where the class name is the controller name and the method on that class to invoke is the action name.

Before attempting to instantiate the controller class, backup controllers need to be in place. After all what will happen if a URL points to a controller that doesn't exist? Take a look at the following code below. 

C#
// Initalize controller meta data for three controllers.
// The front, custom error and default error controllers.
ControllerMetaData frontController = new ControllerMetaData();
frontController.Namespace = exeAssembly + ".Application." + 
  mvcRequest.Module + ".Controllers." + mvcRequest.Controller + "Controller";
frontController.Module = mvcRequest.Module;
frontController.Controller = mvcRequest.Controller;
frontController.Action = mvcRequest.Action;
 
ControllerMetaData customErrorController = new ControllerMetaData();
customErrorController.Namespace = exeAssembly + ".Application." + 
  mvcRequest.Module + ".Controllers.ErrorController";
customErrorController.Module = mvcRequest.Module;
customErrorController.Controller = "Error";
customErrorController.Action = "NotFound";
 
ControllerMetaData errorController = new ControllerMetaData();
errorController.Namespace = "Mvc.ErrorController";
errorController.Controller = "Error";
errorController.Action = "NotFound";
 
// Add The three controllers to a collection.
List<controllermetadata> controllers = new List<controllermetadata>();
controllers.Add(frontController);
controllers.Add(customErrorController);
controllers.Add(errorController);

Notice in the code above, I am using a class ControllerMetaData to hold information about a particular controller. The first meta object holds details about the front controller, while the second two meta objects hold details to an error controller (custom error and system error). The meta objects are then added to a List collection and iterated over. In the event a front controller is not found, the next controller in the list will be checked, this is a custom ErrorController that needs to be in the same module as the front controller. Using this custom ErrorController, I can customize the 404 response when a front controller is not found. Finally, if there is no custom ErrorController, a default ErrorController from my MVC framework is used.

The final part of the process involves attempting to dynamically create a controller instance using Reflection and the dynamic Type. Controllers must inherit from Mvc.Controller, which is an abstract class that provides properties and method to derived controller classes.

The abstract class Mvc.Controller has three virtual methods that are called from the Dispatch method. The first method to get called is an Init() method which initializes the controller. Next a Load() method is called. This method is useful, should you want to run some code such as authentication for every action in the controller. After the Load() method has been called, the Action method is invoked and the result from the method is stored into a variable which is later sent to the browser. If an Action method wants to send output to the browser it needs to return the output or alternatively use the Response.Write() method. And finally after the Action method has been called, an Unload method is invoked, this method is useful in disposing of objects and general clean up of resources.

The Dispatch method also tries to load a Module.cs class for each module and invoke a method called Load(). If the Module.cs file is present with a Load() method, it will be called when any controller belonging to that module is called. For example, I could authenticate a module by adding code in the Load() method for that module and in doing so I wouldn't need to authenticate every controller in that module.

This project is still a work in progress and I welcome and feedback.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Web Developer
United Kingdom United Kingdom
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
-- There are no messages in this forum --