Can someone point me to a C# example where they populate a request and receive the response for a web service where the schema is mostly implemented as ANY elements?
XmlNode is a type in the old XML Document Object Model that dates from c# 1.1. It has been superseded by the much easier to use LINQ to XML object model. What is not well known is that [XmlAnyElementAttribute] actually supports this API. Thus in your GenericRequestData_Type
type you could manually change the Any
property to be of type System.Xml.Linq.XElement[]
:
public class GenericRequestData_Type
{
private System.Xml.Linq.XElement[] anyField;
[System.Xml.Serialization.XmlAnyElementAttribute()]
public System.Xml.Linq.XElement[] Any
{
get
{
return this.anyField;
}
set
{
this.anyField = value;
}
}
}
Then you can initialize the array easily as follows, following the instructions in Creating XML Trees in C# (LINQ to XML):
// Taken from
// https://msdn.microsoft.com/en-us/library/mt693096.aspx
var contacts =
new XElement("Contacts",
new XElement("Contact",
new XElement("Name", "Patrick Hines"),
new XElement("Phone", "206-555-0144"),
new XElement("Address",
new XElement("Street1", "123 Main St"),
new XElement("City", "Mercer Island"),
new XElement("State", "WA"),
new XElement("Postal", "68042")
)
)
);
var genericRequest = new GenericRequestData_Type();
genericRequest.Any = new[] { contacts };
Sample fiddle.
XmlNode is an abstract base type. You can create concrete XmlElement objects and add then to your Any
array following the instructions in Create New Nodes in the DOM:
XmlElement [] Any
array, it is needed to subsequently create elements.Thus the following will work:
var dom = new XmlDocument();
var contacts = dom.CreateElement("Contacts");
dom.AppendChild(contacts);
var contact = dom.CreateElement("Contact");
contacts.AppendChild(contact);
var name = dom.CreateElement("Name");
contact.AppendChild(name);
name.InnerText = "Patrick Hines";
var phone = dom.CreateElement("Phone");
contact.AppendChild(phone);
phone.InnerText = "206-555-0144";
var address = dom.CreateElement("Address");
contact.AppendChild(address);
var street1 = dom.CreateElement("Street1");
address.AppendChild(street1);
street1.InnerText = "123 Main St";
var city = dom.CreateElement("City");
address.AppendChild(city);
city.InnerText = "Mercer Island";
var state = dom.CreateElement("State");
address.AppendChild(state);
state.InnerText = "WA";
var postal = dom.CreateElement("Postal");
address.AppendChild(postal);
postal.InnerText = "68042";
var genericRequest = new GenericRequestData_Type();
genericRequest.Any = new[] { dom.DocumentElement };
Sample fiddle.
Your question is fairly general so I'm going to narrow it down a bit to the following situation: you have a type with an [XmlAnyElement] member like so:
public class GenericRequestData_Type
{
private System.Xml.XmlElement[] anyField;
[System.Xml.Serialization.XmlAnyElementAttribute()]
public System.Xml.XmlElement[] Any
{
get
{
return this.anyField;
}
set
{
this.anyField = value;
}
}
}
And you would like to initialize the Any
field to the following XML:
<Contacts>
<Contact>
<Name>Patrick Hines</Name>
<Phone>206-555-0144</Phone>
<Address>
<Street1>123 Main St</Street1>
<City>Mercer Island</City>
<State>WA</State>
<Postal>68042</Postal>
</Address>
</Contact>
</Contacts>
How can this be done?
There are several options, so I'll break it down into a few different answers.
Firstly, you could design c# classes corresponding to your desired XML using a code-generation tool such as http://xmltocsharp.azurewebsites.net/ or Visual Studio's Paste XML as Classes. Then you can serialize those classes directly to an XmlNode
hierarchy using XmlSerializer combined with XmlDocument.CreateNavigator().AppendChild().
First, introduce the following extension methods:
public static class XmlNodeExtensions
{
public static XmlDocument AsXmlDocument<T>(this T o, XmlSerializerNamespaces ns = null, XmlSerializer serializer = null)
{
XmlDocument doc = new XmlDocument();
using (XmlWriter writer = doc.CreateNavigator().AppendChild())
new XmlSerializer(o.GetType()).Serialize(writer, o, ns ?? NoStandardXmlNamespaces());
return doc;
}
public static XmlElement AsXmlElement<T>(this T o, XmlSerializerNamespaces ns = null, XmlSerializer serializer = null)
{
return o.AsXmlDocument(ns, serializer).DocumentElement;
}
public static T Deserialize<T>(this XmlElement element, XmlSerializer serializer = null)
{
using (var reader = new ProperXmlNodeReader(element))
return (T)(serializer ?? new XmlSerializer(typeof(T))).Deserialize(reader);
}
/// <summary>
/// Return an XmlSerializerNamespaces that disables the default xmlns:xsi and xmlns:xsd lines.
/// </summary>
/// <returns></returns>
public static XmlSerializerNamespaces NoStandardXmlNamespaces()
{
var ns = new XmlSerializerNamespaces();
ns.Add("", ""); // Disable the xmlns:xsi and xmlns:xsd lines.
return ns;
}
}
public class ProperXmlNodeReader : XmlNodeReader
{
// Bug fix from this answer https://stackoverflow.com/a/30115691/3744182
// To http://stackoverflow.com/questions/30102275/deserialize-object-property-with-stringreader-vs-xmlnodereader
// By https://stackoverflow.com/users/8799/nathan-baulch
public ProperXmlNodeReader(XmlNode node) : base(node) { }
public override string LookupNamespace(string prefix)
{
return NameTable.Add(base.LookupNamespace(prefix));
}
}
Next, define types corresponding to your <Contacts>
hierarchy:
[XmlRoot(ElementName = "Address")]
public class Address
{
[XmlElement(ElementName = "Street1")]
public string Street1 { get; set; }
[XmlElement(ElementName = "City")]
public string City { get; set; }
[XmlElement(ElementName = "State")]
public string State { get; set; }
[XmlElement(ElementName = "Postal")]
public string Postal { get; set; }
}
[XmlRoot(ElementName = "Contact")]
public class Contact
{
[XmlElement(ElementName = "Name")]
public string Name { get; set; }
[XmlElement(ElementName = "Phone")]
public string Phone { get; set; }
[XmlElement(ElementName = "Address")]
public Address Address { get; set; }
}
[XmlRoot(ElementName = "Contacts")]
public class Contacts
{
[XmlElement(ElementName = "Contact")]
public Contact Contact { get; set; }
}
[XmlRoot("GenericRequestData_Type")]
public class Person
{
public string Name { get; set; }
public DateTime BirthDate { get; set; }
[XmlArray("Emails")]
[XmlArrayItem("Email")]
public List<string> Emails { get; set; }
[XmlArray("Issues")]
[XmlArrayItem("Id")]
public List<long> IssueIds { get; set; }
}
Finally, initialize your GenericRequestData_Type
as follows:
var genericRequest = new GenericRequestData_Type();
var contacts = new Contacts
{
Contact = new Contact
{
Name = "Patrick Hines",
Phone = "206-555-0144",
Address = new Address
{
Street1 = "123 Main St",
City = "Mercer Island",
State = "WA",
Postal = "68042",
},
}
};
genericRequest.Any = new[] { contacts.AsXmlElement() };
Sample fiddle.