Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Creating Dynamic Object from XML using ExpandoObject and Put Intellisense using C#

0.00/5 (No votes)
14 Feb 2018 1  
Creating dynamic object from XML using ExpandoObject and put Intellisense using C#

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

<?xml version="1.0" encoding="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.

//
// Load an XML document
//
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.

//
// Convert the XML document in to a dynamic C# object.
// 
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;
     }
     // Load an XML document.
     var xDoc = XDocument.Load(new StreamReader(xmlFileName));

     // Convert the XML document in to a dynamic C# object.
     Parse(root, xDoc.Elements().First());
     return root;
}
...
//
// Parse the XML document
//
private static void Parse(dynamic parent, XElement node)
{
   try
   {
      if (node.HasElements)
      {
         if (node.Elements(node.Elements().First().Name.LocalName).Count() > 1)
         {
            //list
            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());
            }
            //element
            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.

//
// To view the output run the below code
//
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);
}

//
// Output
//

...

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here