Howto deserialize custom types declared in Plugin/Extension

Editor
Jan 17, 2013 at 12:58 PM
Edited Jan 17, 2013 at 2:21 PM

I'm trying to serialize/deserialize a custom layer that is defined in a plugin/extension.

While I can see that the layer is written to the dspx file, I can't read the dspx file afterwards. The problem is, that the XmlDeserializer cannot find the appropriate type.

Is there a way to circumvent this?

Side note: There is a bug in the SerializationMap. It does not allow to set readonly fields that are marked with the SerializeAttribute and a ConstructorIndex >= 0.

I'll raise an issue about that.

FObermaier

Editor
Jan 22, 2013 at 3:46 PM

Anyone?

Feb 20, 2013 at 6:00 PM
I have passed this question on to someone that might be able to answer it much better than I can.

Does the element get serialized with a type attribute? Is the Type in the TypeCache portion of the XML (should be <types>)?
Editor
Feb 20, 2013 at 7:32 PM
shieldst wrote:
Does the element get serialized with a type attribute? Is the Type in the TypeCache portion of the XML (should be <types>)?
I think it does. Here is the sample file, note item 10-12:
<?xml version="1.0" encoding="utf-16"?>
<root type="0">
  <types>
    <item key="0" value="System.Object[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
    <item key="1" value="System.String[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
    <item key="2" value="DotSpatial.Controls.Map, DotSpatial.Controls, Version=0.11.9.6, Culture=neutral, PublicKeyToken=4d9e49339a7d240c" />
    <item key="3" value="System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
    <item key="4" value="DotSpatial.Controls.MapFrame, DotSpatial.Controls, Version=0.11.9.6, Culture=neutral, PublicKeyToken=4d9e49339a7d240c" />
    <item key="5" value="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
    <item key="6" value="DotSpatial.Data.Extent, DotSpatial.Data, Version=0.11.9.6, Culture=neutral, PublicKeyToken=c29dbf30e059ca9d" />
    <item key="7" value="System.Double, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
    <item key="8" value="DotSpatial.Controls.MapLayerCollection, DotSpatial.Controls, Version=0.11.9.6, Culture=neutral, PublicKeyToken=4d9e49339a7d240c" />
    <item key="9" value="System.Collections.Generic.List`1[[DotSpatial.Symbology.ILayer, DotSpatial.Symbology, Version=0.11.9.6, Culture=neutral, PublicKeyToken=6178c08da7998387]], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
    <item key="10" value="DotSpatial.Plugins.BruTileLayer.BruTileLayer, DotSpatial.Plugins.BruTileLayer, Version=1.0.4794.27033, Culture=neutral, PublicKeyToken=1d00514a5f195794" />
    <item key="11" value="DotSpatial.Plugins.BruTileLayer.Configuration.OsmLayerConfiguration, DotSpatial.Plugins.BruTileLayer, Version=1.0.4794.27033, Culture=neutral, PublicKeyToken=1d00514a5f195794" />
    <item key="12" value="BruTile.Web.KnownOsmTileServers, BruTile, Version=0.6.4.0, Culture=neutral, PublicKeyToken=00e75fd79e391f40" />
    <item key="13" value="DotSpatial.Projections.ProjectionInfo, DotSpatial.Projections, Version=0.11.9.6, Culture=neutral, PublicKeyToken=272632bddd08a38b" />
    <item key="14" value="System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
    <item key="15" value="DotSpatial.Symbology.DynamicVisibilityMode, DotSpatial.Symbology, Version=0.11.9.6, Culture=neutral, PublicKeyToken=6178c08da7998387" />
    <item key="16" value="System.Collections.Generic.Dictionary`2[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.Object, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
    <item key="17" value="System.DateTime, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
  </types>
  <item type="1" />
  <item type="2">
    <member name="ProjectionEsriString" type="3" value="GEOGCS[&amp;quot;GCS_WGS_1984&amp;quot;,DATUM[&amp;quot;D_WGS_1984&amp;quot;,SPHEROID[&amp;quot;WGS_1984&amp;quot;,6378137,298.257223562997]],PRIMEM[&amp;quot;Greenwich&amp;quot;,0],UNIT[&amp;quot;Degree&amp;quot;,0.0174532925199433]]" />
    <member name="MapFrame" type="4">
      <member name="IsLegendGroup" type="5" value="False" />
      <member name="ExtentsInitialized" type="5" value="True" />
      <member name="ViewExtents" type="6">
        <member name="MaxX" type="7" value="5646374.72214836" />
        <member name="MaxY" type="7" value="5033850.8043615" />
        <member name="MinX" type="7" value="5225909.04467968" />
        <member name="MinY" type="7" value="4632959.17812682" />
      </member>
      <member name="ExtendBuffer" type="5" value="False" />
      <member name="Layers" type="8">
        <member name="InnerList" type="9">
          <item type="10">
            <member name="IsLegendGroup" type="5" value="False" />
            <member name="config" type="11" arg="0">
              <member name="FileCacheRoot" type="3" arg="0" value="C:\Users\obe.IVV-AACHEN\AppData\Local\Temp\Mapnic" />
              <member name="knownOsmTileServers" type="12" arg="1" value="Mapnic" />
              <member name="apiKey" type="3" arg="2" value="" />
            </member>
            <member name="projection" type="13" />
            <member name="showErrorInTile" type="5" value="True" />
            <member name="transparency" type="14" value="0" />
            <member name="DynamicVisibilityWidth" type="7" value="0" />
            <member name="DynamicVisibilityMode" type="15" value="ZoomedIn" />
            <member name="UseDynamicVisibility" type="5" value="False" />
            <member name="IsVisible" type="5" value="True" />
            <member name="LegendText" type="3" value="Mapnic" />
          </item>
        </member>
      </member>
      <member name="DynamicVisibilityWidth" type="7" value="0" />
      <member name="DynamicVisibilityMode" type="15" value="ZoomedIn" />
      <member name="UseDynamicVisibility" type="5" value="False" />
      <member name="LegendText" type="3" value="Map Layers" />
    </member>
  </item>
  <item type="16">
    <entry>
      <key type="3" value="FileCacheRoot" />
      <value type="3" value="C:\Users\obe.IVV-AACHEN\AppData\Local\Temp\" />
    </entry>
    <entry>
      <key type="3" value="MemoryCacheMinimum" />
      <value type="14" value="100" />
    </entry>
    <entry>
      <key type="3" value="MemoryCacheMaximum" />
      <value type="14" value="200" />
    </entry>
    <entry>
      <key type="3" value="BingMapsToken" />
      <value type="3" value="" />
    </entry>
    <entry>
      <key type="3" value="UniqueKey-PluginStoredValueDate" />
      <value type="17" value="01/01/0001 00:00:00" />
    </entry>
    <entry>
      <key type="3" value="FetchBasemap_BasemapName" />
      <value type="3" value="None" />
    </entry>
    <entry>
      <key type="3" value="FetchBasemap_Opacity" />
      <value type="3" value="100" />
    </entry>
  </item>
