Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Creating a custom intelligent configuration file

0.00/5 (No votes)
2 Feb 2009 1  
An article on providing a tool to help web developers to create more flexible web applications.

ConfigurationEx - xml node

ConfigurationEx - Intelligence

Introduction

In our web applications, we always maintain some fixed information such as page titles, page URLs, web site banner text, common settings etc. Storing such information in the web.config file is a good idea. However, retrieving these information from the web.config file is not easy. Also, there is no intelligence at all, so developers should be very carful to make sure the value they typed is actually the same as in the web.config file. In this article, I'll demonstrate how to create a configuration tool which provides a new way to create a custom, intelligent config file.

Background

This article is for developers who want to:

  1. Store a fixed information in the config file instead of hard coding in the source code.
  2. Provide intelligence when typing custom setting values in the source code.
  3. Make custom setting values in the config file with strong typing in the source code.

Using the code

Step 1: Create a code generator.

The first thing to do is to create a code generator class.

/// <summary>
/// Custom code generator.
/// It will retrieve all the namespaces,classes and property from 
/// the configuration file(xml file)
/// </summary>
public class ConfigCodeGenerator
{
    //...
}

There are four key methods:

  • BuildNameSpace
  • BuildAllClasses
  • AddPropertyToClass
  • GenerateUnitCode

Implement the AddPropertyToClass method to add the property specified in the "Property" attribute of the "Class" node in the config file:

/// <summary>
/// Add properties to existed class
/// </summary>
/// <param name="objEntity">Existed class to add properties(reference)</param>
/// <param name="xmlndLs">Xml nodes which hold all properties </param>
/// <returns>True - success False - failed</returns>
private Boolean AddPropertyToClass(ref CodeTypeDeclaration objEntity, 
                                   XmlNodeList xmlndLs)
{
    try
    {
        if ( (objEntity != null) && (xmlndLs != null) && (xmlndLs.Count > 0))
        {

            foreach (XmlNode xmlnd in xmlndLs)
            {
                if(xmlnd.Name == "Property")
                {
                    // Get field type
                    CodeTypeReference objFieldType = new CodeTypeReference(
                           GetPropertyType(xmlnd.Attributes["type"].Value));

                    // Get property name then lower it and add "_"
                    // in front of the string to make the field name 
                    String strPropertyName = xmlnd.Attributes["name"].Value;
                    String strFieldName = "_" + strPropertyName.ToLower();

                    // Get default value of the field
                    Object objValue = GetFieldDefaultValue(xmlnd.Attributes["value"].Value, 
                                      xmlnd.Attributes["type"].Value);

                    // Get comments
                    String strComment = String.Empty;
                    if (xmlnd.Attributes["comment"] != null)
                    {
                        strComment = xmlnd.Attributes["comment"].Value;
                    }

                    // Generate field
                    CodeMemberField objField = new CodeMemberField();
                    objField.Attributes = MemberAttributes.Private | MemberAttributes.Static;
                    objField.Name = strFieldName;
                    objField.Type = objFieldType;
                    objField.InitExpression = new CodePrimitiveExpression(objValue);    // ???ֵ

                    // Generate property
                    CodeMemberProperty objProperty = new CodeMemberProperty();
                    objProperty.Attributes = MemberAttributes.Public | MemberAttributes.Static;
                    objProperty.Name = strPropertyName;
                    objProperty.Type = objFieldType;
                    objProperty.GetStatements.Add(new CodeSnippetStatement("return " + 
                                                  strFieldName + ";"));
                    objProperty.Comments.Add(new CodeCommentStatement("<summary>", true));
                    objProperty.Comments.Add(new CodeCommentStatement(strComment, true));
                    objProperty.Comments.Add(new CodeCommentStatement("</summary>", true));

                    // Add field and property to class
                    objEntity.Members.Add(objField);
                    objEntity.Members.Add(objProperty);
                }
            }

            return true;
        }

        return false;
    }
    catch(Exception ex)
    {
        throw new Exception(ex.Message + "\r\nFailed to add properties to class");
    }
}

