- Posted by matt on May 28, 2008
A very common requirement for web applications is that they be configurable in an easy way. A lot of times it doesn't make sense to use a database to store some simple settings for your web site, especially if the application doesn't require one in the first place. There are a ton of ways to accomplish this task, but it seems to me that XML is perfectly suited for this problem. I don't mean to presume that it is better than other solutions but in case it fits the bill for your project, here is once way it can be used.
In this post I'll demonstrate one simple way to use XML to retrieve, edit, and save settings for your ASP.NET application.
The Scenario
You're building an ASP.NET application in C# that needs to have some user configurable settings. You don't want to use a database to store these settings and you want the ability to edit and save the settings through a web page. The edit page should be created dynamically from the settings contained in the XML file so that the page will not have to be updated if a setting is added.
Limitations of this example
To begin, this is not a "killer" solution to settings management. You can find a much more capable approach to XML settings by viewing the source of BlogEngine.NET and its setting management system.
This example will not allow for a user to add settings on their own that will be automatically recognized by the code. If a new setting is to be added, it will need to have a property set up and defined in the Settings class as well as be added to the Settings.xml file.
This example will not cover the vast possibilities for sub-settings you can obtain using element attributes and many other XML features.
Mostly, this is just a demonstration of how to accomplish the simple goal of storing and retrieving settings in XML.
Step 1: Setup
To begin, I would create an XML file in the App_Data directory of your application. For this example, I will call the file Settings.xml. If you're using Visual Studio just right click the App_Data folder and Add a new item. The file will be pre-filled with the standard xml encoding:
<?xml version="1.0" encoding="utf-8" ?>
You should then add a root element to the XML file. I chose the root element to be <settings></settings>. Within those tags, all of your settings will be listed out as child elements. So far the file looks like this:
<?xml version="1.0" encoding="utf-8" ?>
<settings>
</settings>
Save the file to the App_Data folder.
Step 2: Create the Settings.cs class
Next, create a Settings class in your App_Code folder (or name it whatever you like). This class will be responsible for retrieving and updating the data in the Settings.xml file. Set the class to static so that it is only loaded once for the application.
Step 3: Define the setting elements and properties
You should now begin entering the settings you'll be using for the application. In the Settings.xml file, create an element for each of the settings you will need for your program and pre-populate them with defaults if you like.
Tip: If your setting contains any markup or unsafe characters, surround the settings in the <![CDATA[ your_setting_value_here ]]> tag to avoid xml errors.
I've added some example setting elements to my Settings.xml file below:
<?xml version="1.0" encoding="utf-8"?>
<settings>
<MaxImageUploadSizeKB>1000</MaxImageUploadSizeKB>
<ThumbnailWidth>100</ThumbnailWidth>
<ThumbnailHeight>90</ThumbnailHeight>
<UploadedImageDirectory>~/images/postImages/</UploadedImageDirectory>
<SessionExpiresMinutes>20</SessionExpiresMinutes>
</settings>
Next, for each of the settings you added to the Settings.xml file, create a property for it in the Settings.cs class. I'm assuming >= .NET 2.0 for the code here.
public static class Settings
{
/// <summary>
/// The maximum file size in KB for uploaded images. [Default: 1000KB]
/// </summary>
public static int MaxImageUploadSizeKB { get; set; }
/// <summary>
/// The whole number integer width of the image thumbnails [Default:100]
/// </summary>
public static int ThumbnailWidth { get; set; }
/// <summary>
/// The whole number integer height of the image thumbnails [Default:90]
/// </summary>
public static int ThumbnailHeight { get; set; }
/// <summary>
/// The directory to upload and read post images from/to [Default: ~/images/postImages/]
/// </summary>
public static string PostImageDirectory { get; set; }
/// <summary>
/// The number of minutes before a checkout session expires. [Default: 20]
/// </summary>
public static int SessionExpiresMinutes { get; set; }
}
Step 4: Read the XML file into the properties
I'm sure I will catch some heat for using a DataSet to read the XML file as it seems to go against the whole point of using an XML file in the first place, but I like the simplicity and readability of the code it produces...Plus it's dead easy. The following function will assign the properties in the Settings class to the values in the XML file:
/// <summary>
/// Load the settings from Settings.xml
/// </summary>
public static void LoadSettings()
{
DataSet ds = new DataSet();
ds.ReadXml(HttpContext.Current.Server.MapPath("~/App_Data/Settings.xml"));
if (ds != null && ds.Tables[0].Rows.Count > 0)
{
DataRow dr = ds.Tables[0].Rows[0];
MaxImageUploadSizeKB = Int32.Parse(dr["MaxImageUploadSizeKB"].ToString() ?? "1000");
ThumbnailWidth = Int32.Parse(dr["ThumbnailWidth"].ToString() ?? "100");
ThumbnailHeight = Int32.Parse(dr["ThumbnailHeight"].ToString() ?? "90");
PostImageDirectory = dr["UploadedImageDirectory"].ToString() ?? "~/images/postImages/";
SessionExpiresMinutes = Int32.Parse(dr["SessionExpiresMinutes"].ToString() ?? "20");
}
}
Note that the ?? operator will use the value following the ?? if the expression before it is null. This allows you to hard code defaults if you desire instead of having to do so in the XML file.
Now that you have the properties filled with data from the XML file, you can use them in your application just by referencing Settings.ThumbnailWidth for example.
Step 5: Viewing / Editing the Settings file in a web page
To display the current settings in a way that will allow them to be editable, but also allow the page to work regardless of changes to the available settings, here is something you could do.
- Create a normal .aspx page for the purpose of editing the settings.
- Add a table to the page and set runat="server" and give it an ID.
- On the Page_load event, call a function to dynamically create the setting label along with a text box for changing the value.
- Provide a Save button that will write the changes back to the XML file.
The following function will accomplish the loading task (there are some css styles in this function that are not included here):
private void LoadSettings()
{
XmlDocument xDoc = new XmlDocument();
try
{
xDoc.Load(Server.MapPath("~/App_Data/Settings.xml"));
}
catch { xDoc.LoadXml("<Settings></Settings>"); }
int alt = 0;
XmlNodeReader nodeReader = new XmlNodeReader(xDoc);
while (nodeReader.Read())
{
if (nodeReader.NodeType == XmlNodeType.Element && nodeReader.Name != "settings")
{
HtmlTableRow tr = new HtmlTableRow();
HtmlTableCell c1 = new HtmlTableCell();
HtmlTableCell c2 = new HtmlTableCell();
TextBox tb = new TextBox();
c1.InnerHtml = "<strong>" + nodeReader.Name + "</strong>";
if (alt % 2 == 0)
{
c1.Attributes.Add("class", "postDetailsLabel");
c2.Attributes.Add("class", "postDetailsReview");
}
else
{
c1.Attributes.Add("class", "postDetailsLabel");
c2.Attributes.Add("class", "postDetailsReview");
}
c1.Attributes.Add("style", "padding-right:20px;");
tb.ID = nodeReader.Name;
tb.Text = nodeReader.ReadString();
tb.Width = 400;
c2.Controls.Add(tb);
tr.Controls.Add(c1);
tr.Controls.Add(c2);
settingsTable.Rows.Add(tr);
alt++;
}
}
nodeReader.Close();
}
You could use the meat of that function in a number of ways to produce the html markup that fits your style. This function will produce a table with a row for each setting, a column for the setting label, and a column with a text box holding the current setting value (editable).
Step 6: Saving the setting to the XML file
Finally, the edit page should be able to save the settings back to the XML file.
private void UpdateSettings()
{
string filename = "~/App_Data/Settings.xml";
XmlWriterSettings writerSettings = new XmlWriterSettings(); ;
writerSettings.Indent = true;
//------------------------------------------------------------
// Create XML writer against file path
//------------------------------------------------------------
using (XmlWriter writer = XmlWriter.Create(Server.MapPath(filename), writerSettings))
{
writer.WriteStartElement("settings");
foreach (HtmlTableRow row in settingsTable.Rows)
{
foreach (HtmlTableCell cell in row.Cells)
{
foreach (Control ctrl in cell.Controls)
{
if (ctrl is TextBox)
{
TextBox tb = ctrl as TextBox;
writer.WriteElementString(tb.ID, tb.Text.Trim());
}
}
}
}
writer.WriteEndElement();
}
Settings.LoadSettings();
}
This function iterates through each table row which should represent a setting per row and gets the value from the text box control. The value is then written to the XML file as an element value using the text box id as the element name. Finally, the LoadSettings() function is called to reload the app settings with the new values.
Conclusion
You can use this technique to store all kinds of data in XML files. I'm sure many people will disagree with this method of settings management but the concepts presented here can be used for many different types of data. The core functions in this post will probably make it back into my helper class project in some form or another.
