Creating a Custom BuildProvider in ASP.NET 2.0
 
Published: 18 Jan 2007
Abstract
In this article Bilal Haidar explains the usage of a BuildProvider in ASP.NET 2.0 and shows you how to create a custom BuildProvider to access an XML file in the form of a C# class at design time.
by Bilal Haidar
Feedback
Average Rating: This article has not yet been rated.
Views (Total / Last 10 Days): 60570/ 106

Introduction

Last week we were trying to generate some source code in the form of a C# class out of an XML file that contains some settings configuration at run time and yet, be able to have an intellisense at design time. This is all said and the .NET environment is .NET 1.1. After so much research in this field, we understood that what we are trying to do is a bit hard, but is still doable in .NET 1.1. The work around was to have some sort of an Interface that the generated class implements and, hence, a full access to all the configurations in the XML file through the dynamically generated class in C#. As a drawback you can easily see that, with the use of the Interface, every time we change the XML file we have to change the Interface and have a new build. Not feasible, but it does the job!

Until recently, we have posted on the ASP.NET forums about this issue and Scott Allen was there to refer to a post on Scott Guthrie’s blog that talks about BuildProviders in ASP.NET 2.0, which does exactly what we have been trying to do in the .NET 1.1 Framework.

In this article we will explain what a build provider is, how it is used by the ASP.NET 2.0 Web Application, refer you to some good links online about this topic, and, off course, show you how to create a custom BuildProvider to load an XML file into a C# class and access all the properties of that class at design time!

BuildProviders in ASP.NET 2.0

ASP.NET 2.0 Web Applications use the BuildPrrovider objects to generate source code for the different file types that exist in the application.  What happens is for each file type a different BuildProvider is built and the different properties, methods, fields, etc. for each file type is accessible within the Visual Studio 2005 IDE.

ASP.NET 2.0 includes a set of built-in BuildProviders, for example:

PageBuildProvider

UserControlBuildProvider

MasterPageBuildProvider

With the use of the above providers, you as a developer are able, while working inside the Visual Studio 2005, to access the different properties, methods, etc. at design time in the code-behind or code-inline of a Web Form or UserControl.

Have you ever asked yourself how does Visual Studio, when you add a usercontrol into a page, know all the properties and methods of that usercontrol? Well, BuildProviders are used to do all the work for you! Moreover, BuildProviders are used by the “aspnet_compile.exe” utility to generate the pre-compiled version of a web application as said in this blog post: ASP.NET 2.0 BuildProviders.

For more information on BuildProviders check the MSDN Documentation at: BuildProvider Class.

Create a Custom BuildProvider

Usually you do not create an instance of the BuildProvider. What you do is create a new class that derives from the BuildProvider class. After you create this class you will need to configure this BuildProvider in the web.config in the Compilation section.

There are several methods and properties in the BuildProvider class that you will need to override or use while developing the new BuildProvider.

The main method that you will need to override is the GeneratCode method. This method takes as input parameter an AssemblyBuilder instance. Therefore, the code inside the GenerateCode method should create a new CodeCompileUnit then add it to the AssemblyBuilder. The BuildProvider generates source code for the different files registered in the appropriate language and the AssemblyBuilder combines the source code generated by each BiuldProvider into the assembly.

There is a property on the BiuldProvider class called CodeCompilerType. This property is a read-only property that retrieves the language of the generated source code. Usually this information is sent by the ASP.NET environment through the AssemblyBuilder, however, you can implement this property explicitly and an example of implementing it can be checked in the BuildProvder Class MSDN Documentation.

The CodeCompileUnit, that provides a container for a CodeDOM program graph, will include the namespace, the class(s) and all the needed properties and methods. Hence, the need to add that compile unit into the current running assembly to be able to compile the generated class and add it to the current assembly and have the ability to access the class and all its properties and methods at design time in your classes!

By default, ASP.NET 2.0 environment passes an instance of the AssemblyBuilder based on the preferred compiler language and the context of the file(s) to the BuildProvider methods to be able to generated code for those files given their paths and the programming language to use in generating the source code.

Type of the BuildProvider to build

In this article we will assume we have an XML file that represents a settings XML file for the web application, the structure of the file is as follows.

Listing 1

<?xml version="1.0" encoding="utf-8" ?>
<Settings namespace="SettingsNamespace" classname="bhSettings">
  <Setting type="System.String" name="Name" value="Bilal Haidar" />
  <Setting type="System.Int32" name="Age" value="26" />
