For example, in ASP.NET 2.0 a build provider creates a Web service proxy class from a WSDL file. But there's more. Typed DataSets are created dynamically from an XSD file; themes are created from a subtree of files that contains stylesheets and skins; required satellite assemblies for localized resources are now created dynamically from .resx files. A build provider generates compilable code and keeps it in sync with the source file; as the source file changes, the build provider kicks in again and updates everything.
The ASP.NET root web.config file contains a number of bindings between file types and native build providers. Bindings are stored in a new section under the < compilation> section. The new section is named <buildProviders> and takes the following form:
<compilation>
<buildProviders>
<add extension=".aspx"
type="System.Web.Compilation.PageBuildProvider" />
<add extension=".ascx"
type="System.Web.Compilation.UserControlBuildProvider" />
<add extension=".master"
type="System.Web.Compilation.MasterPageBuildProvider" />
<add extension=".asmx"
type="System.Web.Compilation.WebServiceBuildProvider" />
<add extension=".ashx"
type="System.Web.Compilation.WebHandlerBuildProvider" />
<add extension=".resx"
type="System.Web.Compilation.ResXBuildProvider" />
<add extension=".resources"
type="System.Web.Compilation.ResourcesBuildProvider" />
<add extension=".wsdl"
type="System.Web.Compilation.WsdlBuildProvider" />
<add extension=".xsd"
type="System.Web.Compilation.XsdBuildProvider" />
<add extension=".js"
type="System.Web.Compilation.ForceCopyBuildProvider" />
</buildProviders>
</compilation>
All these build providers are internal classes defined in the System.Web.Compilation namespace inside the system.web assembly. All these classes derive from one common root--the BuildProvider class.
An important change in ASP.NET 2.0 that somewhat relates to the introduction of build providers is that you can no longer have class files scattered in the project folders. All class source files you need must go in the App_Code folder. In ASP.NET 1.x, instead, these files could be located anywhere in the project. The only class files allowed outside the App_Code folder are code-behind classes of Web pages. Needless to say, you can always add class files to a class library project and link the resulting assembly to the ASP.NET application.
The list of native build providers can be extended at will to incorporate custom build providers for custom file types. All that you have to do is create a new build provider class--that is, a class that inherits from System.Web.Compilation.BuildProvider. At the very minimum, the derived class overrides the method GenerateCode. Here's the typical outline of the sample class:
public class MyBuildProvider : BuildProvider { public MyBuildProvider() { } public override void GenerateCode(AssemblyBuilder ab) { // Get the virtual path to the source file string fileName = base.VirtualPath; // Get the tree representing the generated code CodeCompileUnit code = BuildCodeTree(fileName); // Build an assembly using the code tree ab.AddCodeCompileUnit(this, code); } }
As the name suggests, the method GenerateCode parses the input file and generates one or more source classes with the collected information. When done, the code is passed to the assembly builder class and compiled. Nicely enough, the AssemblyBuilder class not just compiles the class to an assembly but also causes the dynamically created assembly to be loaded in the current application domain.
The BuildCodeTree method is the core of the build provider. It is responsible for exposing the code to compile as a CodeDOM tree. Internally, BuildCodeTree processes the input file and generates a CodeDOM tree-that is an object graph that describes a piece of code (type definition, members, methods, statements) in a language-agnostic manner.
When it comes to generating a class based on the contents of an input file, CodeDOM is not the only possible option. You can also construct the final source code to be compiled by concatenating strings in a text writer object. In this case, GenerateCode takes a slightly different form:
public override void GenerateCode(AssemblyBuilder ab) { TextWriter tw = ab.CreateCodeFile(this); if (tw == null) return; // Parse the file and generate the code we want from it string code = ParseFileAndCreateCode(base.VirtualPath); tw.Write(code); tw.Close(); }
Any changes made to the source of a dynamically compiled file automatically invalidates the corresponding assembly, which will then be recreated. You probably won't write custom build providers every week; but still build providers represent a powerful feature for empowering applications. As an example, consider that ASP.NET AJAX--the new version of ASP.NET that incorporates AJAX features--uses build providers to create gateway classes that connect to external Web services.