The easiest way to add ASP.NET MVC 3 to an upgraded ASP.NET 2.0 WebForms application:

- Run the Upgrade Wizard (open the  Visual Studio2008 Web Application in Visual Studio 2010)
- Create a default ASP.NET MVC application for reference (you'll throw it away later)
- Use a differencing tool like
Beyond Compare to integrate the new web.config entries from the ASP.NET MVC sections into the upgraded ASP.NET WebForms application

Upgrading an ASP.NET 2.0 WebForms Application



Now, open this application in Visual Studio 2010. You'll get the Conversion/Upgrade Wizard



Then click Next until Finish. You will get prompt to upgrade the 2.0 application to .NET Framework 4. Click Yes.



Here's the same WebForms application, now upgraded in Visual Studio 2010. It still runs and it's still WebForms. Or, more accurately, it continues to  be ASP.NET.



I'm going to take a new default ASP.NET MVC 3 application and Beyond Compare and compare the upgraded app with the default app.



I'll copy over these folders and files:

- Content
- Controllers
- Models
- Scripts
- Views
- Global.asax, Global.asax.cs

Now, here's the before and after references from the upgraded application. You’ll see the old one on image_6 and the new one on the image_7.



Here's the references I added.

1. Microsoft.CSharp - (as this was a C# app)

2. System.Web.Mvc  - From \Program Files (x86)\Microsoft ASP.NET\ASP.NET MVC 3\Assemblies

3. System.Web.WebPages and System.Web.Razor - From \Program Files (x86)\Microsoft ASP.NET\ASP.NET Web Pages\v1.0\Assemblies

4. System.ComponentModel.DataAnnotations

Next, add these sections to the Web.config. Again, it's easier to use a diff tool and you might have a little trial and error.

Thought: This might be a nice
NuGet package for someone to make...

Add these settings in appSettings:

<appSettings>
   <add key="ClientValidationEnabled" value="true"/>
   <add key="UnobtrusiveJavaScriptEnabled" value="true"/>
</appSettings>

Add these assembly elements under compilation:

<compilation debug="true" targetFramework="4.0">
  <assemblies>
    <add assembly="System.Web.Abstractions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
    <add assembly="System.Web.Helpers, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
    <add assembly="System.Web.Routing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
    <add assembly="System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
    <add assembly="System.Web.WebPages, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
  </assemblies>
</compilation>

Add these namespaces in pages

<system.web>
  <pages>
     <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.Routing" />
       <add namespace="System.Web.WebPages"/>
     </namespaces>
   </pages>
</system.web>

If you're running IIS7 at some point, which I'm sure you will, add these:

<system.webServer>
  <validation validateIntegratedModeConfiguration="false"/>
  <modules runAllManagedModulesForAllRequests="true"/>
</system.webServer>


And finally add this assembly binding redirect, just in case you've got ASP.NET MVC 1 or 2 assemblies in your Global Assembly Cache (GAC).

<configuration>
 <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <dependentAssembly>
        <assemblyIdentity name="System.Web.Mvc" publicKeyToken="31bf3856ad364e35" />
        <bindingRedirect oldVersion="1.0.0.0-2.0.0.0" newVersion="3.0.0.0" />
      </dependentAssembly>
    </assemblyBinding>
  </runtime>
</configuration>


Also, make sure you merge in the Global.asax.cs so that your Routes are registered at application startup.

public class SomeHybrid: System.Web.HttpApplication
{
    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
        filters.Add(new HandleErrorAttribute());
    }

    public static void RegisterRoutes(RouteCollection routes)
    {
        routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

        routes.MapRoute(
            "Default", // Route name
            "{controller}/{action}/{id}", // URL with parameters
            new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
        );

    }

    protected void Application_Start()
    {
        AreaRegistration.RegisterAllAreas();

        RegisterGlobalFilters(GlobalFilters.Filters);
        RegisterRoutes(RouteTable.Routes);
    }
}

Now, at this point I can visit both pages. The WebForms page is a file on disk, so ASP.NET routing passes requests directly on to this page when I /default.aspx. The ASP.NET Routing engine is engaged so I can also hit /Home/Index.


If I want to get fancy, I can add a PageRoute so I have pretty URLs when visit my WebForms pages as well. Just add a route in the Global.asax like this. Make sure that simple routes like these come first, as the default ASP.NET MVC route is very "greedy" and would gobble up a simple URL like /calculator

routes.MapPageRoute("WebFormThing", "Calculator", "~/Default.aspx");

Now I can visit /Calculator and the request is routed to /Default.aspx. And of course, my ASP.NET MVC 3 Razor pages like /Home/Index work also.



Finally, just to make the point, here's the Default.aspx from the WebForms part of my new app next to the source for a Razor page



Enjoy it.