Control XML element name dynamically during serialization - c#

here's my problem to be solved: I've a plugin-structure for multimedia shows that allows to implement media types in external assemblies by subclassing from a base class in my framework. A show holds a list of media types. Shows are loaded and saved in XML using the XmlSerializer. This all works, even with programatic type mapping for plugin MediaTypes.
However, I want to be able to load XML files that contain MediaTypes that are not known, because the plugin isn't available.
For illustration, here is such an XML file:
<MultiMediaShow>
<MediaTypes>
<SomeType />
<SomeType />
<AnotherType />
<UnknownTypeFromPluginNotLoaded />
</MediaTypes>
</MultiMediaShow>
In the above example, I assume 2 "known" MediaTypes SomeType and AnotherType, comming from 2 plugin assemblies. The third type (UnknownTypeFromPluginNotLoaded) is unknown.
I'm already able to deserialize such unknown objects, but struggle with the serialization. In my aplication, I've the following code so far:
// To be implemented / subclassed by plugin assembly types
public abstract class MediaType
{
}
public class UnknownMediaType : MediaType
{
[XmlAnyElement]
public XmlElement[] UnknownChildElements;
[XmlAnyAttribute]
public XmlAttibute[] UnknownAttributes;
[XmlIgnore]
public string XmlTagName; // stores the xml element tag name in the original document
}
[XmlRoot("MultimediaShow")]
public class MultimediaShow
{
public List<MediaType> MediaTypes = new List<MediaType>();
}
When deserializing this with XmlSerializer, I use the event UnknownElement and manually insert an UnknownMediaType element into show.MediaTypes:
void HandleUnknownElements(Show show, List<XmlElementEventArgs> unknownElementEvents, XmlAttributeOverrides overrides)
{
// add a root attribute to UnknownMediaType
XmlAttributes attrs = new XmlAttributes();
XmlRootAttribute xmlRoot = new XmlRootAttribute(e.Element.Name);
attrs.XmlRoot = xmlRoot;
XmlAttributeOverrides o = new XmlAttributeOverrides();
o.Add(typeof(UnknownMediaObject), attrs);
// use a new XmlSerializer and a memory stream for deserializting the object as UnknownMediaType.
XmlSerializer xmlSerializer = new XmlSerializer(typeof(UnknownMediaType), o);
using (MemoryStream memoryStream = new MemoryStream())
{
XmlDocument doc = new XmlDocument();
doc.AppendChild(doc.ImportNode(e.Element, true));
doc.Save(memoryStream);
memoryStream.Position = 0;
try
{
// deserialize the object, store the XML element name and insert it into the chapter
UnknownMediaType t = xmlSerializer.Deserialize(memoryStream) as UnknownMediaObject;
t.XmlTagName = e.Element.Name;
show.MediaTypes.Add(t);
}
catch (Exception exc)
{
System.Diagnostics.Debug.WriteLine(exc.ToString());
//return objectType.IsByRef ? null : Activator.CreateInstance(objectType);
}
}
}
The BIG BIG problem is that such an event doesn't seem to be available when serializing an object. What I get as output (not very surpising) is:
<MultiMediaShow>
<MediaTypes>
<SomeType />
<SomeType />
<AnotherType />
<UnknownMediaType /> // !!!! was 'UnknownTypeFromPluginNotLoaded' !!!!
</MediaTypes>
</MultiMediaShow>
However, this is obviously not the same as what I've deserialized. So the question is, how would I best solve this problem?!?!
All help highly appreciated!!
Cheers,
Felix
UPDATE
I was wondering if it is possible to generate classes programmatically that derive from UnknownMediaType and have the class name taken from the UnknownMediaType.XmlTagName. Or, alternativly, to have an attribute for specifying the XML tag name of a class??
Cheers,
Felix

