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:
- Store a fixed information in the config file instead of hard coding in the source code.
- Provide intelligence when typing custom setting values in the source code.
- 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.
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:
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")
{
CodeTypeReference objFieldType = new CodeTypeReference(
GetPropertyType(xmlnd.Attributes["type"].Value));
String strPropertyName = xmlnd.Attributes["name"].Value;
String strFieldName = "_" + strPropertyName.ToLower();
Object objValue = GetFieldDefaultValue(xmlnd.Attributes["value"].Value,
xmlnd.Attributes["type"].Value);
String strComment = String.Empty;
if (xmlnd.Attributes["comment"] != null)
{
strComment = xmlnd.Attributes["comment"].Value;
}
CodeMemberField objField = new CodeMemberField();
objField.Attributes = MemberAttributes.Private | MemberAttributes.Static;
objField.Name = strFieldName;
objField.Type = objFieldType;
objField.InitExpression = new CodePrimitiveExpression(objValue);
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));
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:
private void BuildAllClasses(XmlNode xmlndRoot, ref CodeTypeDeclaration objTopEntity)
{
foreach (XmlNode xmlnd in xmlndRoot.ChildNodes)
{
if (xmlnd.Name == "Class")
{
CodeTypeDeclaration objSubEntity = null;
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")
{
AddPropertyToClass(ref objTopEntity, xmlndRoot.ChildNodes);
break;
}
}
}
Implement BuildNameSpace
to create the only namespace specified in root node in the config file:
private CodeNamespace BuildNameSpace(XmlDocument xmlConfigFile)
{
try
{
XmlNode xmlndRoot = xmlConfigFile.ChildNodes[1];
CodeNamespace objNameSpace = new CodeNamespace(xmlndRoot.Name);
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);
}
BuildAllClasses(xmlndRoot, ref objTopEntity);
objNameSpace.Types.Add(objTopEntity);
return objNameSpace;
}
catch(Exception ex)
{
throw new Exception(ex.Message + " Failed when execute BuildNameSpace");
}
}
Finally, call BuildNameSpace
in GenerateUnitCode
:
public CodeCompileUnit GenerateUnitCode(XmlDocument xmlConfigFile)
{
if (xmlConfigFile == null)
{
throw new ArgumentNullException("configex");
}
CodeNamespace objNameSpace = BuildNameSpace(xmlConfigFile);
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:
[PermissionSet(SecurityAction.Demand, Unrestricted = true)]
public class ConfigProvider : BuildProvider
{
public ConfigProvider()
{
}
public override void GenerateCode(AssemblyBuilder assemblyBuilder)
{
XmlDocument configFile = new XmlDocument();
try
{
using (Stream file = VirtualPathProvider.OpenFile(this.VirtualPath))
{
configFile.Load(file);
}
}
catch
{
throw;
}
ConfigCodeGenerator objCodeGenerator = new ConfigCodeGenerator();
assemblyBuilder.AddCodeCompileUnit(this,
objCodeGenerator.GenerateUnitCode(configFile));
}
}
How to use this tool
Here are the steps:
- Copy configurationEx.dll to the ASP.NET project's Bin folder.
- Copy configex.xsd and configex.xsx to the ASP.NET project's App_Code folder.
- Add this code in the compilation node of the web.config file:
<buildProviders>
<add extension=".configex" type="ConfigurationEx.ConfigProvider"/>
</buildProviders>
- Create an XML file (for example: MyConfig.configex), and add it to the App_Code folder.
- Open MyConfig.configex, choose configex.xsd as its schema.
- Write your own config values. Here is an example used by the demo project:
="1.0" ="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>
- Use your configuration in the source code. For example:
protected void Page_Load(object sender, EventArgs e)
{
if ( !IsPostBack )
{
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