I have to work with a 3rd-party API that allows me to define and execute "commands", using XML. Since I don't like seeing mixed abstraction levels, I managed to remove all the inline XML / string concatenations by creating a simple XmlCmdBuilder
object - here is the C# implementation (I have one in VB6 as well, the C# one will eventually replace the VB6 equivalent code):
public class XmlCmdBuilder
{
private readonly IList<XmlCmd> _xmlCommands;
public XmlCmdBuilder()
{
_xmlCommands = new List<XmlCmd>();
}
public void AddCommand(XmlCmd cmd)
{
_xmlCommands.Add(cmd);
}
public void Clear()
{
_xmlCommands.Clear();
}
public override string ToString()
{
var builder = new StringBuilder();
builder.Append("<cmd:Commands xmlns:cmd=\"http://www.contoso.com/XmlCommand\">");
foreach (var xmlCommand in _xmlCommands)
{
builder.Append(xmlCommand);
}
builder.Append("</cmd:Commands>");
return builder.ToString();
}
}
The "builder" simply exposes a AddCommand
method that takes in a XmlCmd
object:
public class XmlCmd
{
private readonly IList<XmlCmdParameter> _parameters;
public XmlCmd(string name)
: this(name, new List<XmlCmdParameter>())
{
}
public XmlCmd(string name, params XmlCmdParameter[] parameters)
: this(name, parameters.ToList())
{
}
public XmlCmd(string name, IList<XmlCmdParameter> parameters)
{
_parameters = parameters;
Name = name;
}
public string Name { get; private set; }
public void AddParameter(XmlCmdParameter parameter)
{
_parameters.Add(parameter);
}
public override string ToString()
{
var builder = new StringBuilder();
builder.Append(string.Format("<cmd:Command name=\"{0}\">", Name));
foreach (var xmlCommandParameter in _parameters)
{
builder.Append(xmlCommandParameter);
}
builder.Append("</cmd:Command>");
return builder.ToString();
}
}
Similarly, the XmlCmd
exposes a method to add XmlCmdParameter
objects, which in turn expose methods to add XmlCmdParameterListItem
objects.
This all works very well, but since most commands are predefined (there can be "custom" commands too), I created a factory class that creates XmlCmd
objects off parameter values, like this:
public XmlCmd SetOptionValue(string optionName, string optionValue)
{
var parameters = new[]
{
_parameterFactory.Create("name", optionName, XmlCmdParameterType.StringParam),
_parameterFactory.Create("value", optionValue, XmlCmdParameterType.StringParam)
};
return new XmlCmd("SetOptionValue", parameters);
}
Then in the calling code, I can eliminate all the inline XML and string concatenation and replace it all with a single method call:
var builder = new XmlCmdBuilder();
builder.AddCommand(_cmdFactory.SetOptionValue("OptionName", "OptionValue"));
Then I can continue "building" the xml by adding subsequent AddCommand
calls, and when I need to, I just call ToString()
and pass the generated XML string to the 3rd-party API that knows what to do with it.
I have put these objects in their own class library, which needs a strong name key and ends up deployed in the GAC / Global Assembly Cache, so I want this library to be as "immune to change" as possible; one potential issue is with the XmlCmdFactory
class: the commands are predefined, but I haven't implemented all of them and I might need to change it (add more methods) in the future.
I'd like some feedback on the XmlCmdBuilder
and XmlCmd
classes (XmlCmdParameter
is built the same way, listing it here, along with the rest of the classes, would be almost redundant), and also some pointers in terms of extensibility, not so much about the code itself, but about adding more factory methods: something doesn't feel right about deploying a new version of this library to the GAC whenever a new XML command is needed.