I’m working on a C# class XmlNavigator that simplifies XML navigation. Here’s the code:
using System;
using System.Dynamic;
using System.Xml;
public class XmlNavigator : DynamicObject
{
private XmlNode xml;
public XmlNavigator(string doc)
{
var document = new XmlDocument();
document.LoadXml(doc);
this.xml = document;
}
private XmlNavigator(XmlNode xml)
{
this.xml = xml;
}
public override bool TrySetMember(SetMemberBinder binder, object value)
{
return false;
}
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
result = null;
if(binder.Name.Equals("State"))
result = new XmlNavigator(xml.SelectSingleNode("State"));
else if(binder.Name.Equals("Text"))
result = (xml as XmlDocument)?.DocumentElement.OuterXml;
return true;
}
static void Main(string[] args)
{
string xml =
"<?xml version="1.0" encoding="UTF-8"?>" +
"<State>" +
"<City>New York</City>" +
"</State>";
dynamic xmlObj = new XmlNavigator(xml);
Console.WriteLine(xmlObj.Text ?? string.Empty);
Console.WriteLine(xmlObj.State?.Text ?? string.Empty);
Console.WriteLine(xmlObj.State?.City?.Text ?? string.Empty);
}
}
Current behavior:
The current code only allows accessing the Text property of the root element and navigating to the State element.
Question:
How can I modify the TryGetMember method to achieve dynamic access to nested elements like State.City.Text?
Additional notes:
Feel free to remove any unnecessary parts of the code snippet.
You can mention that you’ve already tried implementing logic for State but want to generalize for nested elements (recursive function).
Goal:
I want to be able to access nested elements using dynamic properties. For instance, if the XML has a structure like:
<State>
<City>New York</City>
</State>
I would like to access the city name using xmlObj.State.City.Text.
1
Here is one possible method. (Warning: this is a “rough-and-ready” solution. For a more robust approach, use an alternative method such as deserialising to a class.)
Add the Newtonsoft.Json
NuGet package and the following using
declaration:
using Newtonsoft.Json;
Then you can do this:
// Convert to JSON:
var doc = XDocument.Parse(xml);
var jsonAsText = JsonConvert.SerializeXNode(doc);
// Parse dynamically:
dynamic xmlObj = JsonConvert.DeserializeObject<ExpandoObject>(jsonAsText);
var state = xmlObj.State;
var city = state.City;
// Access city name directly from deserialised object:
var cityName = (string)xmlObj.State.City;
Again, this is not necessarily recommended for serious software, but for quick tools it can be a useful quick option.
(If you don’t want to “cheat” by converting to JSON, you’ll need a different approach.)
Should work:
public override bool TryGetMember(GetMemberBinder binder, out object? result)
{
if (binder.Name == "Text")
result = xml.InnerText;
else
result = new XmlNavigator(xml.SelectSingleNode(binder.Name));
return true;
}