In fact, I implemented some working solution based on building types dynamically. So far, it's doing what I want. The only downside I see at the moment is that I create them into the current app domain, so I can't unload them (e.g. if a new show is loaded or if the plugins would be made available at runtime).
Here's my code:
void HandleUnknownElements(Show show, List<XmlElementEventArgs> unknownElementEvents, XmlAttributeOverrides overrides)
{
foreach (XmlElementEventArgs e in unknownElementEvents)
{
// (1) Dynamically create a type that simply inherits from UnknownMediaType
AssemblyName assName = new AssemblyName("Show Dynamic Type " + e.Element.Name + " Assembly");
AssemblyBuilder assBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(assName, AssemblyBuilderAccess.Run);
ModuleBuilder modBuilder = assBuilder.DefineDynamicModule(assBuilder.GetName().Name);
TypeBuilder typeBuilder = modBuilder.DefineType(e.Element.Name, TypeAttributes.Class | TypeAttributes.Public, typeof(UnknownMediaType));
Type objectType = typeBuilder.CreateType();
// (2) Add a root attribute to the type as override
XmlAttributes attrs = new XmlAttributes();
XmlRootAttribute xmlRoot = new XmlRootAttribute(e.Element.Name);
attrs.XmlRoot = xmlRoot;
XmlAttributeOverrides o = new XmlAttributeOverrides();
o.Add(objectType, attrs);
// (3) Use a memory stream for creating a temporary XML document that will be deserialized
using (MemoryStream memoryStream = new MemoryStream())
{
XmlDocument doc = new XmlDocument();
doc.AppendChild(doc.ImportNode(e.Element, true));
doc.Save(memoryStream);
memoryStream.Position = 0;
// (4) Deserialize the object using an XmlSerializer and add it
try
{
XmlSerializer xmlSerializer = new XmlSerializer(objectType, o);
UnknownMediaType t = xmlSerializer.Deserialize(memoryStream) as UnknownMediaType;
show.MediaTypes.Add(t);
}
catch (Exception exc)
{
System.Diagnostics.Debug.WriteLine(exc.ToString());
}
}
}
}
Would be glad if you'd post any issues with this and your concerns...
Cheers,
Felix

See whether you can implement the IXmlSerializable interface for your root class.
From MSDN:
There are two reasons to implement
this interface. The first is to
control how your object is serialized
or deserialized by the XmlSerializer.
For example, you can chunk data into
bytes instead of buffering large data
sets, and also avoid the inflation
that occurs when the data is encoded
using Base64 encoding. To control the
serialization, implement the ReadXml
and WriteXml methods to control the
XmlReader and XmlWriter classes used
to read and write the XML. For an
example of this, see How To: Chunk
Serialized Data.
The second reason is to be able to
control the schema. To enable this,
you must apply the
XmlSchemaProviderAttribute to the
serializable type, and specify the
name of the static member that returns
the schema. See the
XmlSchemaProviderAttribute for an
example.
A class that implements the interface
must have a parameterless constructor.
This is a requirement of the
XmlSerializer class

You can implement a customer serializer. It is more work than using the xmlserializer, but gives you full control. See: http://msdn.microsoft.com/en-us/library/ty01x675(v=vs.80).aspx

Related

How to serialize Dynamics CRM metadata

I am trying to serialize a complete set of Dynamics CRM metdata, using roughly the following code:
RetrieveAllEntitiesRequest metaDataRequest = new RetrieveAllEntitiesRequest()
{
EntityFilters = EntityFilters.All
};
// Execute the request
RetrieveAllEntitiesResponse metaDataResponse = (RetrieveAllEntitiesResponse)service.Execute(metaDataRequest);
entitiesMetadata = metaDataResponse.EntityMetadata;
// Store Metadata for faster usage in the future
Serialize<EntityMetadata[]>(entitiesMetadata, "foobar_metadata.xml");
public static void Serialize<T>(T value, string filePath)
{
if (value == null)
{
return;
}
try
{
var xmlserializer = new XmlSerializer(typeof(T));
var stringWriter = new StringWriter();
using (var writer = new XmlTextWriter(stringWriter) { Formatting = System.Xml.Formatting.Indented })
{
xmlserializer.Serialize(writer, value);
File.WriteAllText(filePath, stringWriter.ToString());
}
}
catch (Exception ex)
{
throw new Exception("An error occurred", ex);
}
}
Yet, some of the metadata classes do not have parameterless constructors. So I get the following exception:
[...] cannot be serialized because it does not have a parameterless constructor
I know that the common advice is to extend the affected classes and give them a parameterless constructor. Yet, this could mean many iterations of trial and error as many classes are involved in the Dynamics CRM metadata.
Is there any other way of serializing/deserializing, which doesn't need a parameterless constructor for each class?
Did somebody go through the effort of extending classes for Dynamics CRM metadata and can share the code?
Is there a possibility to automatically omit any non-serializable classes and only serialize the rest?
I have never manually serialized an entity's metadata. Fortunately, exporting a solution that contains an entity serializes the metadata.
While adding the constructors might allow you to get over the hump with "manual" serializing, you could programmatically create a solution, add all the entities to it, and export it as an unmanaged solution.
The exported solution will be a zip file. All of the entities' metadata will be the cutomizations.xml file. You could then parse that XML and split each entity out to its own file.
Out of curiosity, what is driving the requirement to serialize the metadata? Is it for documentation purposes?

