A new flag to optimize ASP.NET compilation behavior

Update (9/04/2009)

Actually downloading this fix for 3.5SP1 has been a confusing topic.  Here are links from which you can download it directly without having to call Product Support.  Note that even though the links appear to refer to an unrelated issue, the binaries do in fact contain this fix.  Sorry for the confusion!

For XP and Server 2003: go here

For Vista and Server 2008: go here

For Windows 7, the change is already in there, so you do not need to install any hot fix!

----------------

Quick summary: we are introducing a new optimizeCompilations switch in ASP.NET that can greatly improve the compilation speed in some scenarios.  There are some catches, so read on for more details.  This switch is currently available as a QFE for 3.5SP1, and will be part of VS 2010.

To turn on this switch, add it to your compilation section:

   <compilation optimizeCompilations="true">

 

What prompted us to add this switch

The ASP.NET compilation system takes a very conservative approach which causes it to wipe out any previous work that it has done any time a ‘top level’ file changes. ‘Top level’ files include anything in bin and App_Code, as well as global.asax. While this works fine for small apps, it becomes nearly unusable for very large apps. E.g. a customer was running into a case where it was taking 10 minutes to refresh a page after making any change to a ‘bin’ assembly.

To ease the pain, we added an ‘optimized’ compilation mode which takes a much less conservative approach to recompilation. The drawback is that there are some edge cases where it does not do the right thing, which is why it had to be an optional setting. But those cases are rare enough to still make the feature extremely useful for people running into these issues.

 

Why it is not necessary to always fully recompile

All the pages (aspx/ascx/master) in an ASP.NET site are compiled with a reference to top level files (bin assemblies, App_Code and global.asax). So it feels natural to think that if any of these change, we need to recompile everything that depends on them.

However, thinking about it a little deeper reveals that it is not the case, and that more often than not pages don’t need to be recompiled. Let’s look at the various types of changes that may occur in the top level assemblies.

Change to a method implementation

That’s the easiest case, and is clearly harmless. You’re not changing any public API signature, so any page compiled against the old version will continue to run fine without requiring recompilation.

Addition of new APIs

You add a class, or some new public members on an existing class. Since these things didn’t exist before, none of the already-compiled pages are using them, and there is no need to recompile.

Addition of a CLR attribute to an existing member

This is the typical Dynamic Data scenario where you add attributes like DisplayName to properties. Since all the CLR attributes are discovered at runtime via reflection, pages don’t need to be recompiled.

Rename or deletion of APIs

Now it starts getting interesting. You had a method Foo() that some pages were calling, and now it’s gone. Clearly, the old page assembly is no longer valid. But let’s compare what happens if you recompile the page vs. if you don’t:

  1. You recompile the page: you get a compile error because it’s using a non-existent method
  2. You don’t recompile the page: you get a MissingMethodException, and the exception message includes the name of the missing method

So in both cases you get an error, but the error in the second case is not quite as clear as in the first case. But still, it is pretty usable, so it is not a big degradation.

Change to a type used in an existing API’s signature

This is the nastiest case. E.g. suppose your Foo() method returns an ‘int’, and you change it to return a ‘short’. Now suppose you have a page that calls ‘Response.Write(o.Foo()) ’. Let’s compare what happens if you recompile the page vs. if you don’t

  1. You recompile the page: everything compiles and runs fine, because the same C# (or VB) code can be used to call the method, no matter what its return type is.
  2. You don’t recompile the page: you get a MissingMethodException, because the previously compiled code cannot work with the new method signature.

Undeniably, this case is broken, and is the primary reason that we can’t turn on this optimization by default. Luckily, in practice this situation in not extremely common, which is why the optimization is still very usable for users that are aware of the limitations.

 

What exactly is the optimization doing

ASP.NET uses a per application hash code which includes the state of a number of things, including the bin and App_Code folder, and global.asax. Whenever an ASP.NET app domain starts, it checks if this hash code has changed from what it previously computed. If it has, then the entire codegen folder (where compiled and shadow copied assemblies live) is wiped out.

When this optimization is turned on (via <compilation optimizeCompilations="true" />), the hash no longer takes into account bin, App_Code and global.asax. As a result, if those change we don’t wipe out the codegen folder.

Of course, if App_Code or global.asax have changed, they get rebuilt. But pages that have been previously compiled don’t (unless they have themselves changed).

Note that this optimization doesn’t affect the app domain shutdown behavior. i.e. we still shut down the domain as soon as any top level file changes. It’s only the amount of compilation in the new domain that is affected.