Its probably fair to say that the ASP.NET team has pushed the envelope as far as web-application compilation models go with the upcoming release of ASP.NET 2.0. Under the covers ASP.NET leverages “build providers” to take the various common ASP.NET files *.aspx, *.ascx and Web.config (to name but a few) and produces source code that can be compiled and executed.
For the most part you can build applications an be blissfully unaware of this feature but if you want to get invited to all the cool parties you should probably get familiar with build providers. A build provider is a class that derives from the System.Web.Compilation.BuildProvider base class, out of the box ASP.NET 2.0 comes with the following (internal) build providers.
As you can see there isn’t much that build providers don’t touch and if you look closely you can tell a lot about the architectural decisions that Microsoft made about certain features (notice what the MasterPageBuildProvider derives from). Build providers really support two things on top of the usual runtime compilation behaviour. First, they are used by the “aspnet_compile.exe” utility to generate the pre-compiled version of a site – this feature would have had to have been in the top five requested features by ASP.NET developers so it was great to see that they got it to work.
The second place where build providers work is at design time to ensure that the intellisense engine has type information to provide developers as they are cutting code. Have you ever wondered how VS2005 knows what properties are exposed via the code-behind file associated with a web-control when you drop it onto your web-form?
Now that we know how build providers are used out of the box lets see if we can practice our own form of ASP.NET voodoo and get them working for us. What I am going to show you is how to create your own build provider which reads a XML files located in the App_Code directory that have the *.exception extension and compiles up a custom exception class that can then be used in code. What use is that? Well thats not the point is it
The first thing that you need to do is create a custom build provider, you can see the code I used below.
We will get to the helper code in a minute, but briefly we can see that this class derives from BuildProvider and overrides the GenerateCode(…) method. The GenerateCode(…) method takes an AssemblyBuilder instance – but don’t be confused, this is not the AssemblyBuilder from the System.Reflection.Emit namespace, this is one specifically designed for the build providers in ASP.NET and it lives in the System.Web.Compilation namespace.
The idea is that we use CodeDom to generate an abstract representation of the code that represents the contents of the *.exception file and pass that to the AssemblyBuilder which then compiles it up into an assembly along with any other code that is being dealt with at the time. What code we generate is completely up to us but typically you would want to take some input from a file with an extension that the build provider is associated with.
Fortunately the BuildProvider base-class has a number of helper methods to make it easy to read in that content as the listing of helper methods show.
The key in the listing above is the call to the BuildProvider.OpenStream() method which allows us to return an open stream to the underlying file that the BuildProvider is working with. You also have access to the BuildProvider.VirtualPath property which gives the site relative path to the file being built. When you first work with the API you might be tempted to try and call MapPath to resolve that to a physical file, however at this point in the pages’ lifecycle you don’t have access to the HttpContext which is where the OpenStream method comes in.
Once you have the BuildProvider written you need to associate it with the file extension that you want to work with, you do that by registering it in the <buildProviders /> section of the Web.config file.
Here you can see that this build provider is associated with the App_Code directory (Code) and is mapped to the *.exception extension (.exception) along with a qualified reference to the BuildProvider class. Once all of this is in place, you can drop a file into the App_Code directory, my file looked like this.
As soon as you save that file and build the ExceptionBuildProvider will kick into action then you will be able to code against the exception class generated as if it was just code you had written yourself.
If you want a bit of a head start or some code to look at you can download my demo code from Project Distributor.