I have a set of XML schema files provided to me. I cannot changed the XML as these will be updated on occasion. I am using xsd.exe to convert the schema files to generated c
Here's the final solution we came up with that leverages when I learned from the original answer. This static class is used to get and set the appropriate properties.
public static class XmlPolymorphicArrayHelper
{
public static TResult GetItem<TIDentifier, TResult>(TResult[] items, TIDentifier[] itemIdentifiers, TIDentifier itemIdentifier)
{
if (itemIdentifiers == null)
{
return default(TResult);
}
var i = Array.IndexOf(itemIdentifiers, itemIdentifier);
return i < 0 ? default(TResult) : items[i];
}
public static void SetItem<TIDentifier, TResult>(ref TResult[] items, ref TIDentifier[] itemIdentifiers, TIDentifier itemIdentifier, TResult value)
{
if (itemIdentifiers == null)
{
itemIdentifiers = new[] { itemIdentifier };
items = new[] { value };
return;
}
var i = Array.IndexOf(itemIdentifiers, itemIdentifier);
if (i < 0)
{
var newItemIdentifiers = itemIdentifiers.ToList();
newItemIdentifiers.Add(itemIdentifier);
itemIdentifiers = newItemIdentifiers.ToArray();
var newItems = items.ToList();
newItems.Add(value);
items = newItems.ToArray();
}
else
{
items[i] = value;
}
}
}
And then call them from the partial class like this:
public partial class LocationType
{
[XmlIgnore]
public string Address
{
get
{
return (string)XmlPolymorphicArrayHelper.GetItem(Items, ItemsElementName, ItemsChoiceType.Address);
}
set
{
XmlPolymorphicArrayHelper.SetItem(ref this.itemsField, ref this.itemsElementNameField, ItemsChoiceType.Address, value);
}
}
}
This sets/creates the appropriate member on the Items array and I can use this for the multiple classes that implement this pattern.
What that schema says is that if some outer type contains an element of type LocationType
, one would expect to find inside either
1) A sub-element <LocNum>
, OR
2) These sub-elements in sequence: <Name>
, <Address>
, <City>
and <State>
.
Thus the data here is polymorphic, even though it isn't being explicitly modeled as such in the c# classes generated by xsd.exe. This sort of makes sense -- a location might be specified explicitly, or indirectly as a look-up in a table.
When deserializing a polymorphic sequence like this, XmlSerializer
puts each element it finds in the array field corresponding to the elements in the sequence, in this case the array Items
. In addition, there should be another corresponding array field identified by the XmlChoiceIdentifierAttribute attribute, in this case ItemsElementName
. The entries in this array must needs be in 1-1 correspondence with the Items
array. It records the name of the element that was deserialized in each index of the Items
array, by way of the ItemsChoiceType
enumeration, whose enum names must match the names in the XmlElementAttribute attributes decorating the Items
array. This allows the specific choice of polymorphic data to be known.
Thus, to round out the implementation of your LocationType
class, you will need to determine whether a given LocationType
is direct or indirect; fetch various properties out; and for each type (direct or indirect), set all required data.
Here is a prototype of that. (You didn't include the definition for LocationTypeState
in your question, so I'm just treating it as a string):
public partial class LocationType
{
public LocationType() { }
public LocationType(string locNum)
{
SetIndirectLocation(locNum);
}
public LocationType(string name, string address, string city, string state)
{
SetDirectLocation(name, address, city, state);
}
public bool IsIndirectLocation
{
get
{
return Array.IndexOf(ItemsElementName, ItemsChoiceType.LocNum) >= 0;
}
}
public string Address { get { return (string)XmlPolymorphicArrayHelper.GetItem(Items, ItemsElementName, ItemsChoiceType.Address); } }
public string LocNum { get { return (string)XmlPolymorphicArrayHelper.GetItem(Items, ItemsElementName, ItemsChoiceType.LocNum); } }
// Other properties as desired.
public void SetIndirectLocation(string locNum)
{
if (string.IsNullOrEmpty(locNum))
throw new ArgumentException();
object[] newItems = new object[] { locNum };
ItemsChoiceType [] newItemsElementName = new ItemsChoiceType [] { ItemsChoiceType.LocNum };
this.Items = newItems;
this.ItemsElementName = newItemsElementName;
}
public void SetDirectLocation(string name, string address, string city, string state)
{
// In the schema, "City" is mandatory, others are optional.
if (string.IsNullOrEmpty(city))
throw new ArgumentException();
List<object> newItems = new List<object>();
List<ItemsChoiceType> newItemsElementName = new List<ItemsChoiceType>();
if (name != null)
{
newItems.Add(name);
newItemsElementName.Add(ItemsChoiceType.Name);
}
if (address != null)
{
newItems.Add(address);
newItemsElementName.Add(ItemsChoiceType.Address);
}
newItems.Add(city);
newItemsElementName.Add(ItemsChoiceType.City);
if (state != null)
{
newItems.Add(state);
newItemsElementName.Add(ItemsChoiceType.State);
}
this.Items = newItems.ToArray();
this.ItemsElementName = newItemsElementName.ToArray();
}
}
public static class XmlPolymorphicArrayHelper
{
public static TResult GetItem<TIDentifier, TResult>(TResult[] items, TIDentifier[] itemIdentifiers, TIDentifier itemIdentifier)
{
if (itemIdentifiers == null)
{
Debug.Assert(items == null);
return default(TResult);
}
Debug.Assert(items.Length == itemIdentifiers.Length);
var i = Array.IndexOf(itemIdentifiers, itemIdentifier);
if (i < 0)
return default(TResult);
return items[i];
}
}