Table of Contents
Screenshots
The simple demonstration in action. The first screenshot is served from the classic webform with the URL: http://localhost:41650/Src/ClassicWebforms/Home.aspx.
The second is served from the MVC application by the "Home" Controller's Index" action. The URL is: http://localhost:41650/src/Home/Index.
The ASP.NET MVC page:
The ASP.NET MVC bug is quickly catching up with many developers because of the cool features of the framework like:
- Convention over configuration
- Don't repeat yourself (a.k.a. the DRY principle)
- Pluggable architecture
The side effect of this is the framework supports Test Driven Development a.k.a. TDD. This article specifically talks about making ASP.NET MVC work with the current WebForms applications. This is not a tutorial for learning ASP.NET MVC. For learning ASP.NET MVC, refer to the "References" section.
Every solution should have a pertinent problem that every developer is trying to solve. Here are a few of the reasons why you might want to run both the project types together:
- Web forms are very good at encapsulating view logic into components. You may be comfortable developing complex solutions using technology you already know.
- The ASP.NET MVC pluggable architecture allows TDD. In some organizations, the code coverage policy may be very strict. And, what better way to fulfill this policy requirement better than writing tests?
- You may be working with an existing complex ASP.NET application and may be in the migration phase. The migration needs to happen incrementally without impacting the existing functionality.
There is no code demonstration as the code is not central to the purpose of this article. The core of the article is the configuration activities which enables you to work with WebForms and MVC in harmony.
The ASP.NET MVC framework has a high degree of support for pluggability and extensibility. The crux of the ASP.NET MVC framework is the following three assemblies shown in the figure which extends the System.Web
:
In addition to this, these assemblies work in medium trust server environments and are bin deployable. This essentially means that the assemblies do not need to be installed into the GAC (Global Assembly Cache). You can simply add a reference to the assemblies, and you are done.
Integrating MVC in existing WebForms applications is a four step process as shown in the diagram below:
The first step is to find the libraries that are required for ASP.NET MVC to work.
- The System.Web.Mvc.dll assembly is stored in: [Installation Directory]\Microsoft.NET\ASP.NET MVC\Assemblies. Note: [Installation Directory] in this case is c:\[windows].
- The System.Web.Abstractions.dll and System.Web.Routing.dll assemblies are stored in [Installation Directory]\Reference Assemblies\Microsoft\Framework\v3.5. Note: [Installation Directory] in this case is generally [%Program Files%].
The example in this article stores these files in the "Reference_Assembly" directory. Once added, you need to reference the three core assemblies in your application by using the Project/Add Reference dialog. Refer to the figure below:
Note: The classic WebForms are located in the "ClassicWebForms" folder, and the ASP.NET MVC Views are located in the "Views" folder.
As mentioned earlier, the ASP.NET MVC follows certain conventions (doing things in a prescribed way). One of the core conventions of ASP.NET MVC is the naming of the directories where the project's Controllers and Views are kept.
For this example, we will use the directories "Controllers" and "Views" for storing the respective controllers and views.
We create the HomeController.cs and put it in the Controller directory in App_Code. In big solutions, the controller may belong to its own respective assembly. The HomeController view file Index.aspx is located in the Views/Home folder.
The third step is to update the web.config file. Please refer to the following diagram to add the required assemblies:
The first three assemblies are the ASP.NET MVC assemblies.
The next thing is to add a namespace reference to the system.web/pages
section. Doing this allows access to the System.Web.Mvc
helpers, System.Linq
, and System.Collections
Generics from the ViewPage.
The only namespaces that are required here are System.Web.Mvc
, System.Web.Mvc.Html
, and System.Web.Mvc.Ajax
. But the other ones are pretty useful if you wish to work with LINQ/Generics.
Finally, you need to register UrlRoutingModule
. This module is responsible for matching the URL being requested to the proper Route (Controller/Action).
Finally, you need to map the routes in the Application_Start
event of the Global.asax.
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Default",
"{home}/{action}/{id}",
new { controller = "Home", action = "Index",
id = "" }
);
}
protected void Application_Start()
{
RegisterRoutes(RouteTable.Routes);
}
Hosting an ASP.NET MVC web application: If you or your web hosting provider have access to your web server's settings, a wildcard script map can be created in order to have full routing support. A wildcard script map enables you to map each incoming request into the ASP.NET framework. Be aware that this option passes every request into the ASP.NET framework (even images and CSS files!), and may have performance implications. If you do not have access to the web server's settings, you can modify the route table to use file extensions. Instead of looking look like this: /Home/Index, URLs would look like this: /Home.aspx/Index. This way, no configuration of the web server is required. It is, however, necessary to make some modifications to the application's route table.
You do not have to configure anything if your IIS 7.0 server is operating in Integrated mode.
Creating a wildcard script map in IIS 7.0
Here is how you can enable a wildcard script map in Internet Information Services 7.0:
- Launch the IIS Manager.
- In the Connections tree-view, select an application.
- In the bottom toolbar, make sure that the Features view is selected.
- Double-click on the Handler Mappings shortcut.
- In the Actions window, click on the Add Wildcard Script Map button.
- Enter the path to the aspnet_isapi.dll file, which is usually located in: %windir%Microsoft.NETFrameworkv2.0.50727aspnet_isapi.dll.
- Enter the name ASP.NET MVC.
- Click on the OK button.
After doing this, any request for this specific web site will be executed by the ASP.NET engine.
Creating a wildcard script map in IIS 6.0
Here is how you can enable a wildcard script map in Internet Information Services 6.0:
- Launch the Internet Information Services IIS Manager.
- Right-click on a web site and select Properties.
- Select the Home Directory tab.
- Near Application settings, click on the Configuration button.
- Select the Mappings tab.
- Near Wildcard application maps, click on the Insert button.
- Enter the path to the aspnet_isapi.dll file, which is usually located in %windir%Microsoft.NETFrameworkv2.0.50727aspnet_isapi.dll.
- Uncheck the Verify that file exists checkbox.
- Click on the OK button.
After following these steps, any request for this specific web site will be executed by the ASP.NET engine.
There is actually one difference between a native MVC web application project and your “upgraded” WebForms project. When you want to add a view template using the Add New Item dialog, Visual Studio won’t offer you an MVC View Page or any of the other MVC-specific items. And, when you right-click inside an action method, you don’t get the option to add a view. That’s because Visual Studio doesn’t realize you’ve got an ASP.NET MVC project.
To resolve this, you need to add a project type hint for Visual Studio. This is pretty simple if done with a few precautions.
Caution: Before venturing out for this, backup your project file (i.e., the one with the .csproj extension). If you edit the project file incorrectly, Visual Studio will be unable to open it.
In Solution Explorer, right-click your project name and choose Unload Project. Right-click the project name again, and choose Edit <yourproject>.csproj (replace the angled bracket content with your project name). You’ll now see the .csproj XML file. Find the <projecttypeguids>
node which contains a semicolon-separated series of GUIDs, and add the following value in front of the others: {603c0e0b-db56-11dc-be95-000d561079b0};. Do not add any extra spaces or line breaks. If you don’t want to type in the GUID by hand, you can copy and paste it from the corresponding section of any valid ASP.NET MVC .csproj file you might have elsewhere. Save the updated .csproj file. Then, reload the project by right-clicking its name in Solution Explorer and choosing Reload Project.
If you get the error “This project type is not supported by this installation”, then either you have mistyped the GUID, or you haven’t installed the MVC Framework on your PC.
If you get the error “Unable to read the project file”, then simply click OK and choose Reload Project again. It seems to sort itself out, for reasons unknown to me.
You should now find that MVC-specific items appear in the Add New Item dialog, alongside the usual WebForms items, and you’ll be able to right-click inside an action method and choose Add View.
The following points should be taken care of while migrating an existing ASP.NET application to ASP.NET MVC:
- Plan the process. Do not migrate at one go. Take an area and do a migration.
- ASP.NET MVC2 (preview) allows applications to be created in "Areas". Areas provide a means of grouping controllers and views to allow building subsections of a large application in relative isolation to other sections.
- Design routes for your new areas.
- Migrate the views. WebForms applications have code-behind files, and this is discouraged in ASP.NET MVC. But in scenarios like this, I think you can keep the code-behind files and make the application work.
- As a practice, create controllers and add actions for each of the events that is raised in the view.
- Since abstraction is the key in the MVC design, you can write Unit Test cases as the functionality is being migrated.
- For Unit Tests, abstract away all concrete dependencies in the code-behind and move those to the controller. Some of the dependencies are Session, Cache, HttpContext (related methods), Repository etc.
- Once the test passes, use your good judgement whether you want to get away with the code-behind.
- Continue the process for the rest of the functionality.
In case you are migrating your existing site to ASP.NET MVC, you may require to support the existing URLs that your customers may have bookmarked. There may be many reasons for this. Your customer may be a premium customer and you don't want' to make him unhappy just because you upgraded your site.
I was aware of this technique, but the example was adapted from Mike's website. So, due credit to him for this. Please find the referenced URL in the References section.
This can be done by creating a custom route. All routes derive from RouteBase
. Derive your legacy route from RouteBase
and override the GetRouteData(..)
method. For example, say you have a URL http://yourdomain.com/profile.aspx?id=specialid. Here's a rudimentary example to understand the process:
using System;
using System.Web;
using System.Web.Routing;
namespace CoolControls.Routes
{
public class LegacyUrlRoute : RouteBase
{
public override RouteData GetRouteData(HttpContextBase httpContext)
{
const string status = "301 Moved Permanently";
var request = httpContext.Request;
var response = httpContext.Response;
var name = "";
var legacyUrl = request.Url.ToString();
var newUrl = "";
var id = request.QueryString.Count != 0 ? request.QueryString[0] : "";
if (legacyUrl.ToLower().Contains("profile.aspx"))
{
var rep = new ProfileRepository();
var pro = rep.GetProfile(Convert.ToInt32(id));
name = pro.Name;
newUrl = "profile/" + name;
response.Status = status;
response.RedirectLocation = newUrl;
response.End();
}
return null;
}
public override VirtualPathData GetVirtualPath(RequestContext requestContext,
RouteValueDictionary values)
{
return null;
}
}
}
The HTTP status code informs user agents (browsers and search engines) that the resource they are looking for has been moved to another location. It should make no difference to the existing links.
If the requested URL does not contain "profile.aspx", null
is returned so that the UrlRoutingModule
can continue to try to match the URL to other routes within the RouteTable.Routes
collection.
One final task, and that is to register the LegacyUrlRoute
in the application's RouteTable
. That's done right at the beginning of the method in Global.asax:
public static void RegisterRoutes(RouteCollection routes)
{
......
routes.Add(new LegacyUrlRoute());
.......
}
Now, if a request is made to an old URL, such as http://mysocialnetwork.com/profile.aspx?id= 101, it is automatically redirected to http://mysocialnetwork.com/profile/rajesh with the correct header sent to the user agent. You can check this through an HTTP debugger like Fiddler.
The harmony between Web Forms and ASP.NET MVC wasn't as complicated as I thought. Overall, it was an excellent learning, and would hopefully be useful in some of the migration projects which I may do in future.
The next series of articles will focus on building an end-to-end application with supposedly best practices using a basic test driven approach.
Some of the readers may not agree with some points like the use of code-behind files in ASP.NET MVC. Even I am not in favor of it. But migration is one area where this has its place and leaves the choice with the developer/designer to migrate it based on need and time constraints. So, please don't take this as "word written on stone". Use your good judgment.
- August 19, 2009 - Added support for legacy routes.
- August 11, 2009 - Added Getting Visual Studio to Offer MVC Items in Menus.
- August 7, 2009 - Added Migration Strategy.
- August 6, 2009, 3:30 PM - Added IIS Deployment.
- August 6, 2009 - Created the first version of the article.