Implement BuildAllClasses to create the classes which are specified in the "Class" nodes in the config file:

/// <summary>
/// Recursive search all the nodes in the xml file 
/// to find the useful elements 
/// </summary>
/// <param name="xmlndRoot">Root of the xml file</param>
/// <param name="objTopEntity">The topmost class in the unit code</param>
private void BuildAllClasses(XmlNode xmlndRoot, ref CodeTypeDeclaration objTopEntity)
{
    foreach (XmlNode xmlnd in xmlndRoot.ChildNodes)
    {
        
        if (xmlnd.Name == "Class")
        {
            CodeTypeDeclaration objSubEntity = null;
            // add child class
            if(xmlnd.Attributes["comment"] != null)
            {
                objSubEntity = BuildEmptyClass(xmlnd.Attributes["name"].Value, 
                                               xmlnd.Attributes["comment"].Value);
            }
            else
            {
                objSubEntity = BuildEmptyClass(xmlnd.Attributes["name"].Value);
            }
            
            objTopEntity.Members.Add(objSubEntity);

            BuildAllClasses(xmlnd, ref objSubEntity);
        }
        else if (xmlnd.Name == "Property")
        {
            // add property
            AddPropertyToClass(ref objTopEntity, xmlndRoot.ChildNodes);
            break;
        }
    }
}

Implement BuildNameSpace to create the only namespace specified in root node in the config file:

/// <summary>
/// Build namespace
/// </summary>
/// <param name="xmlConfigFile">configuration file</param>
/// <returns>the generated namespace</returns>
private CodeNamespace BuildNameSpace(XmlDocument xmlConfigFile)
{
    try
    {
        
        // The root is really the 2nd child node in the xml file cause the 1st one
        // is ""
        XmlNode xmlndRoot = xmlConfigFile.ChildNodes[1];

        // Build namespace use the root name
        CodeNamespace objNameSpace = new CodeNamespace(xmlndRoot.Name);

        // The topmost class
        CodeTypeDeclaration objTopEntity = null;
        
        if(xmlndRoot.Attributes["comment"] != null)
        {
            objTopEntity = BuildEmptyClass(xmlndRoot.Name, 
                                           xmlndRoot.Attributes["comment"].Value);
            objNameSpace.Comments.Add(new CodeCommentStatement("<summary>", true));
            objNameSpace.Comments.Add(new 
                         CodeCommentStatement(xmlndRoot.Attributes["comment"].Value, true));
            objNameSpace.Comments.Add(new CodeCommentStatement("</summary>", true));
        }
        else
        {
            objTopEntity = BuildEmptyClass(xmlndRoot.Name);
        }
        

        // Build all classes in the namespace
        BuildAllClasses(xmlndRoot, ref objTopEntity);

        // Add the topmost class to the namespace
        // Notice: objTopEntity already include all the subclasses and properties
        objNameSpace.Types.Add(objTopEntity);

        return objNameSpace;
    }
    catch(Exception ex)
    {
        throw new Exception(ex.Message + " Failed when execute BuildNameSpace");
    }
}

Finally, call BuildNameSpace in GenerateUnitCode:

/// <summary>
/// Generate unit code
/// </summary>
/// <param name="xmlConfigFile">xml file to be parsed</param>
/// <returns></returns>
public CodeCompileUnit GenerateUnitCode(XmlDocument xmlConfigFile)
{
    if (xmlConfigFile == null)
    {
        throw new ArgumentNullException("configex");
    }

    // Build the only namespace
    CodeNamespace objNameSpace = BuildNameSpace(xmlConfigFile);
    
    // Generate the unit code
    CodeCompileUnit objCompileUnit = new CodeCompileUnit();
    objCompileUnit.Namespaces.Add(objNameSpace);

    return objCompileUnit;
}

Step 2: Inherit from BuildProvider and override the GenerateCode method to add a custom code unit:

