February 6th, 2015

Debugging ASP.NET 5 framework code using Visual Studio 2015

In the previous versions of ASP.NET it was possible to debug certain parts of the framework stack but setting up the development environment and compiling all the binaries was not the easiest task. ASP.NET 5 changes that and makes debugging framework code as easy as debugging your own application. This article shows how to debug into the ASP.NET 5 framework code using Visual Studio 2015.

Before getting into the How-To part, there is one limitation that you have to understand: you must not mix different versions of the runtime and/or packages. Trying to debug the latest MVC packages using an old runtime might be possible but there is no guarantee that it will work, or trying to use Beta2 and Beta3 packages in the same application can lead to some very strange compilation errors. The recommendation is that if you use a BetaX (X = number) packages, X must be the same for all packages and runtime, while if you using the latest development bits everything must be from the development branches/feed.

When you create a new ASP.NET 5 application and debug it you will notice that, like in the previous versions, you are not able to step into framework code. If you look at the modules that are loaded, you can see which of them have their symbols loaded and which not. In addition, you can see from where each module was loaded. (To open the Modules window while in a debug session navigate to Debug -> Windows -> Modules).

How to get the source code for ASP.NET

For this blog post, I am going to use MVC as the framework component being debugged. MVC and the rest of the ASP.NET 5 framework components are available in GitHub. To get the source code for MVC, you have to clone the MVC repository (downloading it as a zip archive does not suffice because the tags are not included, more on that in the next paragraph).

D:debug>git clone https://github.com/aspnet/mvc
Cloning into 'mvc'...
remote: Counting objects: 24774, done.
remote: Compressing objects: 100% (5542/5542), done.
remote: Total 24774 (delta 18951), reused 24354 (delta 18617)
Receiving objects: 100% (24774/24774), 6.91 MiB | 3.40 MiB/s, done.
Resolving deltas: 100% (18951/18951), done.
Checking connectivity... done.
Checking out files: 100% (2072/2072), done.

In the beginning of the article, I mentioned that you must have matching packages and source code. You can see in Visual Studio that all the packages are from the Beta2 release. When you clone an ASP.NET GitHub repository, it defaults to the dev branch. That is fine if you are using the latest packages and runtime from thech dev feed, or if you compile everything yourself. Otherwise, you will have to sync to the commit corresponding to those bits. When we have a major release (Beta1, Beta2, etc.) we tag the particular commit that was used to produce the bits. Using the git tag command, you can see the available tags:

D:debug>cd mvc
D:debugmvc>git tag
6.0.0-alpha2
6.0.0-alpha3
6.0.0-alpha4
6.0.0-beta1
6.0.0-beta2

As the tag name suggests, the Beta2 bits correspond to the 6.0.0-beta2 tag. Sync to that tag using the git checkout <tagname> command:

D:debugmvc>git checkout 6.0.0-beta2
Note: checking out '6.0.0-beta2'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -b with the checkout command again. Example:

  git checkout -b new_branch_name

HEAD is now at 2a57f93... Updating Razor to not use K.Roslyn

Now that we have the sources for the packages used by the application, we can configure the application to use them.

Using the local source code instead of the official binaries

In ASP.NET 5 the unit of deployment is the NuGet package. Also, ASP.NET 5 uses Roslyn under the hood and it is able to compile source code on the fly, without requiring the user to provide binaries compiled a priori.

When you want to debug into the source of another library, you specify where the source code for the package containing that library is. If the runtime finds a valid project, it will use it instead of the actual package coming from NuGet or MyGet. An ASP.NET 5 solution, has a global.json file in the root folder. Inside this file, you can specify where the runtime should look for source code. By default it only looks at the src and test folders belonging to the solution.

To use the MVC code downloaded earlier, you have to add the path to MVC’s src folder in global.json. That’s it, now you can debug MVC! Just run the run the application. Remember to revert the changes to global.json, especially absolute paths, before checking in to source control.

The content of the global.json file:

{
  "sources": [
    "src",
    "test",
    "d:/debug/mvc/src"
  ]
}

The runtime decides which package to load using a naive algorithm. When a NuGet dependency (a package) is loaded:

  1. If any of the source code locations specified in global.json contains a folder with the same name as the package (e.g. Microsoft.AspNet.Mvc) and that folder contains a file named project.json, it will use that folder and the source files inside it.
  2. Otherwise, the compiled binary from the packages folder is loaded.

After you add the path to MVC’s source code, you will see that Visual Studio automatically adds the MVC projects to your solution. Also, if you look at your project’s references node, you will notice that Microsoft.AspNet.Mvc has a different icon and it shows that it was loaded from a different location:

If you start debugging and look at the loaded modules, you will notice that Microsoft.AspNet.Mvc is no longer loaded from the packages folder and that symbols are now available:

Behind the scene, MVC was compiled from sources when the application started and the resulted binaries were used instead.

Now, it is time to debug. Add a break point anywhere in the MVC code and it should be hit when the code is executed:

If you are not yet excited about this amazing new feature, let me add a bonus capability. If you change the MVC code in one of the projects added to the solution and restart the application, it will use the modified MVC. Go ahead, try it! If you fix an issue or improve the framework, you can even send a pull request (see the contributing guidelines for more information).

Troubleshooting

We know that sometimes things don’t work as smooth as described here. Below are the most common mistakes that we have seen:

  1. You are using development packages with old bits.Look at the project References node and make sure that all packages have the same version if using released bits.
  2. You are using an older/newer runtime. Using the kvm command line tool (installed separately from the Home repository), you can see which version of the runtime you are using. kvm list will show which version is currently active (the one marker with *). You can select a different runtime version than the active one, for a particular project, from the Project Properties page in Visual Studio.
    D:debugmvc>kvm list
    
    Active Version     Runtime Architecture Location                        Alias
    ------ -------     ------- ------------ --------                        -----
           1.0.0-beta2 CLR     amd64        C:Usersvictorhu.krepackages
      *    1.0.0-beta2 CLR     x86          C:Usersvictorhu.krepackages default
           1.0.0-beta2 CoreCLR amd64        C:Usersvictorhu.krepackages
           1.0.0-beta2 CoreCLR x86          C:Usersvictorhu.krepackages
    
  3. Global.json is not actually pointing to the correct sources. Make sure the path in global.json is pointing to a cloned repository. Also, notice that it is easiest to use forward slashes (/) because they work on all platforms. If you use backslashes () you have to instead use double backslashes (\) as directory separator, and it will work only on Windows.

Summary

We’d love to hear your feedback. For Visual Studio tooling related issues, please submit bugs through Connect, send suggestions on UserVoice, and quick thoughts via Send-a-Smile in the Visual Studio IDE. For ASP.NET 5, please provide feedback in GitHub or the ASP.NET 5 forum. If you ask a question in Stack Overflow, use the asp.net-5 tag.