Jaw-dropping experience with custom build providers

Onion Blog

Syndication

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
Filed under:

Comments

Scott Allen wrote re: Jaw-dropping experience with custom build providers
on 09-06-2004 8:30 PM
Amazing! Thank you for this juicy bit of information!
Andres Aguiar's Weblog wrote Fun with ASP.NET 2.0 build system
on 09-07-2004 11:12 AM
Andres Aguiar's Weblog wrote Fun with ASP.NET 2.0 build system
on 09-07-2004 11:16 AM
Christopher Bowen wrote Custom Build Providers in ASP.NET 2.0
on 09-07-2004 12:10 PM
Custom Build Providers in ASP.NET 2.0
Eric J. Smith wrote re: Jaw-dropping experience with custom build providers
on 09-07-2004 7:12 PM
You may want to look into CodeSmith as it can basically do this same thing now in VS.NET 2003. CodeSmith includes a custom tool that will allow you to take an XML file as input and generate any number of classes from it.

Also, you mentioned that you wrote a code generation app to generate code from database scheme. This is exactly what CodeSmith is VERY good at and is also 100% extensible whereas I'm guessing your apps output is hard-coded?

http://www.ericjsmith.net/codesmith/

Thanks,
Eric J. Smith
Fritz Onion wrote re: Jaw-dropping experience with custom build providers
on 09-08-2004 3:48 AM
CodeSmith does look powerful, I'm installing it now - thanks for the tip!
-Fritz
David Hayden - Sarasota Web Design Development - F wrote Custom Build Providers in ASP.NET 2.0
on 09-24-2004 6:18 AM
David Hayden - Sarasota Web Design Development - F wrote Custom Build Providers in ASP.NET 2.0
on 09-24-2004 6:20 AM
ShowUsYour wrote Custom Build Providers
on 01-27-2005 12:48 AM
Peter wrote re: Jaw-dropping experience with custom build providers
on 06-14-2005 7:06 AM
I tried to compile your code under beta2 and got the following error:
The 'type' attribute must be set to a valid Type name (format: <typename>[,<assemblyname>])in Web.config line 8

Here is the line cause error
<add extension=".dal" appliesTo="Code" type="Pluralsight.Samples.DalBuildProvider"/>

I have pubs.dal under App_Code directory.

Please help me. Thanks, Peter
Fritz Onion wrote re: Jaw-dropping experience with custom build providers
on 06-15-2005 10:12 AM
Peter - I've updated the sample download (listed at the end of the post) to be beta2 compliant. Try downloading it again and it should work.

-Fritz
Kirk Allen Evans' Blog wrote Web Services Contract First: Drop Schema in app_code with a Custom Build Provider
on 09-02-2005 1:16 PM
I now have complete respect for Fritz' reaction to custom build providers.
Web applications in Visual...
Kirk Allen Evans wrote re: Jaw-dropping experience with custom build providers
on 09-02-2005 1:24 PM
Here is another example of why custom build providers are so cool. Thanks for the post, Fritz!

http://blogs.msdn.com/kaevans/archive/2005/09/02/460231.aspx
benjaminm's blog wrote XSD Custom Build Provider causes strong bodily reaction
on 09-02-2005 2:56 PM
Kale wrote And how to to this for a class library
on 09-05-2005 3:26 AM
Very cool, but can we use this feature outside our web project? i.e. When you want to create these classes for your business logic in a class library.
Bill Evjen's Blog wrote Bill Evjen's Learn ASP.NET Page
on 09-07-2005 6:52 AM
Dino Esposito wrote OK OK--Build providers
on 09-20-2005 8:11 AM
Davide Senatore wrote re: Jaw-dropping experience with custom build providers
on 09-21-2005 1:24 AM
WOW! This is really a cool feature! Can't imagine the thousands of problems i could resolve with it.
Thanks Fritz!
Etienne Lambert wrote My jaw dropped even further!
on 11-02-2005 6:06 AM
My friend, you are an absolute genius and I will be forever grateful for the time you saved me!

