Fix slow startup of ASP.NET MVC 5 on Azure App Services

You've deployed your MVC 5 website to Azure App Service (via Team Services); with pride you visit the site and start clicking... But wait, it's so slow even though you've done everything right, and 'it works on my machine'. Maybe you even scaled up from the free or shared tier, but it's still too slow.

Introduction

I will show you how to speed up your web site start up time using view compilation. Because Azure App Service disc IO is slow, this will make a big impact. It'll improve startup on other servers too. The homepage of my application starts up in 13 seconds now, before it was close to 30 seconds! How I 'measured' this is for another post.

If you publish via Visual Studio, you only need to set the correct checkboxes. You can read this excellent blog post by Gunnar Peiman for some more background information and instructions. But we all know that publishing from Visual Studio isn't the correct way, right? Friends don't let friends 'right-click publish'. Just rub some DevOps on it (TM Donovan Brown)!

Build

I'm using the Team Services build system for this project. It's easy to set up the initial build pipeline, because you can pick a template to start from. The one I used is Azure WebApp under Deployment.

It has steps for nuget restore, build, tests, deployment and publishing the artifact back to Team Services to store with your build results. The hosted build agent, free for up to 240 minutes a month, offers what I need.

Instead of going through the edit build, execute build, check output loop on Team Services, I just opened a 'developer command prompt' that has msbuild.exe in the path and started looking for the right combination of parameters. Yes, our goal can be achieved just by adding MSBuild arguments.

Disclaimer

Builds will be slower, about 30-40 seconds for me. But it'll make your site start up faster because you've moved some workload from the webserver to the build server. So it's worth it.

Step 1

Add /p:MvcBuildViews=true. Just to tell you the difference between building views and precompiling views.

msbuild.exe build.sln /p:DeployOnBuild=true /p:WebPublishMethod=Package /p:PackageAsSingleFile=true /p:PackageLocation="C:\out\\" /p:platform="any cpu" /p:configuration="release" /p:VisualStudioVersion="14.0" /p:MvcBuildViews=true

This will build your views and give you compile-time errors of issues in your views. Let's say you mistype a variable name, you'd normally only see this at run-time (after deployment). With this option, the build will fail and you won't have a deployment with an error in a view. This will not make your site or pages load faster.

Step 2

Replace with /p:PrecompileBeforePublish=true.

msbuild.exe build.sln /p:DeployOnBuild=true /p:WebPublishMethod=Package /p:PackageAsSingleFile=true /p:PackageLocation="C:\out\\" /p:platform="any cpu" /p:configuration="release" /p:VisualStudioVersion="14.0" /p:PrecompileBeforePublish=true

This will not only build your views, but also put them into a dll. No more dynamic compilation by the server at runtime (aka at request time).

Output of precompiled views

For every view you'll get one dll. The more dlls the server has to load, the slower. So let's merge them into a single dll.

Step 3

Add /p:UseMerge=true /p:SingleAssemblyName=AppCode

msbuild.exe build.sln /p:DeployOnBuild=true /p:WebPublishMethod=Package /p:PackageAsSingleFile=true /p:PackageLocation="C:\out\\" /p:platform="any cpu" /p:configuration="release" /p:VisualStudioVersion="14.0" /p:PrecompileBeforePublish=true /p:UseMerge=true /p:SingleAssemblyName=AppCode

All the App_Web dlls have been merged into a single dll. Also notice that the App_global.asax.dll file is gone now, it's been merge into AppCode too. In the build log you'll see calls to aspnet_compiler.exe and aspnet_merge.exe.

.cshtml

What happened with the .cshtml files? Well they are still in the same place as they were, but they have been changed. If you open one of the views you'll see that it contains a placeholder text. This is to indicate that the views have been precompiled. And also make it clear that you don't have the option anymore to edit the view for a quick fix directly on the web server (precompiled, remember?). You'll have to follow your CI/CD flow (like a pro).

You'll also find a .compiled file in the bin folder for every view. It contains XML that links the .cshtml file to the compiled class in the AppCode assembly file.

Summary

Add this to the build parameters and you'll have a ASP.NET MVC 5 that will start up faster and every new page will load faster.

/p:PrecompileBeforePublish=true /p:UseMerge=true /p:SingleAssemblyName=AppCode

Full list of msbuild arguments in Team Services build:

/p:PrecompileBeforePublish=true /p:UseMerge=true /p:SingleAssemblyName=AppCode /p:DeployOnBuild=true /p:WebPublishMethod=Package /p:PackageAsSingleFile=true /p:SkipInvalidConfigurations=true /p:PackageLocation="$(build.artifactstagingdirectory)\"

ProTip

Keep your site alive by using a monitoring tool like uptimerobot.com or appbeat.io (which runs on .NET I believe) ! Their free tier allow checks at a 5 minute interval, which should be enough to keep it running.