There are numerous flavors of ASP.NET modules, for example Web Forms (Web Sites and Web Applications), Web Pages, Model-View-Controller (MVC) and the newest one Core. In this article I want to discuss some learnings about the difference between the compilations of an ASP.NET Web Site (Figure 1) and an ASP.NET Web Application (Figure 2).
Figure 1, ASP.NET Web Site and the difference between ASP.NET Web Site and ASP.NET Web Application
Figure 2, ASP.NET Web Application and the difference between ASP.NET Web Site and ASP.NET Web Application
This article is not about the internals of ASP.NET projects rather an experience I had on the Azure App Service platform. The internals are documented here already “Precompiling Your Website (C#)”. You should recognize the difference between automatic and explicit compilation. Also, if you have an ASP.NET Web Site then seriously consider precompiling with aspnet_compiler.exe as described here “How to: Precompile ASP.NET Web Sites”.
Here are some other articles which might be helpful to get greater context on this subject, or cover some other topics used:
- Why is my ASP.NET application recycling, restarting
- Must use, must know WinDbg commands, my most used
- Lab 28: The impact of debug=true
- Debug = True
- All my ASP.NET articles
- A very useful mex(*) command: !mex.writemodule -a -p D:\aspnetcompile\FIRST
- compilation xdt:Transform=”RemoveAttributes(debug)” /
Well, it all started when I wanted to test my understanding of the way ASP.NET compiles into assemblies and I decided to do this on an Azure App Service. I already knew the concept around Temporary ASP.NET files, the fact that ASPX files get compiled into an assembly (.dll) and I knew where they were stored on a stand-alone server. I am confident the first 2 items are the same on the App Service platform but was not so sure about the third point, I.e. where are the compiled assemblies stored, so I wrote the following code to find out.
public partial class _default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
LabelFileLocation.Text = $"{System.Web.HttpRuntime.CodegenDir}";
rptResults1.DataSource = System.IO.Directory.GetFiles(System.Web.HttpRuntime.CodegenDir, "*.dll");
rptResults1.DataBind();
}
}
I presented the output like the following (summarized)
<asp:Repeater ID="rptResults1" runat="server">
<HeaderTemplate><table></HeaderTemplate>
<ItemTemplate>
<tr>
<td><%# Container.DataItem %></td>
</tr>
</ItemTemplate>
<FooterTemplate></table></FooterTemplate>
</asp:Repeater>
Then I published the code to the App Service platform.
The first time I accessed I received the output shown in Figure 3. (originally I had 3 pages in the root of my ASP.NET Web Site)
Figure 3, ASP.NET Web Site compilation on an Azure App Service
I performed a restart of the web site and the binaries where compiled into a new assembly, Figure 4. This was kind of odd as I wasn’t expecting that because I only did a restart and did not make any deployments or changes.
Figure 4, ASP.NET Web Site compilation on an Azure App Service
I looked in KUDU for the directory of the assembly, and I could not find it, that was super odd.
After clicking on the Another Page and And another Page links, they were all compiled into a single assembly, this was also an odd finding, because I have debug=true set in my web.config file.
I needed to find out the answers to these questions:
- Why did my app recompile after a restart even though I did not make any deployments or configuration changes?
- Why, when I looked for the assembly in KUDU, it wasn’t there, although my source code was correct?
- Why did my app compile into s single assembly even though I had debug=true in my web.config file?
The answer to the first question was that I was actually looking at the KUDU assembly and not the one for my App Service. Take a quick look at Figure 2 here “Create a memory dump for your slow performing Web App” and you will see that KUDU runs in a different process than the App Service. The answer then is that KUDU recompiled itself when instantiating and it was not my App Service.
Question #2 is also concluded by the findings in question #1. Without going into any great detail about the file configuration on the App Service platform, the way I got my ASP.NET Web Site assemblies to show, was be setting WEBSITE_DISABLE_SCM_SEPARATION to true, see Figure 5. *Only do this for testing/debugging, not for any specific reason other than having a greater than 1-1 mapping between site and processes has always been a scenario which is not recommended. So once you are finished with this, remove the setting and separate them again.
Figure 4, ASP.NET Web Site compilation on an Azure App Service, cannot find Temporary ASP.NET files on KUDU
Question #3 was because there was a Web.Debug.config file which was stripping out the debug=true when I deployed it stripped out that attribute.
<compilation xdt:Transform="RemoveAttributes(debug)" />
I wrote about XDT files here, here and here, but this one took me some thought time to realize/remember/recognize it.
After setting WEBSITE_DISABLE_SCM_SEPARATION to true I did a stop/start I.e. cold start… and saw that indeed there was only a single process running both SCM/KUDU and my web site, Figure 5.
Figure 5, ASP.NET Web Site compilation on an Azure App Service, cannot find Temporary ASP.NET files on KUDU
Initial publish and compile of default.aspx, name remained the same output after all requests, Figure 6.
Figure 6, ASP.NET Web Site compilation on an Azure App Service
Just to be certain I took a memory dump of the process, dumped and analyzed the module. To be honest I was still a little baffled with the output of the assembly, because I found my debug=true answer only after I had completed all my testing. Sometimes I write articles in a different order than they actually happened. My confusion lead to me testing out batch compilation, read about that here “compilation Element (ASP.NET Settings Schema)”. Because I was expecting 1 assembly per page, the output in Figure 7 was not expected.
Figure 7, ASP.NET Web Site compilation on an Azure App Service
I expected an assembly per page because I thought I was running with debug=true. Reading about batch compilation I read that it compiles all pages into an assembly contained within a specific directory. Therefore I created 2 directories and placed an ASPX files into each. I performed a new deployment with multiple directories. There was no change in PID, so there was no ‘cold-start’, Figure 8.
Figure 8, ASP.NET Web Site compilation on an Azure App Service
But it did recompile into a different binary but remains in the same directory/path, Figure 9.
Figure 9, ASP.NET Web Site compilation on an Azure App Service
Took a memory dump, just to be sure of my self and, as I had only accessed the root and see all pages in the directory compiled, my hypothesis seems to match up with reality in regards to batch compilation, Figure 10.
Figure 10, ASP.NET Web Site compilation on an Azure App Service
I accessed the Directory 1 link, and now see an additional assembly, Figure 11.
Figure 11, ASP.NET Web Site compilation on an Azure App Service
Took another memory dump. dumped the module and see it was indeed the Directory 1 page, Figure 12.
Figure 12, ASP.NET Web Site compilation on an Azure App Service
I did the same for Directory 2, Figure 13 and 14.
Figure 13, ASP.NET Web Site compilation on an Azure App Service
Figure 14, ASP.NET Web Site compilation on an Azure App Service
I was also able to navigate to the assemblies themselves in SCM/KUDU, Figure 15.
Figure 15, ASP.NET Web Site compilation on an Azure App Service
Once I cleared the debug=true thing, all was well and it was a good learning experience. HTH