How to deserialize JSON to objects of the correct type, without having to define the type before hand?

I searched through similar questions and couldn't find anything that quite matched what i was looking for.
New to C# so bear with me please.
I have some json files that i am deserializing. I want the files to deserialize to objects of the correct type, without having to define the type before hand. Here's my code:
public class loadJson
{
//path of the file location
public void readJson(string path)
{
//array of files at the path location. right now just reading one file
FileInfo[] files = new DirectoryInfo(path).GetFiles("seleniumExample.json").ToArray();
foreach (FileInfo fi in files)
{
dynamic b1 = null;
using (StreamReader file = new StreamReader(fi.FullName))
{
string fileText = file.ReadToEnd();
//Console.WriteLine(fileText);
try
{
b1 = Newtonsoft.Json.JsonConvert.DeserializeObject(fileText);
}
catch(Exception e)
{
Console.WriteLine("ERROR!!!! " + e.ToString());
}
file.Close();
}
}
}
}
I have a bunch of object types that I will be feeding into my program through json files.
I don't want to have to explicitly call b1 a Bid, or a Client, or any other specific predefined class. If I do explicitly call b1 a Bid, it loads all the info just fine and fills out the correct instance variables.
But when I use "dynamic", or general "object", it can't figure it out and just initializes to an "object".
Is there a way to perform generic deserialization and have it create an object of the correct class based on the fields defined in the json file?
Thanks in advance for the help, and i apologize if my question is incredibly unclear. If so, please just let me know how I can help clear up any ambiguity. Thanks again.
Json.NET has the ability to record the .Net object type of polymorphic types during serialization, by using the setting TypeNameHandling = TypeNameHandling.Auto. When the setting is enabled the .Net type of polymorphic objects will appear as a synthetic property called "$type", for instance:
"$type": "Newtonsoft.Json.Samples.Stockholder, Newtonsoft.Json.Tests"
However, the "$type" property is never emitted for the root object if you call the conventional methods JsonConvert.SerializeObject(Object) or JsonSerializer.Serialize(TextWriter, Object). Instead, you must use one of the serialization methods that accepts an "expected" root type, for instance SerializeObject(Object, Type, JsonSerializerSettings) or JsonSerializer.Serialize(TextWriter, Object, Type). Passing typeof(object) as the expected type guarantees the type property will appear:
var settings = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.Auto };
var json = JsonConvert.SerializeObject(rootObject, typeof(object), settings);
If you create your JSON files using this setting, the JSON itself will remember the type of object serialized. This type will be used by Json.NET during deserialization, as long as you use set TypeNameHandling to something other than TypeNameHandling.None. e.g.:
var settings = new Newtonsoft.Json.JsonSerializerSettings { TypeNameHandling = Newtonsoft.Json.TypeNameHandling.Auto };
b1 = Newtonsoft.Json.JsonConvert.DeserializeObject(fileText, settings);
Working sample .Net fiddle here.
Caveats: this way of storing .Net types in JSON is nonstandard. Other serializers such as DataContractJsonSerializer do not process type information in this format.
Note also this caution from the Newtonsoft docs:
TypeNameHandling should be used with caution when your application deserializes JSON from an external source. Incoming types should be validated with a custom SerializationBinder when deserializing with a value other than None.
For a discussion of why this may be necessary, see TypeNameHandling caution in Newtonsoft Json, How to configure Json.NET to create a vulnerable web API, and Alvaro Muñoz & Oleksandr Mirosh's blackhat paper https://www.blackhat.com/docs/us-17/thursday/us-17-Munoz-Friday-The-13th-JSON-Attacks-wp.pdf.
Deserialize your JSON into the most basic form:
Dictionary<string, object> theData= new JavaScriptSerializer().Deserialize<Dictionary<string, object>>(jsonString);
string baseItemName = (string)theData["baseItem"];
Dictionary<string, object> someNode= (Dictionary<string, object>)theData["something"];
string anything = (string)someNode["anything"];
string nothing = (string)someNode["nothing"];
The call to Deserialize() creates a tree of Dictionary<string, object> that you can traverse at will.

