For some quite long time I naively thought that it is not possible (or at least very difficult) to make ASP.NET MVC stuff work on a Web Site project. But, my latest hacking of MVC opened another perspectives. There are no any difficulties with ASP.NET MVC on Web Sites. I’ll share an experience of integration ASP.NET MVC in my work project legacy Web Site.
Pre-conditions
In my case I integrated ASP.NET MVC2, since we are still running on .NET 3.5. If you are running .NET 4.0 the best is to go with ASP.NET MVC3 already. The difference of setup would not be huge, just in proper versions of assemblies.
I assume you already have ASP.NET MVC2 framework on your machine (if you have VS2010 - you definitely have one), if no just go that link and install it.
Web config changes
MVC assembly have to be added to web.config
. Since the MVC infrastructure heavily depends on Routing mode, it have to be put to web.config
as well.
Register MVC assemblies:
<compilation defaultLanguage="c#" debug="true">
<assemblies>
<add assembly="System.Core, Version=3.5.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/>
<add assembly="System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
<add assembly="System.Data.DataSetExtensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/>
<add assembly="System.Xml.Linq, Version=3.5.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/>
<add assembly="System.Design, Version=2.0.0.0, Culture=neutral, PublicKeyToken=B03F5F7F11D50A3A"/>
<!-- Your application specific assemblies-->
...
<!-- ASP.NET MVC -->
<add assembly="System.Web.Abstractions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
<add assembly="System.Web.Routing, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
<add assembly="System.Web.Mvc, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
</assemblies>
</compilation>
Namespaces:
<pages enableViewStateMac="false" enableViewState="false" validateRequest="true" viewStateEncryptionMode="Never">
<controls>
<add tagPrefix="asp" namespace="System.Web.UI" assembly="System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
<add tagPrefix="asp" namespace="System.Web.UI.WebControls" assembly="System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
</controls>
<-- ASP.NET MVC -->
<namespaces>
<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.Linq"/>
<add namespace="System.Collections.Generic"/>
<add namespace="Microsoft.Web.Mvc"/>
</namespaces>
</pages>
Register URLRouting module and handler.
<system.webServer>
<validation validateIntegratedModeConfiguration="false"/>
<modules>
...
<!-- ASP.NET MVC -->
<add name="UrlRoutingModule" type="System.Web.Routing.UrlRoutingModule, System.Web.Routing, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
</modules>
<handlers>
...
<!-- ASP.NET MVC -->
<add name="UrlRoutingHandler" preCondition="integratedMode" verb="*" path="UrlRouting.axd" type="System.Web.HttpForbiddenHandler, System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
</handlers>
</system.webServer>
Global.asax changes
You would be probably have global.asax
without code-behind class. It’s not a problem, all you need is slightly change the Application_Start()
method. Basically you can go and register routes there, as you do in any ASP.NET MVC application. But I didn’t do that. Instead, I used the advantages of Areas feature of MVC2, since it makes better logical grouping. So, my global.asax
are:
public void Application_Start()
{
AreaRegistration.RegisterAllAreas();
}
Models, Views, Controllers
In typical ASP.NET MVC application you have 3 folders, for - Models, Views, Controllers. Models/Controllers are C# code, views are .aspx pages. With a Web Site you typically place code into App_Code
folder. But you should not do that. Instead, it it better to create a separate class library project that would hold your models and controllers. Views would be placed under corresponding Area folder.
New project for Models/Controllers
Just create a new class library project with such folder structure:
The most important - it should contain Area Registration class inside. This is the one that suppose to register all router for your application:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.Mvc;
namespace Sba.Web.Areas.Sales
{
public class SalesAreaRegistration : AreaRegistration
{
public override string AreaName
{
get
{
return "Sales";
}
}
public override void RegisterArea(AreaRegistrationContext context)
{
// All routes are here..
context.MapRoute(
"Sba_Sales",
"sba/sales/{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
}
}
That’s it. Now you implement Controller/Models in usual way and able to add unit tests project for new assembly.
Make sure that assembly will be placed into to Web Site /Bin
folder. I simply added ../Site/Bin
as project output directory. At the site initialization and Application_Start()
call of Global.asax, all AreaRegistration
instances would be created and routes being registered.
Views
Views have to be placed in the Web Site folder itself. MVC has several rules for views placements, so you have to follow them. Views for controllers that are part of Area’s have to be places into Site/Areas/AreaName/Views
, the common stuff line master pages are in Site/Views/Shared
. It is very important that each View folder must contain the web.config
inside. You can either pick it up from default MVC2 application of just grab from gist.
IIS configuration
Our application previously worked in “Classic” mode. Even thought, it is possible to run MVC on “Classic” I would not really recommend it to do and if production environment allows, just switch you application pool to “Integrated mode”.
After you did it, you can go and run you application. If you setup basic Home controller and view, you should be able to access it by given route. If you receiving 403 HTTP error check:
- Are you really on integrated mode?
- Are there any physical folders that the same as you routing, e.g /sba/sales route and /site/sba/sales folder? In such case you either should correct the routing or change site folder structure.
Session is Null issue
Moving along and starting implementation of some real controllers, we’ve got very strange NullReferenceException
problem. After bit debugging it turned out that HttpContext.Current.Session
is null in controller actions. That’s the common problem for legacy web sites. It is very well described in this stackoverflow answer. So, you just need to add one more section into web.config
file.
<configuration>
...
<system.webServer>
...
<modules>
<remove name="Session" />
<add name="Session" type="System.Web.SessionState.SessionStateModule"/>
...
</modules>
</system.webServer>
</configuration>
After all of those configuration changes your Web Site is fully adopted to ASP.NET MVC framework usage. So, just go ahead and feel the awesomeness of MVC!