Tig och lid!

Tig och lid! header image 2

Upgrading a web forms web appliction to mvc 3, evolution style

april 17th, 2011 · 1 Comment ·

Scenario: You’ve got an existing web application coded in web forms asp.net. You’ve seen the light and want to move over to asp.net mvc 3. Excellent! So, how do you do it? You have two options:

  1. Big Bang revolution. Stop all development on the old site until you have recoded everything in mvc3.
  2. Mvc 3 Evolution. Implement all new functionality in mvc 3. Convert old pages and user controls one by one to mvc3.

I guess it’s obvious that I like option number 2, and that is also how we’re doing it at the client where I work.

It’s easy to implement the first part of the Mvc3 Evolution; just follow the wisdom of Scott Hanselman and you’re set. Then you can write all new functionality in new controllers and views.

So, the hard part left. How can you call actions and views from your old web forms pages and user controls, ascx and aspx? Is it really possible?

Yes, it is possible. But it is a bit tricky and some-what hackish, but if you just ignore the few ugly lines, you can slowly reach mvc3 nirvana without having to go through a big bang release.

What I wanted to accomplish was to be able to call actions and views from my existing web forms pages and user controls, so that I could convert old user controls into views and actions one by one and be able to call them from the old, non-converted pages. The trick is to mimick the mvc3 and asp.net framework, so that we can write code in our pages and user controls like this:


<% WebFormMvcUtil.RenderPartial("Home", "Index", null); %>
<% WebFormMvcUtil.RenderPartial("Index", null); %>
<% WebFormMvcUtil.RenderAction("Home", "Index", null); %>
<% WebFormMvcUtil.RenderAction("Hone", "Index", new { id  = 25}); %>

which in razor views of course would look like this


@Html.Partial("Home", "Index")
@Html.Partial("Index")
@Html.Action("Home", "Index")
@Html.Action("Hone", "Index", new { id  = 25})

The code posted below does all of this for us, setting up the needed route data and contexts.

And these few lines of code is about what you need to be able to step-by-step take your web forms application to a modern mvc3 application. Enjoy!

 

using System;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;

namespace My.Namespace
{
// Code based upon http://stackoverflow.com/questions/702746/how-to-include-a-partial-view-inside-a-webform
// and extended by Jonas Lincoln, 2011

public class FakeController : Controller
{
}

public static class WebFormMvcUtil
{
private const string NameSpace = "My.Namespace";

public static void RenderPartial(string partialName, object model)
{
RenderPartial("Fake", partialName, model);
}

/// <summary>
/// Renders the partial view found in ~/Views/{controllerName}/{partialName}.cshtml
/// or ~/Views/Shared/{partialName}.cshtnk
/// </summary>
/// <param name="controllerName"></param>
/// <param name="partialName"></param>
/// <param name="model">Data passed on to the view as Model</param>
public static void RenderPartial(string controllerName, string partialName, object model)
{
// get the controller
var controller = GetController(controllerName);

//get a wrapper for the legacy WebForm context
var httpContext = new HttpContextWrapper(HttpContext.Current);

//create a mock route that points to the empty controller
var rt = new RouteData();
rt.Values.Add("controller", controllerName);

//create a controller context for the route and http context
var controllerContext = new ControllerContext(
new RequestContext(httpContext, rt), controller);

//find the partial view using the viewengine
var view = ViewEngines.Engines.FindPartialView(controllerContext, partialName).View;

//create a view context and assign the model
var viewContext = new ViewContext(controllerContext, view,
new ViewDataDictionary { Model = model },
new TempDataDictionary(), HttpContext.Current.Response.Output);

//render the partial view
view.Render(viewContext, HttpContext.Current.Response.Output);
}

public static void RenderAction(string controllerName, string actionName, object routeObject)
{
// get the controller
var controller = GetController(controllerName);

var httpContext = new HttpContextWrapper(HttpContext.Current);

// set up the route data
var routeData = new RouteData();

routeData.Values.Add("controller", controllerName);
routeData.Values.Add("action", actionName);

// add optional route information
foreach (var info in routeObject.GetType().GetProperties())
{
routeData.Values.Add(info.Name, info.GetValue(routeObject, null));
}

var cai = new ControllerActionInvoker();

var requestContext = new RequestContext(httpContext, routeData);

var controllerContext = new ControllerContext(requestContext, controller);

// Slightly hackish, but is needed to get everything to work
// This seems to be the way that MVC does it as well.
controllerContext.Controller.ControllerContext = controllerContext;

cai.InvokeAction(controllerContext, actionName);
}

private static Controller GetController(string controllerName)
{
var type = Type.GetType(string.Format("{0}.{1}Controller", NameSpace, controllerName));
if (type == null)
return null;

var controller = Activator.CreateInstance(type, false) as Controller;
return controller;
}
}
}

Tags: hack

1 response so far ↓

Leave a Comment