</root>
The XmlDeserializer fails in the ReadTypeCache(XElement) function calling Type.GetType() on the item with key="10". (BruTileLayer).
It just returns null. I've added a mechanism to query the type from the plugin providing it.
  1. I added an event for to query the type from the plugin. Here is the event arguments class:
// Event arguments
public class QueryTypeEventArgs : EventArgs 
{
    public QueryTypeEventArgs(string typeName)
    {
        TypeName = typeName;
    }
    public string TypeName { get; private set; }
    public Type QueriedType { get;  set; }
}
  1. Added the event handler to the XmlDeserializer class and added a constructor to provide that event handler. In the ReadTypeCache i added code to raise the event when creation of the type did not lead to any result.
public class XmlDeserializer
{
    //...
    private readonly EventHandler<QueryTypeEventArgs> _queryType;

    public XmlDeserializer()
        :this(null){}

    public XmlDeserializer(EventHandler<QueryTypeEventArgs> qt)
    {
        _queryType = qt;
    }
    //...
    private Dictionary<string, Type> ReadTypeCache(XElement rootNode)
    {
        Dictionary<string, Type> result = new Dictionary<string, Type>();
        TypeNameManager typeNameManager = new TypeNameManager();
        foreach (var typeNode in rootNode.Elements(XmlConstants.TYPE_CACHE).Elements(XmlConstants.ITEM))
        {
            var keyAttrib = typeNode.Attribute(XmlConstants.KEY);
            var valueAttrib = typeNode.Attribute(XmlConstants.VALUE);
            if (keyAttrib == null || valueAttrib == null)
                continue;

            // string[] parts = valueAttrib.Value.Split(',');

            Type t;

            try
            {
                // This method is too strict to handle auto-incrementing versioned dll references, but is
                // needed to correctly grab core .Net Framework stuff and works faster for same-version dll files.
                t = Type.GetType(valueAttrib.Value);
            }
            catch (FileLoadException)
            {
                // Attempting to load the fully qualified name failed.  Search under more general terms
                // and see if we can find a valid assembly with the specified type.
                string updatedName = typeNameManager.UpdateTypename(valueAttrib.Value);
                t = Type.GetType(updatedName);
            }

            if (t == null)
            {
                    
                if (_queryType != null)
                {
                    var qtea = new QueryTypeEventArgs(valueAttrib.Value);
                    _queryType(this, qtea);
                    t = qtea.QueriedType;
                }
            }
            result.Add(keyAttrib.Value, t);
        }

        return result;
    }
    //..
}
  1. The Serialization Manager publicly exposes such a QueryType event and invokes the XmlDeserializer with it:
        public EventHandler<QueryTypeEventArgs> QueryType { get; set; }

        private void OpenProjectFile(string fileName)
        {
            var graph = CreateObjectGraph(this._applicationManager, null);
            XmlDeserializer d = new XmlDeserializer(QueryType);
            //...
        }
  1. The plugin can subscribe to the event and provide type information. The sample does not fit anymore, but you get the idea
FObermaier
Feb 22, 2013 at 3:52 PM
I spent some time looking at this. I'll tell you what I found but it might not be anything new or helpful.

It looks like the type name is being saved correctly, so that means that for some reason the Type.GetType method is failing to find the type even though it is being passed the correct value.

From MSDN about the GetType(String) method
GetType only works on assemblies loaded from disk. If you call GetType to look up a type defined in a dynamic assembly defined using the System.Reflection.Emit services, you might get inconsistent behavior.
Could anything like what is described above be happening?
Editor
Feb 25, 2013 at 2:45 PM
shieldst wrote:
GetType only works on assemblies loaded from disk. If you call GetType to look up a type defined in a dynamic assembly defined using the System.Reflection.Emit services, you might get inconsistent behavior.
Could anything like what is described above be happening?
No, all referenced assemblies are on the disk all report
  • IsDynamic == false
  • IsFullyTrusted == true
One referenced library targets Net20 though.