Only thing is, god I didn't know the CodeDom would be so hard to use! Do you know of any CodeDom Code generator? ;o)

Thanks ever so much for sharing this!

Cheers from the UK
blog.dreampro wrote Custom build providers to generate DAL classes
on 11-28-2005 10:11 AM
Jordy wrote re: Jaw-dropping experience with custom build providers
on 11-29-2005 9:11 AM
Custom builders are the way to go.... codesmith is crap (with respect) :-o .. at least you don't have to pay for the functionality that is already in vs2005. Codesmith on the other hand is not free. VS2003 could do this with custom tools, but these are so much easier to write now.
David wrote re: Jaw-dropping experience with custom build providers
on 12-14-2005 2:05 PM
It seems that the Build Providers has changed abit in the final release, I keep getting an error with the appliesTo="Code" attribute
Tyrone wrote re: Jaw-dropping experience with custom build providers
on 01-06-2006 8:04 AM
It has changed a bit. I was able to just delete the appliesTo="Code" attribute altogether and it just works. Thats one thing you get with Beta Code :)
AzamSharp wrote re: Jaw-dropping experience with custom build providers
on 01-06-2006 12:23 PM
This is too good. But I am facing one problem. When I make changes in the pubs.dal file like changing the name of database to Northwind and table names to Categories and Products and compile them I get the following error:

Error 10 The type 'MyDAL.Product' already contains a definition for 'UnitPrice' c:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\Temporary ASP.NET Files\buildtester\942f10cc\8a0a32cf\App_Code.nxnmaq_r.1.cs


yem583 wrote re: Jaw-dropping experience with custom build providers
on 01-18-2006 4:41 PM
The code sample provided in this demo assumes that the field names will have underscores in them. If they don't you get the error you received because the property names end up being the same as the local variable/member names are. I worked around this by prefixing the member names with an underscore.

foreach (DataColumn dc in ds.Tables[0].Columns)
{
CodeMemberField field = new CodeMemberField(dc.DataType,"_"+
CreateMemberNameFromSqlName(dc.ColumnName, false));
dalClass.Members.Add(field);

CodeMemberProperty prop = new CodeMemberProperty();
prop.Name = CreateMemberNameFromSqlName(dc.ColumnName, true);
prop.Type = new CodeTypeReference(dc.DataType);
prop.Attributes = MemberAttributes.Public;
prop.GetStatements.Add(new CodeMethodReturnStatement(
new CodeFieldReferenceExpression(
new CodeThisReferenceExpression(),
CreateMemberNameFromSqlName("_"+dc.ColumnName, false))));
prop.SetStatements.Add(new CodeAssignStatement(
new CodeFieldReferenceExpression(
new CodeThisReferenceExpression(),
CreateMemberNameFromSqlName("_"+dc.ColumnName, false)),
new CodePropertySetValueReferenceExpression()));
dalClass.Members.Add(prop);
}
Srivatsn's Blog wrote Custom Build providers for C# using MSBuild
on 01-28-2006 3:51 AM
Custom Build providers has been&nbsp;the prerogative of ASP.NET 2.0 developers so far. ASP.NET has a...
Charlie Hurley wrote re: Jaw-dropping experience with custom build providers
on 02-08-2006 2:24 PM
Wanted to get the Build Provider Sample and Check it out, it seems quite interesting
the blog of Mork wrote DataContract BuildProvider
on 03-25-2006 6:32 AM
Greg Young wrote Custom Build Providers
on 03-26-2006 11:19 PM
Jonathan Minond wrote re: Jaw-dropping experience with custom build providers
on 05-02-2006 4:58 AM
Build Providers and Code Dom are super cool.
This article led me to go learn how to make an enumGenerator, objectGenerator, and more.
Awsome Fritz
Tom Goodman wrote re: Jaw-dropping experience with custom build providers
on 05-13-2006 10:52 AM
This blog posting was of great use in learning new information and also in exchanging our views. Thank you.
Tom Goodman
http://www.providersbase.com
Jørn Schou-Rode wrote re: Jaw-dropping experience with custom build providers
on 05-19-2006 11:38 PM
Now this is really something! Automatic DAL generation is indeed going to help me build .NET applications quickly. I will be back when I have tried this out :o)
gozh2002 wrote re: Jaw-dropping experience with custom build providers
on 09-13-2006 11:42 PM
BuilderProvider is really a Jaw-dropping feature. And just can't believe it this is an article already two year old.
Johan Danforth's WebLog wrote [.NET 2.0] Playing Around with BuildProvider and CodeDom
on 12-02-2006 1:45 PM
This is old news for some of you, but I&#39;ve never looked at the ASP.NET buildprovider functionality
Blog o' Johan @ IRM wrote [.NET 2.0] Playing Around with BuildProvider and CodeDom
on 12-02-2006 1:45 PM
This is old news for some of you, but I&#39;ve never looked at the ASP.NET buildprovider functionality
Andrew L. Van Slaars wrote BuildProviders in ASP.NET Part I
on 01-02-2007 5:36 PM
BuildProviders in ASP.NET Part I
Cranialstrain wrote re: Jaw-dropping experience with custom build providers
on 01-11-2007 8:46 AM
Excellent, thank you very much; this really helped me open my eyes to the possiblities of Build Providers.

