3

I'm developing an application that accepts plugins and I decided to use MEF. Everything worked fine until I tried to use AppDomain and ShadowCopy. Now, when trying to retrieve plugin from the MEF container, I'm facing a serialization exception on the metadata interface.

Here are the several components of my code:

The container:

public class PluginManagerExtended : MarshalByRefObject
    {
        private static readonly string PluginPath = Path.Combine(AppDomain.CurrentDomain.SetupInformation.ApplicationBase, "Plugins");
        private CompositionContainer container;
        private DirectoryCatalog directoryCatalog;            

        [ImportMany(AllowRecomposition = true)]
        public IEnumerable<Lazy<IXrmToolBoxPlugin, IPluginMetadata>> Plugins { get; set; }

        public void Initialize()
        {
            try
            {
                var regBuilder = new RegistrationBuilder();
                regBuilder.ForTypesDerivedFrom<Lazy<IXrmToolBoxPlugin, IPluginMetadata>>().Export<Lazy<IXrmToolBoxPlugin, IPluginMetadata>>();

                var catalog = new AggregateCatalog();
                catalog.Catalogs.Add(new AssemblyCatalog(typeof(PluginManagerExtended).Assembly, regBuilder));

                directoryCatalog = new DirectoryCatalog(PluginPath, regBuilder);
                catalog.Catalogs.Add(directoryCatalog);

                container = new CompositionContainer(catalog);
                container.ComposeParts(this);
            }
            catch (ReflectionTypeLoadException ex)
            {
                if (ex.LoaderExceptions.Length == 1)
                {
                    throw ex.LoaderExceptions[0];
                }
                var sb = new StringBuilder();
                var i = 1;
                sb.AppendLine("Multiple Exception Occured Attempting to Intialize the Plugin Manager");
                foreach (var exception in ex.LoaderExceptions)
                {
                    sb.AppendLine("Exception " + i++);
                    sb.AppendLine(exception.ToString());
                    sb.AppendLine();
                    sb.AppendLine();
                }

                throw new ReflectionTypeLoadException(ex.Types, ex.LoaderExceptions, sb.ToString());
            }
        }

        public void Recompose()
        {
            directoryCatalog.Refresh();
            container.ComposeParts(directoryCatalog.Parts)
        }

        internal void DoSomething()
        {
            Plugins.ToList().ForEach(p => MessageBox.Show(p.Metadata.Name));
        }
}

The metadata interface:

public interface IPluginMetadata
{
    string BackgroundColor { get; }
    string BigImageBase64 { get; }
    string Description { get; }
    string Name { get; }
    string PrimaryFontColor { get; }
    string SecondaryFontColor { get; }
    string SmallImageBase64 { get; }
}

The plugin I'm working with:

[Export(typeof(IXrmToolBoxPlugin)),
ExportMetadata("Name", "A Sample Tool 2"),
ExportMetadata("Description", "This is a tool to learn XrmToolBox developement")
ExportMetadata("SmallImageBase64", null),
ExportMetadata("BigImageBase64", null),
ExportMetadata("BackgroundColor", "Lavender"),
ExportMetadata("PrimaryFontColor", "#000000"),
ExportMetadata("SecondaryFontColor", "DarkGray")]
public class Plugin : PluginBase
{
    /// <summary>
    /// This method return the actual usercontrol that will
    /// be used in XrmToolBox
    /// </summary>
    /// <returns>User control to display</returns>
    public override IXrmToolBoxPluginControl GetControl()
    {
        return new SampleTool2();
    }
}

And the code that uses both elements:

var cachePath = Path.Combine(AppDomain.CurrentDomain.SetupInformation.ApplicationBase,
    "ShadowCopyCache");
var pluginsPath = Path.Combine(AppDomain.CurrentDomain.SetupInformation.ApplicationBase,
    "Plugins");

if (!Directory.Exists(cachePath))
{
    Directory.CreateDirectory(cachePath);
}
if (!Directory.Exists(pluginsPath))
{
    Directory.CreateDirectory(pluginsPath);
}

var setup = new AppDomainSetup
{
    CachePath = cachePath,
    ShadowCopyFiles = "true",
    ShadowCopyDirectories = pluginsPath
};

domain = AppDomain.CreateDomain("XrmToolBox_AppDomain", AppDomain.CurrentDomain.Evidence, setup);
pManager =
    (PluginManagerExtended)
        domain.CreateInstanceAndUnwrap(typeof(PluginManagerExtended).Assembly.FullName,
            typeof(PluginManagerExtended).FullName);

pManager.Initialize();

// This code works well as the Metadata are accessed in the container
pManager.DoSomething();

// This code fails
string plugins = String.Join(",", pManager.Plugins.Select(p => p.Metadata.Name));

The exception I got is the following:

System.Runtime.Serialization.SerializationException: Type 'proxy_XrmToolBox.Extensibility.Interfaces.IPluginMetadata_bafb7089-c69c-4f81-92f8-a88eda6d70eb' in assembly 'MetadataViewProxies_760ed609-1713-4fe9-959c-7bfa78012252, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null' is not marked as serializable.

XrmToolBox.Extensibility.Interfaces.IPluginMetadata is the advanced metadata interface above.

I don't know what "MetadataViewProxies_760ed609-1713-4fe9-959c-7bfa78012252, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null" is

What can I do to fix this error?

Thank you

Selim Yildiz
  • 5,254
  • 6
  • 18
  • 28
Tanguy
  • 33
  • 3

1 Answers1

2

To access objects in another appdomain they must be serializable. Although you don't seem to be using IPluginMetadata in the code above I assuming it's for a strongly typed custom metadata attribute. Try making whatever implements that Serializable.

Ananke
  • 1,250
  • 9
  • 11
  • Thank you for jumping in the thread! The interface IPluginMetadata is just declared and used in Lazy statements. There is no explicit class declaration that implements this interface – Tanguy Oct 08 '15 at 12:33
  • Ah ok, got hidden by the scollbar :) The way I've done it in the past was to declare the interface and to have an attribute which implements the interface, and decorate that attribute with a [MetadataAttribute]. You could try doing that and making the attribute serializable. The other approach might add a method to plugin manager to return the metadata as a serializable DTO e.g. List GetPluginMetadata() which does the Linq query for you within the new app domain. – Ananke Oct 08 '15 at 12:50
  • Same error when implementing the GetPluginMetadata() method. Can you clarify (or point me to an external resource) how to work with the attribute you mentioned? – Tanguy Oct 08 '15 at 14:25
  • Please can you share the modifications you attempted in your question as it will help me, and any others who look at your question. – Ananke Oct 08 '15 at 14:28
  • Sure! I added this method in the plugin manager `public List GetPluginMetadata() { return Plugins.Select(p => p.Metadata).ToList(); }` and I used it in the main app: `string plugins = String.Join(",", pManager.GetPluginMetadata().Select(p => p.Name));` – Tanguy Oct 08 '15 at 14:35
  • If you change that to: return Plugins.Select(p => p.Metadata.Name).ToList(); does it correctly return a list of names without error? If so map the Metadata to a new serializable type before returning it. – Ananke Oct 08 '15 at 14:38
  • Yes, that seems to work! Ok, I will try to create a new Type that will embed all properties declared in the interface and see if everything works as expected – Tanguy Oct 09 '15 at 11:19
  • That works! I created a `PluginMetadata` class that implements `IPluginMetadata` and changed all my references to `Lazy` to `Lazy` I have another problem now but it is not related to this question so I validate your answer! – Tanguy Oct 09 '15 at 11:54