</Settings> 

As you can see, we have a Settings XML file with a Setting record. Each setting record has three attributers, the Data Type, Name, and Value attributes. Therefore, the source code generated for such a file should contain two properties, Name and Age, whose data types are String and Int32, and their values are “Bilal Haidar” and 26 respectively.

The BuildProvider that we are going to build in this article shall parse this XML file and generate a C# class (since we will create a new Web Site – C#) that has two properties as mentioned above. This way, developers working on the web site will have a very nice intellisense for this XML file without having to write a single line of code to parse the above XML file. They will only be responsible for accessing the different settings of the XML file through a well structured C# class. This is why BuildProviders are so cool!

Constructing the SettingsBuildProvider

The following code shows the main structure of the SettingsBuildProvider class deriving from the BuildProvider.

Listing 2

public class SettingsBuildProvider: BuildProvider
{
  public override void GenerateCode(AssemblyBuilder assemblyBuilder)
  {
    // Get the path to the filename
    string fileName = base.VirtualPath;
    SettingsGenLib settingsGenLib = new SettingsGenLib();
    // Create a new CodeCompileUnit
    CodeCompileUnit codeUnit = settingsGenLib.GenerateSettingsClass(fileName,
      true);
 
    // Add the unit to be compiled and added to the current assembly
    assemblyBuilder.AddCodeCompileUnit(this, codeUnit);
  }
}

You can see a property called VirtualPath, which is defined on the base class that is the BuildProvider. This property represents the context of the file for which the source code is to be generated and, hence, it contains the path to that file in the current web application and mainly in the App_Code folder inside your application.

Then we define a new instance of an internal class that we added to handle all the details of generating the source code and is called SettingsGenLib. The SettingsGenLib has a single public method called GenerateSettingsClass that takes as input the following two parameters.

FileName: File name to generate the source code to.

IsVirtualInputFile: A Boolean specifying that the file name is read from the VirtualPath property of the BuildProvider or not. It will affect the way we access and parse the XML file.

The GenerateSettingsClass method returns an instance of the CodeCompileUnit which is then added to the instance of the AssemblyBuilder that has been passed by the ASP.Net environment to be able to compile and generate the class(s) out of the CodeCompileUnit created.

The SettingsGenLib class sand GenerateSettingsClass method

The SettingsGenLib is a helper class we have created to include all the details of implementing the BuildProvider. This way it will be easier for you to focus on the main class that is deriving from the BiuldProvider and the methods that need to be overridden.

The GenerateSettingsClass method is a long method, so we will split it into pieces and explain each separately.

Listing 3