/// <summary>
/// Custom config provider
/// </summary>
[PermissionSet(SecurityAction.Demand, Unrestricted = true)]
public class ConfigProvider : BuildProvider
{

    public ConfigProvider()
    {
    }

    /// <summary>
    /// Override this method to generate own unit code
    /// </summary>
    /// <param name="assemblyBuilder">Assembly builder provided by the base class</param>
    public override void GenerateCode(AssemblyBuilder assemblyBuilder)
    {
        XmlDocument configFile = new XmlDocument();

        try
        {
            using (Stream file = VirtualPathProvider.OpenFile(this.VirtualPath))
            {
                configFile.Load(file);
            }
        }
        catch
        {
            throw;
        }

        // Create a code generator to generate the whole code
        ConfigCodeGenerator objCodeGenerator = new ConfigCodeGenerator();

        // Generate the unit code and add it to the assembly builder
        assemblyBuilder.AddCodeCompileUnit(this, 
                        objCodeGenerator.GenerateUnitCode(configFile));
    }
}

How to use this tool

Here are the steps:

  1. Copy configurationEx.dll to the ASP.NET project's Bin folder.
  2. Copy configex.xsd and configex.xsx to the ASP.NET project's App_Code folder.
  3. Add this code in the compilation node of the web.config file:
    <buildProviders>
        <add extension=".configex" type="ConfigurationEx.ConfigProvider"/>
    </buildProviders>
  4. Create an XML file (for example: MyConfig.configex), and add it to the App_Code folder.
  5. Open MyConfig.configex, choose configex.xsd as its schema.
  6. Write your own config values. Here is an example used by the demo project:
    <?xml version="1.0" encoding="utf-8" ?>
    <WebsiteData xmlns="http://mazong1123.ys168.com/configex.xsd" 
                 comment="Custom website settings">
      <Class name="PageUrlGroup" comment="All page urls in this website">
        <Property name="MainPage" value="~/Default.aspx" 
                  type="string" comment="Main page url"/>
        <Property name="SecondPage" value="~/SecondPage.aspx" 
                  type="string" comment="Second page url"/>
      </Class>
    
      <Class name="PageTitles" comment="Titles displayed in the brower">
        <Property name="MainPage" value="ConfigurationEx Test Page" 
                  type="string" comment="Main page title"/>
        <Property name="SecondPage" value="Second Page" 
                  type="string" comment="Second page title"/>
      </Class>
    
      <Class name="ButtonText" comment="Collection of button text">
        <Property name="ToSecondPage" value="Go to second page" type="string"/>
        <Property name="ToMainPage" value="Back to main page" type="string"/>
      </Class>
      
      <Class name="PageInfomation" comment="Collection of page infomation">
        <Property name="Title" value="Thanks to use ConfigurationEx!" 
                  type="string" comment="Main page title"/>
        <Property name="Banner" value="This is a banner infomation from custom config file" 
                  type="string" comment="Banner infomation"/>
      </Class>
    </WebsiteData>
  7. Use your configuration in the source code. For example:
    protected void Page_Load(object sender, EventArgs e)
    {
        if ( !IsPostBack )
        {
            // Initialize page data
            Title = WebsiteData.WebsiteData.PageTitles.MainPage;
            lbTitle.Text = WebsiteData.WebsiteData.PageInfomation.Title;
            lbBanner.Text = WebsiteData.WebsiteData.PageInfomation.Banner;
    
            lbBtnToSecondPage.PostBackUrl = 
              WebsiteData.WebsiteData.PageUrlGroup.SecondPage;
            lbBtnToSecondPage.Text = 
              WebsiteData.WebsiteData.ButtonText.ToSecondPage;
        }
    }

Points of interest

Since I have to maintain a lot of information like page URLs, I try to find an easy way to reduce the work time. That's why this tool was created. Now, it's possible to write my own code without any hard coding and not having to remember the actual setting values. Just enjoy it!

History

  • Version 1.0 - 1/30/2009.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here