A C# helper to read and write XML from and to objects
I really like jQuery’s pattern of attribute getters and setters. They are fluent and work really well with HTML and XML DOMs. If you specify a value in addition to the name, it’s setting, otherwise it’s getting. In C#, we have an OK API for XML, XElement, but it’s not as easy to use as jQuery’s attr methods. It is also missing the flexibility of Javascript with regards to parameter types. To recreate the simplicity of attr in C#, I built a set of extension methods for the most common simple types:
var el = new XElement("node"); el.Attr("foo", "bar") .Attr("baz", 42) .Attr("really", true); var answer = el.Attr("baz");
The element built by this code looks like this:
<node foo="bar" baz="42" really="true"/>
And the answer variable will contain “42”.
Even with this API, there is still a fair amount of repetition in code that reads and writes XML from and to objects. You could rely on serialization in those cases, of course, but when you need a little more control, and the types are not necessarily serializable, or if you just want to do it manually, you need something more. This is why I also built ToAttr and FromAttr. Both are extension methods that take an object and an expression for what property of the object to get or set from XML attributes. The methods will infer the type and name from the property.
This is especially useful when writing the import and export methods in an Orchard part driver:
protected override void Importing(
UspsShippingMethodPart part,
ImportContentContext context) {
var el = context.Data.Element(typeof(UspsShippingMethodPart).Name); if (el == null) return; el.FromAttr(part, p => p.Name) .FromAttr(part, p => p.Size) .FromAttr(part, p => p.WidthInInches) .FromAttr(part, p => p.LengthInInches) .FromAttr(part, p => p.HeightInInches) .FromAttr(part, p => p.MaximumWeightInOunces) .FromAttr(part, p => p.Priority) .FromAttr(part, p => p.International) .FromAttr(part, p => p.RegisteredMail) .FromAttr(part, p => p.Insurance) .FromAttr(part, p => p.ReturnReceipt) .FromAttr(part, p => p.CertificateOfMailing) .FromAttr(part, p => p.ElectronicConfirmation); } protected override void Exporting(
UspsShippingMethodPart part, ExportContentContext context) {
context.Element(typeof (UspsShippingMethodPart).Name) .ToAttr(part, p => p.Name) .ToAttr(part, p => p.Size) .ToAttr(part, p => p.WidthInInches) .ToAttr(part, p => p.LengthInInches) .ToAttr(part, p => p.HeightInInches) .ToAttr(part, p => p.MaximumWeightInOunces) .ToAttr(part, p => p.Priority) .ToAttr(part, p => p.International) .ToAttr(part, p => p.RegisteredMail) .ToAttr(part, p => p.Insurance) .ToAttr(part, p => p.ReturnReceipt) .ToAttr(part, p => p.CertificateOfMailing) .ToAttr(part, p => p.ElectronicConfirmation); }
There is no need to specify attribute names or types here, everything is inferred from the expression. Both methods manipulate XML looking like this:
<UspsShippingMethodPart Name="Foo" Size="L" WidthInInches="10" LengthInInches="11" HeightInInches="12" MaximumWeightInOunces="1.3" Priority="14" International="true" RegisteredMail="true" Insurance="false" ReturnReceipt="true" CertificateOfMailing="true" ElectronicConfirmation="true"/>
UPDATE: You may notice that there is still quite some repetition of the part parameter in the import/export code above. In order to remove this repetition, I’ve added a small class that aggregates the XML element with a context and has simpler ToAttr and From Attr methods. With this new helper class, we can rewrite the driver’s import/export code to be even more concise:
protected override void Importing(
UspsShippingMethodPart part, ImportContentContext context) {
var el = context.Data.Element(typeof (UspsShippingMethodPart).Name); if (el == null) return; el.With(part) .FromAttr(p => p.Name) .FromAttr(p => p.Size) .FromAttr(p => p.WidthInInches) .FromAttr(p => p.LengthInInches) .FromAttr(p => p.HeightInInches) .FromAttr(p => p.MaximumWeightInOunces) .FromAttr(p => p.Priority) .FromAttr(p => p.International) .FromAttr(p => p.RegisteredMail) .FromAttr(p => p.Insurance) .FromAttr(p => p.ReturnReceipt) .FromAttr(p => p.CertificateOfMailing) .FromAttr(p => p.ElectronicConfirmation); } protected override void Exporting(
UspsShippingMethodPart part, ExportContentContext context) {
context.Element(typeof (UspsShippingMethodPart).Name) .With(part) .ToAttr(p => p.Name) .ToAttr(p => p.Size) .ToAttr(p => p.WidthInInches) .ToAttr(p => p.LengthInInches) .ToAttr(p => p.HeightInInches) .ToAttr(p => p.MaximumWeightInOunces) .ToAttr(p => p.Priority) .ToAttr(p => p.International) .ToAttr(p => p.RegisteredMail) .ToAttr(p => p.Insurance) .ToAttr(p => p.ReturnReceipt) .ToAttr(p => p.CertificateOfMailing) .ToAttr(p => p.ElectronicConfirmation); }
You can find the code for this helper class here:
https://gist.github.com/bleroy/5384405
And I have a small test suite for the whole thing here:
https://gist.github.com/bleroy/5385284