Adopting ASP.NET MVC enhancements in an Existing Web Forms Project

This article is from our ASP.NET MVC 101 Tutorial Series

Whenever we are starting with a Greenfield project, that is a new project from ground up, we prefer using the latest in the technology stack to the point where the team is comfortable with. However, Greenfield projects are pretty rare; enhancement and feature addition projects, often referred to as brown-field projects, are the way forward for existing applications in an Enterprise. In these projects, we are often saddled with predetermined technologies or frameworks and shoe horning the latest and greatest could mean un-necessary delays and instability. After all ‘don’t fix what’s not broken’ right?

Given this scenario, it’s common to have well established ASP.NET 2.0 Web Forms projects that need enhancements. If the enhancements are independent enough, as in creation of new Screens with new functionality, that is no way going to be ‘embedded’ in existing functionality. We can take a clean break and develop the new functionality in ASP.NET MVC by mixing and matching Web Forms with MVC.

Today we will take a look at the caveats, gotchas and the GUIDs we need to take care to make this possible.

Getting Started – Upgrading your Web Forms Solution

Let’s say our legacy application was built using ASP.NET 2.0 or 3.5 in Visual Studio 2010.

legacy-solution

First thing we would like to do is to upgrade the Solution to using Visual Studio 2012. I am using the VS Express edition here so that it makes sense to most of you who do not have a Professional or Ultimate edition.

To upgrade, first thing to do is to open the Solution in VS 2012. Unless you have very specific third party components that are strictly dependent on older versions of .NET Framework, the migration should be seamless and you should get a Migration Report similar to the one below

migration-report

If you see your Solution Explorer now, the Project is all set up.

solution-explorer-post-migration

Rebuild the solution at this point to ensure everything is building fine.

Upgrading all the way to .NET 4.5

The default upgrade is to .NET 4.0. Let’s upgrade all the way to 4.5. To do this, right click on the Project folder above and select Properties. Under the Application Tab, find the Target Framework and set it to .NET Framework 4.5. It will warn that this requires closing and reopening of the solution. Go for it.

Build again and run the project to see that everything is working fine.

Adding ASP.NET MVC Framework Components

Now let’s add MVC Framework support to our Application. Thanks to Nuget, this is a rather easy process. For those new to Nuget, read NuGet – The Visual Studio Sweetner.

Right click on the solution and select Manage Nuget Packages. Select the Online node and search for ASP.NET MVC. The result should be similar to the following

mvc4-dependency

Install the Microsoft ASP.NET MVC 4 package. Accept any additional license agreements and let the installation complete.

Next we have to install the following additional packages. We can do it from Nuget Package Manager Console as well. The commands for each are shown below

jQuery

PM> install-package jQuery -version 1.9.1

Ideally we wouldn’t specify the version, just that as of now there is a conflict between jQuery 2 and jQuery Validation framework. So we’ll stick to 1.9.1.

jQuery Validation

PM> install-package jQuery.Validation

Newtonsoft JSON

PM> install-package Newtonsoft.Json

jQuery UI Combined Library

PM> install-package jquery.ui.combined

Modernizer

PM> install-package modernizr

jQuery Unobtrusive Validation

PM> install-package Microsoft.jQuery.Unobtrusive.Validation
PM> install-package Microsoft.jQuery.Unobtrusive.Ajax


Microsoft ASP.NET Web Optimization

PM> install-package Microsoft.AspNet.Web.Optimization

WebGrease (update): The Web Optimization bundle installs WebGrease 1.0. We should updated it to the latest (1.3 at the time of writing).

PM> update-package WebGrease

Updating Web.config

Now that our dependencies are setup, let’s update the Web.config to unable WebPages 2.0, Unobtrusive Validation and also add the namespaces for MVC.

The App Settings

We add the following inside the <configuration> node

<appSettings>
<add key="webpages:Version" value="2.0.0.0" />
<add key="webpages:Enabled" value="false" />
<add key="PreserveLoginUrl" value="true" />
<add key="ClientValidationEnabled" value="true" />
<add key="UnobtrusiveJavaScriptEnabled" value="true" />
</appSettings>

The System.Web settings

Inside the System.Web node, we add the following namespaces and the runtime framework setting.

<system.Web>

<httpRuntime targetFramework="4.5" />
<pages controlRenderingCompatibilityVersion="4.0">
  <namespaces>
   <add namespace="System.Web.Helpers" />
   <add namespace="System.Web.Mvc" />
   <add namespace="System.Web.Mvc.Ajax" />
   <add namespace="System.Web.Mvc.Html" />
   <add namespace="System.Web.Optimization" />
   <add namespace="System.Web.Routing" />
   <add namespace="System.Web.WebPages" />
  </namespaces>
