Every once in a while when I'm exploring a technology, I experience a jaw-dropping moment where I'm blown away by how elegant, or well designed, or rife with potential something is. This weekend I had such a moment with ASP.NET 2.0 (yes, I know it was a holiday weekend, but I suddenly have a lot of deadlines looming). In preparation for a conference talk I'm giving at WinDev this year on compiliation in ASP.NET 2.0, I had a chance to try building my own custom build provider. As you may already know, when you place a source code file (like mycomponent.cs) under the top level /code directory it is compiled into an assembly and the rest of your pages can then reference the classes in that assembly. This is a nice feature in that it completes the delay-compile picture for deployment. In the ASP.NET 1.x you had to pre-compile any components you wanted to include and deploy them in the /bin directory (or find a page that wasn't using code behind and hijack its src= attribute, or use the assembly directive, each of which had its own set of issues). Now you can truly deploy nothing but .as*x and source files to a site, or even work in that mode and deploy with only binary.
What's even more interesting about the /code directory is that you can place other types of files in it as well, including .wsdl and .xsd files. Dropping a .wsdl file into /code will trigger a custom build step that involves generating a webservice proxy class which you then have immediate access to in your pages. VS.NET 2005 has really nice support for this too - as soon as you drop a .wsdl file into the directory you can use the object browser to view the generated proxy class, and you immediately have intellisense in all your pages for the proxy. Dropping a .xsd file into /code generates a typesafe DataSet-derived class like you may have used in the past when you ran the xsd.exe utility or selected 'Generate typesafe DataSet' from the designer.
Anyway, these are nice features, but this was not the jaw-dropping momemt for me. It turns out that you can build your own 'builder' and associate it with a specific extension. Once you deploy it, any file with your extension placed in the /code directory will then use your builder to spit out whatever code you want and compile it as part of the generated assembly for the /code directory. I have recently been working on building a data access layer for a client of mine, and for a number of reasons we chose not to go with typed DataSets, so instead I had written a code generator to extract schema information from a database connection and spit out a class with all the appropriate fields and properties based on the column types (actually similar to what the typed DataSet generator does, but without a lot of the other baggage). This seemed like a perfect candidate to test out as a custom builder, so I set to work.
The first step was to define the input file for my builder (this would be the file you place in the /code directory once I was done). I had an XML format I had been using, so I stuck with it and decided to use the extension of .dal for the file association. Here's a sample .dal file that would generate a class for the authors, publishers, discounts, and sales tables of the pubs database:
<!-- file: pubs.dal -->
<dalGenerator>
<connectionString>server=.;trusted_connection=yes;database=pubs</connectionString>
<namespace>MyDAL</namespace>
<tables>
<table>
<name>authors</name>
</table>
<table>
<name>publishers</name>
</table>
<table>
<name>discounts</name>
</table>
<table>
<name>sales</name>
</table>
</tables>
</dalGenerator>
Now, to associate the .dal extension with a custom build provider, you add an entry to your web.config file that looks like:
<configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">
<system.web>
<compilation>
<buildProviders>
<add extension=".dal" appliesTo="Code" type="PS.DalBuildProvider" />
</buildProviders>
</compilation>
</system.web>
</configuration>
Where the PS.DalBuildProvider was the class I had yet to build. So the next step is obviously to go build the build provider class. To do this, you create a class the derives from the BuildProvider abstract base class, and most of the time you will just override the GenerateCode method which takes a reference to a AssemblyBuilder class as a paramter. Your job in this method is to create a new CodeCompileUnit (yes, you have to use the CodeDom) filled with whatever code you want compiled and add it to the AssemblyBuilder as a compile unit.
Here's a shell of such a class to give you an idea of what must be done - I've used the CodeSnippetCompileUnit in this sample so I can easily drop in a chunk of C# code. In the real DalBuildProvider I used the CodeDom classes (I'll provide a link to the complete sample below) which I recommend you do too for language independence.
using System;
using System.Text;
using System.Web.Compilation;
using System.Web;
using System.Web.UI;
using System.CodeDom;
using System.Web.Hosting;
using System.IO;
namespace PS
{
public class DalBuildProvider : BuildProvider
{
public override void GenerateCode(AssemblyBuilder ab)
{
// retrieve input file
string filename = base.VirtualPath;
XmlDocument doc = new XmlDocument();
using (Stream inFile = VirtualPathProvider.OpenFile(base.VirtualPath))
{
doc.Load(inFile);
}
// this is where I would navigate the DOM to generate code
string sampleCode = "class Foo {}";
ab.AddCodeCompileUnit(this,
new CodeSnippetCompileUnit(samplecode));
}
}
}
Once this class was built, I deployed it in my local /bin and added the pubs.dal file shown above to the /code directory, and it worked! Here's a screenshot of what the screen looked like when my jaw actually hit the floor :)
You can download the whole DAL Generator Build Provider sample here. Happy jaw dropping!
Posted
Sep 06 2004, 01:16 PM
by
fritz-onion