I do have one question however you may be able to help with - if I wanted to manually invoke a Build Provider at build-time (e.g. via Macro pre-build event) how would I do that? e.g. rebuild a custom extension/provider even though the App_Code file hasn't changed?

Ian
AzamSharp wrote Using Build Providers to Dynamically Create Entity Classes
on 01-24-2007 1:52 PM
There are lot of good articles about Build Providers. Bilal Haider in his article Creating Custom BuildProvider
BarrySumpter wrote re: Jaw-dropping experience with custom build providers
on 02-15-2007 11:37 PM
Hi All,

I am having disastrous problems with my .xsd for a strongly typed dataset file after changing the database.

Does anyone have any further info on how to use this .dll with vb.net.

A step-by-step procedure would be great. I'd be happy to updated it as I test it. And offer it for others of course.

Again I'm working with vb.net 2005 Pro and SQL Server 2005 Express.

All the best to everyone.
BarrySumpter wrote re: Jaw-dropping experience with custom build providers
on 02-16-2007 1:20 PM
vb.net

[code]

Imports System
Imports System.Text
Imports System.Web.Compilation
Imports System.Web
Imports System.Web.UI
Imports System.CodeDom
Imports System.Web.Hosting
Imports System.IO
Namespace PS

Public Class DalBuildProvider
Inherits BuildProvider

Public Overloads Overrides Sub GenerateCode(ByVal ab As AssemblyBuilder)
Dim filename As String = MyBase.VirtualPath
Dim doc As XmlDocument = New XmlDocument
' Using
Dim inFile As Stream = VirtualPathProvider.OpenFile(MyBase.VirtualPath)
Try
doc.Load(inFile)
Finally
CType(inFile, IDisposable).Dispose()
End Try
Dim sampleCode As String = "class Foo {}"
ab.AddCodeCompileUnit(Me, New CodeSnippetCompileUnit(samplecode))
End Sub
End Class
End Namespace
[/code]
DotNetKicks.com wrote Jaw-dropping experience with custom build providers - Onion Blog - Plu
on 08-02-2008 8:54 AM

You've been kicked (a good thing) - Trackback from DotNetKicks.com

ramil wrote re: Jaw-dropping experience with custom build providers
on 08-02-2008 8:55 AM

This is cool! tnx

Add a Comment

(required)  
(optional)
(required)  
Remember Me?