Introduction
How often we have seen settings defined in config file like this:
<xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="mailserver" value="default"/>
<add key="mailPort" value="800"/>
<add key="useSSL" value="true"/>
<add key ="from" value="abc@prowareness.com"/>
<add key ="To1" value="recieverl@prowareness.com"/>
<add key ="TolDescription" value="stakeholder"/>
<add key ="To2" value="reciever2@prowareness.com"/>
<add key ="To2Description" value="security admin"/>
<add key ="To3" value="reciever3@prowareness.com"/>
<add key ="To3Description" value="ops team head"/>
-->
</appSettings>
The problem starts when there are a lot and there is no organized/logical way of putting them in config file and reading in application. The more organized way of implementing custom app setting is via custom configSection
. Let’s first have a glimpse of config file to know what we want to achieve.
<settings>
<mailSetting>
<name>default</name>
<port>800</port>
<usessl>true</usessl>
<from>abc@prowareness.com</from>
<description companyName="Prowareness" />
<to>
<email description="stakeholder" value="reciever1@prowareness.com"/>
<email description="security admin" value="reciever2@prowareness.com"/>
<email description="ops team head" value="reciever3@prowareness.com"/>
</to>
</mailSetting>
</settings>
As it’s apparent now, we have organized our mail server related settings in a more organized way. Now by creating a strongly typed class for these settings, we can read these settings directly by using our custom type. How? Let’s understand this process step by step.
Step 1
First, we need to create a custom SettingsConfiguration
class or any name you like inherited from IConfigurationSectionHandler
interface. This is the class which we will use to register our custom config section in config file. For now, just add a method called Create
as shown below. We will discuss rest of the code in subsequent steps.
using System.Collections.Generic;
using System.Configuration;
using System.Xml;
using System.Xml.Serialization;
namespace CustomAppSettingsEx.Configuration
{
public sealed class SettingsConfiguration : IConfigurationSectionHandler
{
public MailSettingElement Read()
{
XmlSerializer serializer = new XmlSerializer(typeof(MailSettingElement));
var sectionNode = (XmlNode)ConfigurationManager.GetSection("settings");
MailSettingElement mailSetting;
if (sectionNode != null)
{
XmlNode mailSettingNode = sectionNode.SelectSingleNode("mailSetting");
var reader = new XmlNodeReader(mailSettingNode);
mailSetting = (MailSettingElement)serializer.Deserialize(reader);
}
else
{
return new MailSettingElement
{
Name = "default.mail.smtp",
Port = 8080,
UseSsl = true,
From="abc@prowareness.nl",
Description=new Description(){CompanyName="Prowareness"},
To = new To() { Email = new List<Email>()
{
new Email() { Description = "stakeholder",
Value = "reciever1@prowareness.com" },
new Email() { Description = "security admin",
Value = "reciever2@prowareness.com" },
new Email() { Description = "ops team head",
Value = "reciever3@prowareness.com" },
} }
};
}
return mailSetting;
}
public object Create(object parent, object configContext, XmlNode section)
{
return section;
}
}
}
Step 2
Now, we will register this type into our config file. How? Here is the way:
="1.0" ="utf-8"
<configuration>
<configSections>
<section name="settings" type="CustomAppSettingsEx.Configuration.SettingsConfiguration,
CustomAppSettingsEx" />
</configSections>
Here the name "settings
" should be the same as defined in our mailSetting
parent node. In type first argument is our config class name with full namespace and the second parameter is the assembly name in which this config class is defined.
<settings>
<mailSetting>
So, our basic config skeleton is ready. Let’s fill it with mailSetting
that we want to achieve.
Step 3
As seen earlier, we already have our mailSetting
defined in config file and to read it via XmlSerializer
, we need to create mapping classes for each node [if node does contain any other node(s)]. So here are our classes.
using System.Collections.Generic;
using System.Xml.Serialization;
namespace CustomAppSettingsEx.Configuration
{
[XmlRoot("mailSetting")]
public class MailSettingElement
{
[XmlElement("name")]
public string Name { get; set; }
[XmlElement("port")]
public int Port { get; set; }
[XmlElement("usessl")]
public bool UseSsl { get; set; }
[XmlElement("description")]
public Description Description { get; set; }
[XmlElement("from")]
public string From { get; set; }
[XmlElement("to")]
public To To { get; set; }
}
[XmlRoot("description")]
public class Description
{
[XmlAttribute("companyName")]
public string CompanyName { get; set; }
}
[XmlRoot("to")]
public class To
{
[XmlElement("email")]
public List<Email> Email { get; set; }
}
[XmlRoot("email")]
public class Email
{
[XmlAttribute("description")]
public string Description { get; set; }
[XmlAttribute("value")]
public string Value { get; set; }
}
}
Here, we can have a different class name, but name defined in XmlRoot
should match with what is defined in config file. Now, there are different ways of defining classes and their members based on their representation.
<name>default</name>
: In this case, we can have a property defined decorated with attribute XmlElement
where name in attribute should match with name defined in config file.
<description companyName="Prowareness" />
: For mapping this kind of node, we need to have a separate class where CompanyName
will be decorated with attribute XmlAttribute
instead of XmlElement
.
[XmlRoot("description")]
public class Description
{
[XmlAttribute("companyName")]
public string CompanyName { get; set; }
}
- Now, it's a little tricky if we want to define an email collection like this in our config file.
<to>
<email description="stakeholder" value="reciever1@prowareness.com"/>
<email description="security admin" value="reciever2@prowareness.com"/>
</to>
Here is the solution:
[XmlRoot("to")]
public class To
{
[XmlElement("email")]
public List<Email> Email { get; set; }
}
[XmlRoot("email")]
public class Email
{
[XmlAttribute("description")]
public string Description { get; set; }
[XmlAttribute("value")]
public string Value { get; set; }
}
In this case, List<Email>
will be our XmlElement
and the Email
class can be defined as above. So now, all our the mapping classes are ready. Let’s see how to read it in our application.
Step 4
Now, let’s go back to our class SettingsConfiguration
’s Read
method.
public MailSettingElement Read()
{
XmlSerializer serializer = new XmlSerializer(typeof(MailSettingElement));
var sectionNode = (XmlNode)ConfigurationManager.GetSection("settings");
MailSettingElement mailSetting;
if (sectionNode != null)
{
XmlNode mailSettingNode = sectionNode.SelectSingleNode("mailSetting");
var reader = new XmlNodeReader(mailSettingNode);
mailSetting = (MailSettingElement)serializer.Deserialize(reader);
}
else
{
return new MailSettingElement
{
Name = "default.mail.smtp",
Port = 8080,
UseSsl = true,
From="abc@prowareness.nl",
Description=new Description(){CompanyName="Prowareness"},
To = new To() { Email = new List<Email>()
{
new Email() { Description = "stakeholder",
Value = "reciever1@prowareness.com" },
new Email() { Description = "security admin",
Value = "reciever2@prowareness.com" },
new Email() { Description = "ops team head",
Value = "reciever3@prowareness.com" },
} }
};
}
return mailSetting;
}
Here, we are reading our custom config section named "settings
" which we registered in step 2. We get our "mailSetting
" node using SelectSingleNode
method and then using XmlSerializer
we deserialize it to our MailSettingElement
class. And then, now onwards, we can use this class to get the settings from config file and apply wherever needed.
So what advantages am I’m getting out of this?
As we can see, even if sectionNode
is null
in the above code (meaning you missed defining settings in config file due to various reasons, may be you are not sure it’s value in development or test environment), you can still pass some default value in code as shown above.
By using this approach, we can logically organize our settings in different sections which can be reused across applications if this class is developed as an assembly.
And here is the way to read/use these settings:
private static void CustomSettings()
{
SettingsConfiguration setting = new SettingsConfiguration();
MailSettingElement mailSetting = setting.Read();
string mailTo = string.Empty;
for(int i=0; i< mailSetting.To.Email.Count;i++)
{
if (i != 0)
mailTo += ",";
mailTo += mailSetting.To.Email[i].Value;
}
MailMessage mailMessage = new MailMessage(mailSetting.From, mailTo, "custom subject",
"mail body");
SmtpClient mailClient=new SmtpClient(mailSetting.Name);
mailClient.EnableSsl = mailSetting.UseSsl;
mailClient.Port = mailSetting.Port;
try
{
mailClient.Send(mailMessage);
}
catch (Exception)
{
}
#region Read All
Console.WriteLine("<mailSetting>");
Console.WriteLine("\t<name>{0}</name>", mailSetting.Name);
Console.WriteLine("\t<port>{0}</port>", mailSetting.Port);
Console.WriteLine("\t<usessl>{0}</usessl>", mailSetting.UseSsl);
Console.WriteLine("\t<from>{0}</from>", mailSetting.From);
Console.WriteLine("\t<description companyName = \"{0}\"/>",
mailSetting.Description.CompanyName);
Console.WriteLine("\t<to>");
foreach (Email email in mailSetting.To.Email)
{
Console.WriteLine("\t\t<email description = \"{0}\"
value = \"{1}\"/>", email.Description, email.Value);
}
Console.WriteLine("\t</to>");
Console.WriteLine("</mailSetting>");
#endregion
Console.ReadLine();
}
That’s all folks for this tip. Please let me know your views/comments.