How to debug ASP.NET MVC using source code
Steve Moss
This technical post explains how you can debug Microsoft's ASP.NET MVC v1.0 framework, using its source code.
Why do it? Well apart from it just being interesting to look under the hood of one of Microsoft's first open-source products, I have found it particularly useful when building unit tests. For example, when creating a test I may need to know what Context properties & methods MVC will use, in order that I can provide appropriate mock objects (using Moq in my case), for this. By stepping into the MVC code, you can find out.
I'm assuming for this post that you already know the essentials of MVC v1.0, and have it installed on your machine (in particular, that System.Web.Mvc.dll is already installed in the Global Assembly Cache (GAC).
What needs to be done?
In summary we need to remove any references to System.Web.Mvc.dll in the GAC, add the source code for System.Web.Mvc to our Visual Studio solution, and update the references to point to this source code.
In my case I am also using the futures assembly Microsoft.Web.Mvc (this is mainly for the useful Html.RenderAction() extension to HtmlHelper it contains, which in .NET Framework v4 / MVC v2 has been moved to the System.Web.Mvc assembly). If you are not using Microsoft.Web.Mvc, then just ignore instructions that relate to it.
If your Visual Studio solution has more than one project that requires debugging (for example, a separate test project) then just apply the steps below to each project.
1. Download the source code
Go to the .NET page on CodePlex and find the link to the ASP.NET MVC1.0 Source.
Download & extract the .zip file's contents to a directory. If you wish, you can then open the solution file (MvcDev.sln) in Visual Studio to directly view the code that makes MVC tick.
We are interested here, though, in how to use the code during debugging, to step into it from a running application...
2. Delete existing GAC assembly references
Open the project you wish to debug, using Visual Studio.
Actually, I'd recommend you make a complete copy of your application's files, and use that. All the steps here can be reversed, but it's probably easier to have a disposable copy just for debugging.
Remove the GAC references to System.Web.Mvc.dll and Microsoft.Web.Mvc.dll (if using):
3. Add source code projects to your Visual Studio solution
Add the source code projects, which contain the debugging versions of the MVC assemblies, to your solution:
- Right-click on the name of your Solution and select
- Add => Existing Project
- Find the directory you created that contains the MVC source code and select the project file. This will be something like:
- \YourSourceCodeDirectory\src\SystemWebMvc\System.Web.Mvc.csproj
- Repeat for the futures assembly, Microsoft.Web.Mvc, if using:
- \YourSourceCodeDirectory\src\MvcFutures\MvcFutures.csproj
4. Add project references to the source code projects
Add references from your MVC project to the source code projects, to replace the ones you deleted in Delete existing GAC assembly references:
- Right-click on the References folder and select Add Reference...
- Select the Projects tab of the Add Reference dialogue
- Select the System.Web.Mvc project:
- Repeat for the MvcFutures project (if using)
At this stage, your Visual Studio solution should like something like the following:
(This particular example is based on the "SportsStore" application in Steven Sanderson's excellent book, "Pro ASP.NET MVC Framework").
5. Fix up the root web.config file
By default, a number of sections in the root web.config of an MVC application include strongly named references to System.Web.Mvc, which means ASP.NET will look in the GAC for it, rather than using the debugging versions, we want to use.
By removing or modifying these references, the compiler will look in the project's /bin directory by default, and use the debugging version there instead.
Comment out the assembly reference in the <compilation> element:
<compilation 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.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=1.0.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.Data.Linq, Version=3.5.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/>
</assemblies>
</compilation>
Modify all other strongly named references to System.Web.Mvc by setting PublicKeyToken=null in each case.
For my installation, the only additional references are to the MvcHttpHandler, used to verify & process http requests (the application runs fine without changing these references, but you will only be able to debug & step into the handler code if you modify the assembly's strong name, as suggested):
<httphandlers>
<remove path="*.asmx" verb="*"></remove>
<add type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" path="*.asmx" verb="*" validate="false"></add>
<add type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" path="*_AppService.axd" verb="*" validate="false"></add>
<add type="System.Web.Handlers.ScriptResourceHandler, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" path="ScriptResource.axd" verb="GET,HEAD" validate="false"></add>
<!--<add verb="*" path="*.mvc" validate="false" type="System.Web.Mvc.MvcHttpHandler, System.Web.Mvc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>-->
<add verb="*" path="*.mvc" validate="false" type="System.Web.Mvc.MvcHttpHandler, System.Web.Mvc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
</httphandlers>
<handlers>
<!-- Items removed for brevity -->
<add name="ScriptHandlerFactory" verb="*" path="*.asmx" preCondition="integratedMode" type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
<add name="ScriptHandlerFactoryAppServices" verb="*" path="*_AppService.axd" preCondition="integratedMode" type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
<add name="ScriptResource" preCondition="integratedMode" verb="GET,HEAD" path="ScriptResource.axd" type="System.Web.Handlers.ScriptResourceHandler, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
<!--<add name="MvcHttpHandler" preCondition="integratedMode" verb="*" path="*.mvc" type="System.Web.Mvc.MvcHttpHandler, System.Web.Mvc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>-->
<add name="MvcHttpHandler" preCondition="integratedMode" verb="*" path="*.mvc" type="System.Web.Mvc.MvcHttpHandler, System.Web.Mvc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
<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>
I suggest you also search your web.config for any other strongly-named instances of "System.Web.Mvc" that may need changing, in case your configuration does not match mine exactly.
6. Fix up the Views web.config file
There is a further web.config file in the Views directory (if you are using the MVC naming convention, otherwise in whatever directory you keep your .aspx view files). This needs a similar fix to the root web.config to set PublicKeyToken=null for each strongly-named reference to System.Web.Mvc:
<pages validateRequest="false"
pageParserFilterType="System.Web.Mvc.ViewTypeParserFilter, System.Web.Mvc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"
pageBaseType="System.Web.Mvc.ViewPage, System.Web.Mvc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"
userControlBaseType="System.Web.Mvc.ViewUserControl, System.Web.Mvc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
<controls>
<add assembly="System.Web.Mvc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" namespace="System.Web.Mvc" tagPrefix="mvc" />
</controls>
</pages>
What can go wrong?
That should be it - after recompilation your application will spring back to life, allowing you to set break points and step into the MVC source code to examine its inner workings.
The procedure above is quite straightforward, but (as I found over a long afternoon) it is quite easy to miss something & get an error when trying to run your application.
However, all the errors I encountered actually boiled down to accidentally leaving a strongly-named reference to System.Web.Mvc in an assembly or web.config file.
(Other sites I searched for information on these errors suggested it was necessary to change the version number of your debugging System.Web.Mvc assembly, or delete System.Web.Mvc from the GAC, to force the application to use your version, rather than the one in the GAC. However, if your references are all correct, neither of these actions should be necessary).
Some of the errors you may encounter are described below. If you come across others & have solutions to them, please add them to the Comments to enhance this post.
Ambiguous Assembly
System.Web.HttpParseException was unhandled by user code
Message="The type 'System.Web.Mvc.ViewMasterPage' is ambiguous: it could come from assembly
'C:\\Windows\\assembly\\GAC_MSIL\\System.Web.Mvc\\1.0.0.0__31bf3856ad364e35\\System.Web.Mvc.dll'
or from assembly
'C:\\Path-to-application\\WebUI\\bin\\System.Web.Mvc.DLL'.
Please specify the assembly explicitly in the type name."
Source="System.Web"
ErrorCode=-2147467259
This error will occur if you have not removed the reference to System.Web.Mvc as described in Fix up the root web.config file.
HtmlHelper Extensions
error CS1928: 'System.Web.Mvc.HtmlHelper' does not contain a definition for 'RenderAction' and the best extension method overload 'Microsoft.Web.Mvc.ViewExtensions.RenderAction(System.Web.Mvc.HtmlHelper, string, string)' has some invalid arguments
This error occured when I had the release version of Microsoft.Web.Mvc.dll in the /bin directory of my project, and was using the source code (debugging) version of System.Web.Mvc.dll. When I added the source code of Microsoft.Web.Mvc to my project (& updated the references to use this as described in the process above), this error went away.
This seems to be because the release version of Microsoft.Web.Mvc includes a reference to the strongly named System.Web.Mvc, which the rest of the project is no longer using (in favour of the debugging version).
No debug information
The following module was built either with optimizations enabled or without debug information:
C:\Windows\assembly\GAC_MSIL\System.Web.Mvc\1.0.0.0_31bf3856ad364e35\System.Web.Mvc.dll
To debug this module, change its project build configuration to Debug mode. To suppress this message, disable the 'Warn if no user code on launch' debugger option.
This error can occur if you have not corrected the strongly named references to System.Web.Mvc in the Views web.config file (see Fix up the Views web.config file above).
If you ignore this error, you may be notified of a further compilation error such as:
System.Web.HttpCompileException was unhandled by user code
Message="c:\\SomePath\\WebUI\\Views\\Products\\List.aspx(13): error CS1579:
foreach statement cannot operate on variables of type 'object' because 'object'
does not contain a public definition for 'GetEnumerator'"
Source="System.Web"
ErrorCode=-2147467259
However, once you fix the references in web.config, this error should no longer occur.
Comments
Excellent post. I've been debugging with the ASP.NET MVC source code for a few months now, but I wish I have had this when I had to set it up the first time.<br /><br />Thanks,<br /><br />Adrian
Adrian GrigoreHi Steve, Thanks for posting this - worked first time for me. I was using Steve Sandersons instructions which don't actually work on his own sports store example. I guess he started using the MvcFutures assembly after he wrote that post.<br /><br />stuart
Stuart ClodeThanks for an excellent post Steve. Helped me alot.
Adil ZafarThank you for this! It was quite easy to set up.<br /><br />I can confirm that it works for MVC 2 as well. The only additional thing I did was to put null for the assemblyIdentity for System.Web.Mvc for the binding redirect.
Tahir HassanExcellent article. Thanks very much!
jpIt works with ASP.Net MVC3 as well but you may have to update some references in the MVC3 project for System.Web.Razor, System.Web.WebPages, System.Web.WebPages.Razor.<br /><br />A caveat which I have not figured out yet is Intellisense does not work anymore in my Razor views.
PascalComments are closed