</pages>

</system.Web>

  
The Runtime settings

Inside the <runtime> node, we add the following assembly binding redirections. Add the <runtime> node if it doesn’t exist.

<runtime>
  <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
    <dependentAssembly>
      <assemblyIdentity name="System.Web.Helpers" publicKeyToken="31bf3856ad364e35" />
      <bindingRedirect oldVersion="1.0.0.0-2.0.0.0" newVersion="2.0.0.0" />
    </dependentAssembly>
    <dependentAssembly>
      <assemblyIdentity name="System.Web.Mvc" publicKeyToken="31bf3856ad364e35" />
      <bindingRedirect oldVersion="0.0.0.0-4.0.0.0" newVersion="4.0.0.0" />
    </dependentAssembly>
    <dependentAssembly>
      <assemblyIdentity name="System.Web.WebPages" publicKeyToken="31bf3856ad364e35" />
      <bindingRedirect oldVersion="1.0.0.0-2.0.0.0" newVersion="2.0.0.0" />
    </dependentAssembly>
    <dependentAssembly>
      <assemblyIdentity name="WebGrease" publicKeyToken="31bf3856ad364e35" />
      <bindingRedirect oldVersion="0.0.0.0-1.3.0.0" newVersion="1.3.0.0" />
    </dependentAssembly>
  </assemblyBinding>
</runtime>

  
So far so good. Build and Run the Application again to ensure nothing is broken.

Adding MVC Tooling Support to the Project

Now that we have the MVC dependencies installed, we should be able to generate MVC Controller and Views. Unfortunately when we right click on the Solution Explorer, these options do not come up. To do this, we need to Unload our project and hack a ProjectType GUID into the csproj xml.
To unload the project, right click on the project node in Solution Explorer and select ‘Unload Project’. The project node goes gray and is marked unavailable. Right click on it and click on Edit <Project Name>.

image

Locate the <ProjectTypeGuids> node in the csproj xml.

project-type-guids-initial

Add the following GUID to it {E3E379DF-F4C6-4180-9B81-6769533ABE47}; at the beginning. The final <ProjectTypeGuids> node should be similar to the following

project-type-guids-final

How did I get that GUID? Sumit Maitra to the rescue who reverse engineered an MVC Project Template :). Reload the Project and Right click on any node in the Web Project, you should be able see the ‘Add Controller’ shortcut.

Next up Bundles, Filters and Area

A standard MVC Project template has an App_Start folder that had static classes for initializing Bundles, Routes and Filters. Let us add these manually.

1. Add the App_Start folder

2. Add BundleConfig.cs and register the jQuery Bundles and the jQuery UI CSS files as follows

public static class BundleConfig
{
public static void RegisterBundles(BundleCollection bundles)
{
  bundles.Add(new ScriptBundle("~/bundles/jquery").Include(
   "~/Scripts/jquery-{version}.js"));
  bundles.Add(new ScriptBundle("~/bundles/jqueryui").Include(
   "~/Scripts/jquery-ui-{version}.js"));
  bundles.Add(new ScriptBundle("~/bundles/jqueryval").Include(
   "~/Scripts/jquery.unobtrusive*",
   "~/Scripts/jquery.validate*"));
  bundles.Add(new ScriptBundle("~/bundles/modernizr").Include(
   "~/Scripts/modernizr-*"));
  bundles.Add(new StyleBundle("~/Content/css").Include("~/Content/site.css"));
  bundles.Add(new StyleBundle("~/Content/themes/base/css").Include(
   "~/Content/themes/base/jquery.ui.core.css",
   "~/Content/themes/base/jquery.ui.resizable.css",
   "~/Content/themes/base/jquery.ui.selectable.css",
   "~/Content/themes/base/jquery.ui.accordion.css",
   "~/Content/themes/base/jquery.ui.autocomplete.css",
   "~/Content/themes/base/jquery.ui.button.css",
   "~/Content/themes/base/jquery.ui.dialog.css",
   "~/Content/themes/base/jquery.ui.slider.css",
   "~/Content/themes/base/jquery.ui.tabs.css",
   "~/Content/themes/base/jquery.ui.datepicker.css",
   "~/Content/themes/base/jquery.ui.progressbar.css",
   "~/Content/themes/base/jquery.ui.theme.css"));
}
}

3. If you would like MVC to intercept unhandled errors, add this FilterConfig class. Else you can postpone it till you need to register another Filter.

