创建智能自定义配置文件






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日。