Serialize an Object to XML: IList<CustomObject> Property Causes Exception

I am using the following function to attempt to serialize an object to XML..
public static string SerializeObject<T>(T obj)
{
try
{
string xmlString = null;
MemoryStream memoryStream = new MemoryStream();
XmlSerializer xs = new XmlSerializer(typeof(T));
XmlTextWriter xmlTextWriter = new XmlTextWriter(memoryStream, Encoding.UTF8);
xs.Serialize(xmlTextWriter, obj);
memoryStream = (MemoryStream)xmlTextWriter.BaseStream;
xmlString = UTF8ByteArrayToString(memoryStream.ToArray()); return xmlString;
}
catch (Exception ex)
{
return string.Empty;
}
}
When attempting to serialize an object that has an IList property in it, I get the following exception..
Cannot serialize member 'ObjectModel.Order.LineItems' of type 'System.Collections.Generic.IList
Can someone help me change my function to accommodate for this scenario?
Is there anything I can do this existing code to look into the input object. If its of type Ilist change it to a List? Can somoeone help me with code for that if its at all possible??
There's no great solution for this, only the workaround of using a concrete type like List<T> in this case - you could either change the existing property to be List<T> or add an additional property used just for serialization of type List<T> (and XML-ignore your existing property).
XmlSerializer does not handle properties of type IList<T>. There are some workarounds, the most straightforward of which is to change the type of the property:
https://www.google.com/search?q=xmlserializer+ilist
How important is Xml output? binary format is more accomodating. you could convert the output to base64 string if needed.
http://msdn.microsoft.com/en-us/library/system.runtime.serialization.formatters.binary.binaryformatter(v=vs.71).aspx

C# de-serialise Xml to object and serialise back to Xml again

I would like to use JsonFx to convert XML to/from custom types and LINQ queries. Can anyone please provide an example to de-serialisation and serialisation back again?
Here's an example of the XML I'm working with.
XML pasted here: http://pastebin.com/wURiaJM2
JsonFx Supports several strategies of binding json to .net objects including dynamic objects. https://github.com/jsonfx/jsonfx
Kind regards
Si
PS I did try pasting the xml document into StackOverflow but it removed a lot of the documents quotes and XML declaration.
Here's a method that I have used. It may require some tweaking:
public static string SerializeObject<T>(T item, string rootName, Encoding encoding)
{
XmlWriterSettings writerSettings = new XmlWriterSettings();
writerSettings.OmitXmlDeclaration = true;
writerSettings.Indent = true;
writerSettings.NewLineHandling = NewLineHandling.Entitize;
writerSettings.IndentChars = " ";
writerSettings.Encoding = encoding;
StringWriter stringWriter = new StringWriter();
using (XmlWriter xml = XmlWriter.Create(stringWriter, writerSettings))
{
XmlAttributeOverrides aor = null;
if (rootName != null)
{
XmlAttributes att = new XmlAttributes();
att.XmlRoot = new XmlRootAttribute(rootName);
aor = new XmlAttributeOverrides();
aor.Add(typeof(T), att);
}
XmlSerializer xs = new XmlSerializer(typeof(T), aor);
XmlSerializerNamespaces xNs = new XmlSerializerNamespaces();
xNs.Add("", "");
xs.Serialize(xml, item, xNs);
}
return stringWriter.ToString();
}
And for Deserialization:
public static T DeserializeObject<T>(string xml)
{
using (StringReader rdr = new StringReader(xml))
{
return (T)new XmlSerializer(typeof(T)).Deserialize(rdr);
}
}
And call it like this:
string xmlString = Serialization.SerializeObject(instance, "Root", Encoding.UTF8);
ObjectType obj = Serialization.DeserializeObject<ObjectType>(xmlString);
Hope this helps. The rootName parameter in the Serialize method lets you customize the value of the root node in the resulting xml string. Also, your classes must be decorated with the proper Xml attributes which will control how an entity is serialized.
This post explains how to create an XSD and a Classes from an XML file and then covers serialisation and de-serialisation.
http://geekswithblogs.net/CWeeks/archive/2008/03/11/120465.aspx
Using this technique with the XSD.exe to create an XSD and then classes in a CS file I was able to serialisation and then de-serialisation back again.
However the serialisation process does not create an exact representation of the source XML, so there's still some post work to be done there.

How do I create an XmlNode from a call to XmlSerializer.Serialize?

