创建智能自定义配置文件






4.40/5 (9投票s)
一篇关于提供工具来帮助Web开发者创建更灵活的Web应用程序的文章。


引言
在我们的Web应用程序中,我们总是维护一些固定的信息,例如页面标题、页面URL、网站横幅文本、通用设置等等。将这些信息存储在web.config文件中是一个好主意。但是,从web.config文件中检索这些信息并不容易。此外,没有任何智能提示,因此开发人员应该非常小心,以确保他们输入的值实际上与web.config文件中的值相同。在本文中,我将演示如何创建一个配置工具,该工具提供了一种创建自定义、智能配置文件的新方法。
背景
本文适用于想要以下操作的开发人员:
- 将固定信息存储在配置文件中,而不是在源代码中进行硬编码。
- 在源代码中输入自定义设置值时提供智能提示。
- 使配置文件中的自定义设置值在源代码中具有强类型。
使用代码
步骤1:创建一个代码生成器。
首先要做的是创建一个代码生成器类。
/// <summary>
/// Custom code generator.
/// It will retrieve all the namespaces,classes and property from 
/// the configuration file(xml file)
/// </summary>
public class ConfigCodeGenerator
{
    //...
}
有四个关键方法:
- BuildNameSpace
- BuildAllClasses
- AddPropertyToClass
- GenerateUnitCode
实现AddPropertyToClass方法,以添加在配置文件中“Class”节点的“Property”属性中指定的属性。
/// <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");
    }
}
实现BuildAllClasses以创建配置文件中“Class”节点中指定的类。
/// <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;
        }
    }
}
实现BuildNameSpace以创建配置文件根节点中指定的唯一命名空间。
/// <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");
    }
}
最后,在GenerateUnitCode中调用BuildNameSpace。
/// <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;
}
步骤2:继承自BuildProvider并重写GenerateCode方法以添加自定义代码单元。
/// <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));
    }
}
如何使用此工具:
以下是步骤:
- 将configurationEx.dll复制到ASP.NET项目的Bin文件夹中。
- 将configex.xsd和configex.xsx复制到ASP.NET项目的App_Code文件夹中。
- 将此代码添加到web.config文件的 compilation 节点中。<buildProviders> <add extension=".configex" type="ConfigurationEx.ConfigProvider"/> </buildProviders>
- 创建一个XML文件(例如:MyConfig.configex),并将其添加到App_Code文件夹中。
- 打开MyConfig.configex,选择configex.xsd作为其架构。
- 编写您自己的配置值。这是演示项目使用的一个示例:<?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>
- 在源代码中使用您的配置。例如: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; } }
关注点
因为我必须维护大量的页面URL之类的信息,所以我试图找到一种简单的方法来减少工作时间。这就是创建此工具的原因。现在,可以编写自己的代码,而无需任何硬编码,也不必记住实际的设置值。尽情享受吧!
历史
- 版本1.0 - 2009年1月30日。


