65.9K
CodeProject 正在变化。 阅读更多。
Home

创建智能自定义配置文件

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.40/5 (9投票s)

2009年2月2日

CPOL

2分钟阅读

viewsIcon

28733

downloadIcon

244

一篇关于提供工具来帮助Web开发者创建更灵活的Web应用程序的文章。

ConfigurationEx - xml node

ConfigurationEx - Intelligence

引言

在我们的Web应用程序中,我们总是维护一些固定的信息,例如页面标题、页面URL、网站横幅文本、通用设置等等。将这些信息存储在web.config文件中是一个好主意。但是,从web.config文件中检索这些信息并不容易。此外,没有任何智能提示,因此开发人员应该非常小心,以确保他们输入的值实际上与web.config文件中的值相同。在本文中,我将演示如何创建一个配置工具,该工具提供了一种创建自定义、智能配置文件的新方法。

背景

本文适用于想要以下操作的开发人员:

  1. 将固定信息存储在配置文件中,而不是在源代码中进行硬编码。
  2. 在源代码中输入自定义设置值时提供智能提示。
  3. 使配置文件中的自定义设置值在源代码中具有强类型。

使用代码

步骤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));
    }
}

如何使用此工具:

以下是步骤:

  1. configurationEx.dll复制到ASP.NET项目的Bin文件夹中。
  2. configex.xsdconfigex.xsx复制到ASP.NET项目的App_Code文件夹中。
  3. 将此代码添加到web.config文件的 compilation 节点中。
    <buildProviders>
        <add extension=".configex" type="ConfigurationEx.ConfigProvider"/>
    </buildProviders>
  4. 创建一个XML文件(例如:MyConfig.configex),并将其添加到App_Code文件夹中。
  5. 打开MyConfig.configex,选择configex.xsd作为其架构。
  6. 编写您自己的配置值。这是演示项目使用的一个示例:
    <?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. 在源代码中使用您的配置。例如:
    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日。
© . All rights reserved.