I am using a class library which represents some of its configuration in .xml. The configuration is read in using the XmlSerializer. Fortunately, the classes which represent the .xml use the XmlAnyElement attribute at which allows me to extend the configuration data for my own purposes without modifying the original class library.
<?xml version="1.0" encoding="utf-8"?>
<Config>
<data>This is some data</data>
<MyConfig>
<data>This is my data</data>
</MyConfig>
</Config>
This works well for deserialization. I am able to allow the class library to deserialize the .xml as normal and the I can use my own XmlSerializer instances with a XmlNodeReader against the internal XmlNode.
public class Config
{
[XmlElement]
public string data;
[XmlAnyElement]
public XmlNode element;
}
public class MyConfig
{
[XmlElement]
public string data;
}
class Program
{
static void Main(string[] args)
{
using (Stream fs = new FileStream(#"c:\temp\xmltest.xml", FileMode.Open))
{
XmlSerializer xser1 = new XmlSerializer(typeof(Config));
Config config = (Config)xser1.Deserialize(fs);
if (config.element != null)
{
XmlSerializer xser2 = new XmlSerializer(typeof(MyConfig));
MyConfig myConfig = (MyConfig)xser2.Deserialize(new XmlNodeReader(config.element));
}
}
}
I need to create a utility which will allow the user to generate a new configuration file that includes both the class library configuration as well my own configuration, so new objects will be created which were not read from the .xml file. The question is how can I serialize the data back into .xml?
I realize that I have to initially call XmlSerializer.Serialize on my data before calling the same method on the class library configuration. However, this requires that my data is represented by an XmlNode after calling Serialize. What is the best way to serialize an object into an XmlNode using the XmlSerializer?
Thanks,
-kevin
btw-- It looks like an XmlNodeWriter class written by Chris Lovett was available at one time from Microsoft, but the links are now broken. Does anyone know of an alternative location to get this class?
So you need to have your class contain custom configuration information, then serialize that class to XML, then make that serialized XML into an XML node: is that right?
Could you just take the string created by the XMLSerializer and wrap that in it's own XML tags?
XmlSerializer xs = new XmlSerializer(typeof(MyConfig));
StringWriter xout = new StringWriter();
xs.Serialize(xout, myConfig);
XmlDocument x = new XmlDocument();
x.LoadXml("<myConfig>" + xout.ToString() + "</myConfig>");
Now x is an XmlDocument containing one element, "<myconfig>", which has your serialized custom configuration in it.
Is that at all what you're looking for?
It took a bit of work, but the XPathNavigator route does work... just remember to call .Close on the XmlWriter, .Flush() doesn't do anything:
//DataContractSerializer serializer = new DataContractSerializer(typeof(foo));
XmlSerializer serializer = new XmlSerializer(typeof(foo));
XmlDocument doc = new XmlDocument();
XPathNavigator nav = doc.CreateNavigator();
XmlWriter writer = nav.AppendChild();
writer.WriteStartDocument();
//serializer.WriteObject(writer, new foo { bar = 42 });
serializer.Serialize(writer, new foo { bar = 42 });
writer.WriteEndDocument();
writer.Flush();
writer.Close();
Console.WriteLine(doc.OuterXml);
One solution is to serialize the inner object to a string and then load the string into a XmlDocument where you can find the XmlNode representing your data and attach it to the outer object.
XmlSerializer xser1 = new XmlSerializer(typeof(Config));
XmlSerializer xser2 = new XmlSerializer(typeof(MyConfig));
MyConfig myConfig = new MyConfig();
myConfig.data = "My special data";
StringBuilder sb = new StringBuilder();
StringWriter sw = new StringWriter(sb);
XmlWriter xw = new XmlTextWriter(sw);
xser2.Serialize(xw, myConfig);
XmlDocument doc = new XmlDocument();
doc.LoadXml(sb.ToString());
Config config = new Config();
config.data = "some new info";
config.element = doc.LastChild;
xser1.Serialize(fs, config);
However, this solution is cumbersome and I would hope there is a better way, but it resolves my problem for now.
Now if I could just find the mythical XmlNodeWriter referred to on several blogs!
At least one resource points to this as an alternative to XmlNodeWriter: http://msdn.microsoft.com/en-us/library/5x8bxy86.aspx. Otherwise, you could write MS using that form they have on the new MSDN Code Library replacement for GotDotNet looking for XmlNodeWriter.

Categories