2

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

Felix
  • 101
  • 1
  • 2
  • 6

3 Answers3

2

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

Felix
  • 101
  • 1
  • 2
  • 6
1

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

sll
  • 61,540
  • 22
  • 104
  • 156
  • Yeah, I thought about IXmlSerializable. The problem is that my actual model is much more complex than what I used for illustration above. So, implementing the entire serialization of the MultimediaShow by hand would be horrible. Plus, whenever I change the model, I'd need to change the serialization code as well -- a very dangerous fact, since this is easily forgotten (particularly with several developers at work). Would it be possible to combine both IXmlSerializable and XmlSerializer in a smart way that allows me to just serialize the UnknownMediaType objects by hand? – Felix Jun 30 '11 at 14:42
  • How about using `System.Reflection.Emit.TypeBuilder` for creating a new type at runtime that derives from UnknownMediaType and with the class name identically to the xml tag name? Wouldn't this work? – Felix Jul 01 '11 at 06:29
  • Is it possible that ThirdPartyMediaTypy will be pretty complex structure or they are stricted by some rules? – sll Jul 01 '11 at 08:52
  • no, plugin types can be whatever they want. Not restricted at all. Why? – Felix Jul 04 '11 at 06:08
0

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

Shiraz Bhaiji
  • 64,065
  • 34
  • 143
  • 252
  • I'll have a look at it. How would I use custom serialization with XML? Does the XmlSerializer check for this interface? – Felix Jun 30 '11 at 14:43
  • How about using `System.Reflection.Emit.TypeBuilder` for creating a new type at runtime that derives from UnknownMediaType and with the class name identically to the xml tag name? Wouldn't this work? – Felix Jul 01 '11 at 06:28