This post is cross-posted from my personal blog.
In an earlier blog I introduced the idea of using ASP.NET MVC to develop DotNetNuke modules, and I walked through how you could create a simple ASP.NET MVC Module Application. Now that I have demonstrated that it is feasible to use the MVC Framework in a WebForms Application, I will start to dig into how this was done.
But first, before we look at the code, lets review how the standard ASP.NET MVC Pipeline works. This is summarized effectively by Steve Sanderson, on his blog.
Figure 1 – ASP.NET MVC Pipeline
|
|
This diagram was based on an early Preview of the ASP.NET MVC Framework, but the basic process has not changed. There are 4 stages in the standard ASP.NET MVC pipeline; Routing, Instantiate Controller, Locate and Execute Action, Instantiate and Render View.
Routing
In the routing step the Incoming Http Request is mapped to the MvcHandler and the MvcHandler’s ProcessRequest method is called, assuming of course that you have registered the handler in your web.config file (Listing 1).
Listing 1 – Registering the MVCHttpHandler in web.config
|
<add name="MvcHttpHandler" preCondition="integratedMode" verb="*" path="*.mvc"
type="System.Web.Mvc.MvcHttpHandler, System.Web.Mvc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
|
Controller Instantiation
The ProcessRequest method (Listing 2) creates a ControllerFactory (line 8), which in term attempts to create a Controller (line 9), using the RouteData which is part of the RequestContext. Assuming that the ControllerFactory is able to create a Controller Instance, ProcessRequest will call the Execute method of the Controller (line 19), thus passing control to the Controller.
Listing 2 – MvcHandler, ProcessRequest method
|
1: protected internal virtual void ProcessRequest(HttpContextBase httpContext) {
2: AddVersionHeader(httpContext);
3:
4: // Get the controller type
5: string controllerName = RequestContext.RouteData.GetRequiredString("controller");
6:
7: // Instantiate the controller and call Execute
8: IControllerFactory factory = ControllerBuilder.GetControllerFactory();
9: IController controller = factory.CreateController(RequestContext, controllerName);
10: if (controller == null) {
11: throw new InvalidOperationException(
12: String.Format(
13: CultureInfo.CurrentUICulture,
14: MvcResources.ControllerBuilder_FactoryReturnedNull,
15: factory.GetType(),
16: controllerName));
17: }
18: try {
19: controller.Execute(RequestContext);
20: }
21: finally {
22: factory.ReleaseController(controller);
23: }
24: }
|
Action Invoker
We have now successfully routed control to the appropriate Controller.
The Execute method of the ControllerBase class calls the ExecuteCore (Listing 3) method of the Controller class. This in turn calls the ActionInvoker (line 6), which again uses the RouteData to invoke the specific action on the Controller.
Listing 3 – Controller, ExecuteCore method
|
1: protected override void ExecuteCore() {
2: TempData.Load(ControllerContext, TempDataProvider);
3:
4: try {
5: string actionName = RouteData.GetRequiredString("action");
6: if (!ActionInvoker.InvokeAction(ControllerContext, actionName)) {
7: HandleUnknownAction(actionName);
8: }
9: }
10: finally {
11: TempData.Save(ControllerContext, TempDataProvider);
12: }
13: }
|
An action maps to a method name in the Controller class.
Listing 4 – The HomeController class
|
1: public class HomeController : Controller
2: {
3: public ActionResult Index()
4: {
5: ViewData["Message"] = "Welcome to ASP.NET MVC!";
6:
7: return View();
8: }
9:
10: public ActionResult About()
11: {
12: return View();
13: }
14: }
|
For example in Listing 4 which shows the HomeController in the default ASP.NET MVC project – the Index and About methods represent the index and about actions of the HomeController.
You would reach these methods using the routes “/home/index” and “/home/about”, although as the first route represents the default controller “home” and the default action “index”, the null route would also reach the Index method.
Render View
Finally the ActionInvoker gets the returned ActionResult from the action method ands calls its ExecuteResult method. If the ActionResult is a ViewResult (Listing 5) this in turn will render the View (line 17).
Listing 5 – ViewResultBase, ExecuteResult
|
1: public override void ExecuteResult(ControllerContext context) {
2: if (context == null) {
3: throw new ArgumentNullException("context");
4: }
5: if (String.IsNullOrEmpty(ViewName)) {
6: ViewName = context.RouteData.GetRequiredString("action");
7: }
8:
9: ViewEngineResult result = null;
10:
11: if (View == null) {
12: result = FindView(context);
13: View = result.View;
14: }
15:
16: ViewContext viewContext = new ViewContext(context, View, ViewData, TempData);
17: View.Render(viewContext, context.HttpContext.Response.Output);
18:
19: if (result != null) {
20: result.ViewEngine.ReleaseView(context, View);
21: }
22: }
|
Plugging in to the ASP.NET MVC Pipeline
So, given that DotNetNuke is a WebForms Application how do we plug in to this Pipeline with the least possible impact . What I mean by this is that we don’t want to have to rewrite the MVC Framework in order to get everything to work. We want to be able to use all of the extensibility points that the Framework enables, with a minimum amount of effort.
In the next part of this series I will describe how we achieve this goal.