public CodeCompileUnit GenerateSettingsClass(string fileName, bool isVirtualInputFile)
{
  // Create a new compile unit
  CodeCompileUnit compileUnit = new CodeCompileUnit();

The above code snippet shows the method header and the input parameters.

First of all, a new instance of the CodeCompileUnit is created; this instance will hold the namespace and class declarations to be compiled later on and added to the AssemblyBuilder.

Listing 4

// Hold the contents of the XML file
XmlDocument doc = new XmlDocument();
if (isVirtualInputFile)
using(Stream inFile = VirtualPathProvider.OpenFile(fileName))
{
  doc.Load(inFile);
}
 
else
using(Stream inFile = File.Open(fileName, FileMode.Open))
{
  doc.Load(inFile);
}

The code above loads the XML file into an XMLDocument instance. The isVirtualInputFile says whether we are loading a virtual path from the App_Code, if true, then we can use the VirtualPathProvider OpenFile static method. Hopefully, in the near future we will be delivering an article on the VirtualPathProvider and showing you how to create a custom VirtualPathProvider.

Listing 5

// Get a node list representing the root
XmlNodeList nodeList = doc.SelectNodes("/Settings");
 
// Get the namespace of the generated class
string ns = "";
if (nodeList[0].Attributes["namespace"].InnerText != "")
  ns = nodeList[0].Attributes["namespace"].InnerText;
// Define the namespace name
if (ns == string.Empty)
  ns = "DefaultNamespace";
 
// Get the class name
string className = "";
if (nodeList[0].Attributes["classname"].InnerText != "")
  className = nodeList[0].Attributes["classname"].InnerText;
// Define the class name
if (className == string.Empty)
  className = "DefaultClass";
else
  className = FormatPropertyName(className);

In the code above, we have selected the root node since it contains two important attributes, the Namespace and Class name. We select the root node and set the namespace and class name respectively using some useful methods on the XmlDocument and with the help of XPath.

Now that we have both the namespace and class name, it is time to add them to the CodeCompileUnit we created above.

Listing 6

// Create a new namespace
CodeNamespace namespaceUnit = new CodeNamespace(ns);
 
// Add the namespace to the compile unit
compileUnit.Namespaces.Add(namespaceUnit);
 
// Create a new class
CodeTypeDeclaration settingsClass = new CodeTypeDeclaration(className);
 
// Add it to the namespace types
namespaceUnit.Types.Add(settingsClass);

First of all, we create a new namespace using CodeNamespace object giving it the namespace name we retrieved from the XML file then add it to the Namespace collection of the CodeCompileUnit.

After that, we create a new instance of the CodeTypeDeclaration, which represents the new class to be generated, and add it to the Types collection of the newly added namespace.

Listing 7

// Get the list of setting fields
nodeList = doc.SelectNodes("//Settings/Setting");
foreach (XmlNode node in nodeList)
{
  string typename = node.Attributes["type"].InnerText;
  string propName = node.Attributes["name"].InnerText;
  string propValue = node.Attributes["value"].InnerText;
  XmlRecord xmlRecord = new XmlRecord(typename, propName, propValue);
 
// Create field
  CodeMemberField itemField = CreateField(xmlRecord);
  settingsClass.Members.Add(itemField);
 
// Create Property
  CodeMemberProperty itemProperty = CreateProperty(xmlRecord);
  settingsClass.Members.Add(itemProperty);
}

In the code above we used an XPath expression to retrieve all the Setting records from the XML file. Each Setting record will be transformed into a property in the new generated class.

The code is pretty simple; we get all the Setting records, loop through them one by one and access the three main attributes used for each record: type, name, and value. The type and name will be used for the type of the member and property created for this record and the value will be used to set the initial value of the member mentioned in above.

The code creates a new CodeMemberField instance by calling the helper method CreateField, which we will check very soon. The created field is added to the members' collection of the class to be generated.

In addition to the field created, a new property is created by using the CodeMemberProperty object. Another helper method is used, CreateProperty.

The above code is repeated for each Setting record inside the XML file, thus creating a new property and field for each Setting record.

Finally, the GenerateSettingsClass returns the CodeCompileUint we have created early in the method, where we added the namespace, class, members and properties.

The CodeCompileUnit returned is added to the AssemblyBuilder instance to compile and generate the class and add it to the current assembly.

CreateField method

This method is a helper method used to create a new field.

Listing 8

private CodeMemberField CreateField(XmlRecord record)
{
// Create a new field with the specified data type in the XML file
  CodeMemberField cfield = new CodeMemberField(record.typeName, FormatFieldName
    (record.propName));
 
// Specify that the field should be private and static
  cfield.Attributes = MemberAttributes.Private | MemberAttributes.Static;
 
// Assign the value found in the XML file to this field
  string valueToAssign = "";
  switch (record.typeName)
  {
    case "System.String":
    case "System.Guid":
      valueToAssign = string.Format("\"{0}\"", record.propValue);
      break;
    case "System.Char":
      valueToAssign = string.Format("'{0}'", record.propValue);
      break;
    default:
      valueToAssign = string.Format("{0}", record.propValue);
      break;
  }
  cfield.InitExpression = new CodeSnippetExpression(valueToAssign);
 
  return cfield;
}

The CreateField method takes as input an XmlRecord parameter. The XmlRecord is a structure we have created to join the type, name, and value of each Setting record.

First of all, a new instance of the CodeMemberField is created with the type and name of the field. The field is set to be a private and static field. Finally, we assign a value for this field by using the InitExpression property. We are taking special consideration for the string, guid, and char data types to include required double and single quotes respectively.

CreateProperty method

This method is similar to the above method in which we create a new public and static property.

Listing 9

private CodeMemberProperty CreateProperty(XmlRecord record)
{
// Create a new property
  CodeMemberProperty cproperty = new CodeMemberProperty();
 
// Set the name, type, and access of the proprety
  cproperty.Name = FormatPropertyName(record.propName);
  cproperty.Type = new CodeTypeReference(record.typeName);
  cproperty.Attributes = MemberAttributes.Public | MemberAttributes.Static;
 
// We only need Read-Only properties
  cproperty.GetStatements.Add(new CodeMethodReturnStatement(new
    CodeFieldReferenceExpression(null, FormatFieldName(record.propName))));
 
  return cproperty;
}

A new instance of the CodeMemberProperty is created following this the name, type and attributes are set respectively. Since the XML file contains all the data and no need to set any of the properties, we will only be creating read-on properties. Therefore, we need to add a Get section for the new property created and this is done by using the GetStatements property which contains the statements in the “get” section of a property. The “get” section is simple, we are just returning the member we created above.

This way, we have finished creating the BuildProvider. Now, we create a new Web application and then add a reference to this BuildProvider. The BuildProvider in this article was created in a separate Class Library.

After adding the reference to the SettingsBuildProvider, we need to configure the BuildProvider in the Web.config.

Configuring the SettingsBuildProvider in the Web.config

We will need to tell the ASP.NET application to use the new BuildProvider we created. To do so, we need to add this section into the web.config.

Listing 10

<system.web>
  <compilation debug="true">
<buildProviders>
  <add extension=".settings" type="BuildProviders.SettingsBuildProvider"/>
</buildProviders>
  </compilation>
</system.web>

As you can see, we have added a new buildProviders entry. In the entry we specify the extension of the file that the newly created BiuldProvider will generate the source code for. The file extension is a custom one of your choice and we have chosen it to be “.settings.” The type specifies the full path to the BuildProvider we created.

Once you configure your web application as above, you still need to add the XML file we displayed into the App_Code folder. This is a must do step! Create a new XML file with the extension of “.settings” and place the file in the App_Code folder.

Now that you have finished all the configurations required, you can open any web form, go to the code-behind and try to write down the name of the namespace you have chosen in the XML file. Then you will see the inteliisense is active, the class name will be shown, and all the properties within the class are accessible as read-only properties as shown below.

Listing 11

string name = SettingsNamespace.BhSettings.Name;
int age = SettingsNamespace.BhSettings.Age;

You can see that the properties are type-safe so no need to convert any data type, simply use them as they are!

Finally, this is the class that has been dynamically generated for you at the design time.

Listing 12

namespace SettingsNamespace
{
  public class BhSettings
  {
    private static string name = "Bilal Haidar";
 
    private static int age = 26;
 
    public static string Name
    {
      get
      {
        return name;
      }
    }
    public static int Age
    {
      get
      {
        return age;
      }
    }
  }
}
Downloads

References

Conclusion

We have briefly explained what the BuildProvider is and how it is used by the ASP.NET 2.0 Web applications to generated source code for the different types of files used within an application. Then we presented a complete example on how to create a custom build provider.



User Comments

Title: DAL build provider   
Name: deepak nalwaysa
Date: 2010-12-13 11:38:11 PM
Comment:
very nicce aticle and i read alwas article
Title: DAL Build Providers   
Name: Thomsen
Date: 2009-11-17 2:59:36 AM
Comment:
Here an example of how you can create DAL with ASP.NET Build Providers:

1. http://www.codeproject.com/KB/aspnet/DALComp.aspx

2. http://jbaurle.wordpress.com/2007/07/24/creating-dal-components-using-custom-aspnet-build-providers-and-compiler-techniques/

Thomsen
Title: Excellent   
Name: shahfein Khan
Date: 2008-09-01 10:26:46 AM
Comment:
each and every point is covered very neatly.
Title: About BuildProvide   
Name: Mike Klok
Date: 2008-06-18 5:00:18 AM
Comment:
Hi very good article
Title: About BuildProvide   
Name: tonyduan
Date: 2008-04-20 11:30:29 PM
Comment:
Hi,
Your writing is very very pretty.
i have a question,where can find the cs file be generated,and can i place the class in a cs file i appointed?
Title: About BuildProvide   
Name: manish
Date: 2007-01-23 6:04:50 AM
Comment:
Hi, This is very good article.






Community Advice: ASP | SQL | XML | Regular Expressions | Windows


©Copyright 1998-2017 ASPAlliance.com  |  Page Processed at 2017-08-20 7:36:23 AM  AspAlliance Recent Articles RSS Feed
About ASPAlliance | Newsgroups | Advertise | Authors | Email Lists | Feedback | Link To Us | Privacy | Search