public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
  filters.Add(new HandleErrorAttribute());
}
}

Tying them all up

Now that we have the startup files defined, let’s tie them up and invoke them from Application_Start in Global.asax. If you didn’t have Global.asax then add it to the root of the Web Project.
The final Application_Start method looks as follows

protected void Application_Start()
{
    AreaRegistration.RegisterAllAreas();
    FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
    BundleConfig.RegisterBundles(BundleTable.Bundles);
}


It needs these additional namespaces

using System.Web.Optimization;
using System.Web.Routing;
using System.Web.Security;
using System.Web.SessionState;

  
Now we are all set to add new functionality using the ASP.NET MVC Framework.

Adding an Area

Right click on the Project node and select Add->Area. To learn more about Areas, check Areas in ASP.NET MVC is a Useful Feature

Provide an Area name. I am calling it v45 below indicating update to version 4.5. Feel free to name it as per your project’s requirements.

add-area

This will add the following folder structure to the Solution

new-area-folders

As you can see, we have a class for Registering the Area and then empty Controller, Models and Views folders.

Adding ASP.NET MVC Functionality to Web Forms

Adding Entity Framework and Testing out MVC CRUD operations

Now that MVC is all set up, let’s add EntityFramework to the mix and develop a simple CRUD app to test it out. Your requirements may be more ‘involved’ to say the least.

PM> install-package EntityFramework

Add a _Layout.cshtm

ladd-layout-view

Update the file to contain a single line of markup as follows

<h2>Hello MVC</h2>

Now let's add the following Person class to the Model folder in our V45 area.

public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public string Address { get; set; }
}

  
Build the project. Now right click on the Controllers folder and select Add Controller.

add-controller

Setup the Controller as above and hit Add. Run the Application and navigate to the Index view of the Person controller as follows. Note the V45/Person route.

person-index

Click on ‘Create New’ and add Person Details.

 add-new-person

Hit Create and voila! Person is saved to DB!

index-with-person


We just added MVC functionality to ASP.NET WebForms project!!

Conclusion

In real world, changes to existing applications are slow to happen and rewrites almost never happen. However we have to keep moving to new version of frameworks and newer development methodologies because they tend to be more secure and better performing, not to mention the support aspect also.

Keeping these maintenance issues in mind, we saw how we could move an ASP.NET Web Forms application to the latest .NET Framework and then add new functionality using the ‘newer’ ASP.NET MVC framework keeping the old functionality intact.

Depending on your existing project’s dependencies, your mileage may vary slightly, however the general steps remain the same.

Download the entire source code of this article (Github)




About The Author

Suprotim Agarwal
Suprotim Agarwal, Developer Technologies MVP (Microsoft Most Valuable Professional) is the founder and contributor for DevCurry, DotNetCurry and SQLServerCurry. He is the Chief Editor of a Developer Magazine called DNC Magazine. He has also authored two Books - 51 Recipes using jQuery with ASP.NET Controls. and The Absolutely Awesome jQuery CookBook.

Follow him on twitter @suprotimagarwal.

6 comments:

Tasos K said...

Very nice concept! I will surely look into it since we have a few Web Forms projects that cannot simply be converted to MVC.

I would like to ask if you noticed any performance issues when this application is in production?

Loading both Web Forms and MVC modules could lead to a less fast application?

Suprotim Agarwal said...

@Tasos: Your question is subjective and depends on many factors. If you want a plain comparison in what gives better performance between WebForms and MVC, then MVC takes the prize (one reason being there's no viewstate, second its closer to HTTP)

If you mean adding MVC to an existing WebForm application may slow things down, then my answer is No. If you feel the performance has degraded, always do a performance test on vanilla webforms first before adding MVC, so that you have benchmarks to compare.

Tasos K said...

I was asking for the second case, thank you for your answer!

Tim said...

We have a large winforms vb project, I assume if we followed this methodology we couldnt use c# MVC?

Cheers
Tim

Deef said...

@Tim
You can't mix languages in same project. You can add VB.NET MVC to VB.NET WebForms project.
But you mention Winforms project? That's a different world.

Unknown said...

Great article. Been needing to look into this due to a Web Forms project that's older, but need to have new stuff added to it.

Sadly, I got the following when trying to nav to my new Area.

Description: HTTP 404. The resource you are looking for (or one of its dependencies) could have been removed, had its name changed, or is temporarily unavailable. Please review the following URL and make sure that it is spelled correctly.

Requested URL: /VoterUI/Person

Any help on how I can go about debugging this would be appreciated! :)