Introduction
In my current project, we need to store a lot of constant values to run the system which can be achieved in different ways like using Resource File, Web.config, etc. We want those values to be environment specific (e.g., Development, Stage, QA, Production) and also Intellisense feature to use those values.
Existing approaches have the following limitations:
- In case of config files, Intellisense feature is not available. Hence, there is always a probability of human error which is very difficult to find out.
- In our scenario, there are cases where we want collection object to be populated from the constant values which are cumbersome in case of resource file approach used.
- To enhance maintainability, values used for given context / module should be clubbed together which is difficult to achieve using existing approaches.
While searching for a better way, I found the .NET System.Dynamic.ExpandoObjec
class, it will represent an object whose members can be dynamically added and removed at run time. I also found that an ExpandoObject
was being used to represent an XML document. That is, while parsing, an ExpandoObject
instance is created and each XML element is added as a member of the ExpandoObject
. With a little more research, I found "ImpromptuInterface
" package in NuGet. With the “ImpromptuInterface
", any object can be wrapped with an interface.
So we decided to load the XML data into a dynamic type using the System.Dynamic.ExpandoObjec
class.
Using the Code
For example, this XML: Constants.xml
="1.0"="utf-8"
<Settings>
<Config>
<AppData1>Data1</AppData1>
<AppData2>Data2</AppData2>
<SFTP>
<UserName>TestUser</UserName>
<Password>TestPwd</PassWord>
<Port>22</Port>
</SFTP>
</Config>
</Settings>
The below line of code loads an XML file from disk.
var xDoc = XDocument.Load(new StreamReader(xmlFileName));
...
The below section creates a list of XML node names that are known to contain lists of items. Once the XML is loaded, creation of the dynamic object can begin by calling Parse()
method. Once the parsing is complete, the parsed data may then be used.
Parse(root, xDoc.Elements().First());
...
Below functions are used to load the XML to dynamic object in C#.
public static dynamic GetConstantsData(string xmlFileName)
{
dynamic root = new ExpandoObject();
if (string.IsNullOrEmpty(xmlFileName))
{
return root;
}
var xDoc = XDocument.Load(new StreamReader(xmlFileName));
Parse(root, xDoc.Elements().First());
return root;
}
...
private static void Parse(dynamic parent, XElement node)
{
try
{
if (node.HasElements)
{
if (node.Elements(node.Elements().First().Name.LocalName).Count() > 1)
{
var item = new ExpandoObject();
var list = new List<dynamic>();
foreach (var element in node.Elements())
{
Parse(list, element);
}
AddProperty(item, node.Elements().First().Name.LocalName, list);
AddProperty(parent, node.Name.ToString(), item);
}else{
var item = new ExpandoObject();
foreach (var attribute in node.Attributes())
{
AddProperty(item, attribute.Name.ToString(), attribute.Value.Trim());
}
foreach (var element in node.Elements())
{
Parse(item, element);
}
AddProperty(parent, node.Name.ToString(), item);
}
}else{
AddProperty(parent, node.Name.ToString(), node.Value.Trim());
}
}
catch (Exception Ex)
{
throw new Exception(Ex.Message);
}
}
...
private static void AddProperty(dynamic parent, string name, object value)
{
try
{
if (parent is List<dynamic>)
{
(parent as List<dynamic>).Add(value);
}
else
{
(parent as IDictionary<string, object>)[name] = value;
}
}
catch (Exception Ex)
{
throw new Exception(Ex.Message);
}
}
...
Install ImpromptuInterface
Open Package Manages Console Using Visual Studio Click Tools -> NuGet Package Manages -> Package Manages Console. Run the below command:
PM> Install-Package ImpromptuInterface
...
Defining Interfaces
Prior to adding the above dynamic behaviors to objects, we need to make sure the objects have interface methods. The interfaces for the sample XML are defined as follows:
public interface IXML
{
ISettings Settings { get; }
}
public interface ISettings
{
IConfig Config { get; }
}
public interface IConfig
{
string AppData1 { get; set; }
string AppData2{ get; set; }
ISFTP SFTP { get; }
}
public interface ISFTP
{
string UserName { get; set; }
string PassWord { get; set; }
object Port { get; set; }
}
...
Wrapping Objects and Attaching Behaviors
The extension method ActLike<I>
of object can be used to wrap an object with an interface.
static void Main(string[] args)
{
dynamic constantsData = XmlToDynamic.GetConstantsData("Constants.xml");
IXML xml = ImpromptuInterface.Impromptu.ActLike(constantsData);
Console.WriteLine("AppData1 = " + xml.Settings.Config.AppData1);
Console.WriteLine("AppData2 = " + xml.Settings.Config.AppData2);
Console.WriteLine("UserName = " + xml.Settings.Config.SFTP.UserName);
Console.WriteLine("PassWord = " + xml.Settings.Config.SFTP.PassWord);
Console.WriteLine("Port = " + xml.Settings.Config.SFTP.Port);
}
...