Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MEF plugins with their own configuration files?

I'm trying to load plugins at runtime and access their configuration files. The configuration sections in their config files are mapped to classes derived from ConfigurationElementCollection, ConfigurationElement and ConfigurationSection. The plugins and their configuration files are location in a subfolder called "Plugins".

The problem is that I can't seem to load the plugin configuration data and deserialize it into their respective classes correctly.

Here is an example of a plugin config for the plugin EmailPlugin.dll:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section name="EmailConfigurationSection" type="Foo.Plugins.EmailConfigurationSection, EmailPlugin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" allowDefinition="Everywhere" allowExeDefinition="MachineToApplication" restartOnExternalChanges="true"/>
  </configSections>
  <EmailConfigurationSection server="192.168.0.10">
    <EmailSettings>
      <add  keyword="ERROR"
                    sender="[email protected]"
                    recipients="[email protected], [email protected]"
                    subject = "Error occurred"
                    body = "An error was detected"
            />
    </EmailSettings>
  </EmailConfigurationSection>
</configuration>

I load this using this code:

    private static System.Configuration.Configuration config = null;
    public static System.Configuration.Configuration CurrentConfiguration
    {
        get
        {
            if (config == null)
            {
                Assembly assembly = Assembly.GetAssembly(typeof(EmailPlugin));
                string directory = Path.GetDirectoryName(assembly.CodeBase);
                string filename = Path.GetFileName(assembly.CodeBase);
                string assemblyPath = Path.Combine(directory, filename);
                config = ConfigurationManager.OpenExeConfiguration(new Uri(assemblyPath).LocalPath);
            }

            return config;
        }
    }

This results in the error:

An error occurred creating the configuration section handler for EmailConfigurationSection: Could not load file or assembly 'EmailPlugin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies. The system cannot find the file specified.

I added this to the top of the config file:

<runtime>
  <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
     <probing privatePath="Plugins"/>
  </assemblyBinding>
</runtime>

So the DLL is found, but it doesn't not cast to the proper class when I try to retrieve it:

EmailConfigurationSection defaults = CurrentConfiguration.Sections["EmailConfigurationSection"] as EmailConfigurationSection;

It always returns null. I know it's looking at the correct location and configuration file because I can retrieve the XML using this code:

var section = CurrentConfiguration.Sections["EmailConfigurationSection"];
string configXml = section.SectionInformation.GetRawXml();

However, when I try to deserialize it with this code:

var serializer = new XmlSerializer(typeof(EmailConfigurationSection));
object result;

EmailConfigurationSection defaults;
using (TextReader reader = new StringReader(configXml))
{
    defaults = (EmailConfigurationSection)serializer.Deserialize(reader);
}

... I get an exception:

There was an error reflecting type 'Foo.Plugins.EmailConfigurationSection'.

This is the contents of the InnerException:

You must implement a default accessor on System.Configuration.ConfigurationLockCollection because it inherits from ICollection.

I assume it's referring to the class EmailConfigElementCollection, but then the message does not make sense because this class does have a default accessor:

    public EmailConfigElement this[int index]
    {
        get
        {
            return (EmailConfigElement)BaseGet(index);
        }
        set
        {
            if (BaseGet(index) != null)
            {
                BaseRemoveAt(index);
            }
            BaseAdd(index, value);
        }
    }

I've used this code successfully in other projects (even with separate DLLs/configs), but this is the first time I'm trying to use it with MEF. Does anyone know what the problem is, or a suitable workaround?

I'm using .NET 4.5

like image 349
ilitirit Avatar asked Aug 14 '12 14:08

ilitirit


1 Answers

I fixed this with the following modification:

public static System.Configuration.Configuration CurrentConfiguration
{
    get
    {
        if (config == null)
        {
            // Added the next bit
            AppDomain.CurrentDomain.AssemblyResolve += (o, args) =>
            {
                var loadedAssemblies = AppDomain.CurrentDomain.GetAssemblies();
                return loadedAssemblies.Where(asm => asm.FullName == args.Name)
                                           .FirstOrDefault();
            };

            Assembly assembly = Assembly.GetAssembly(typeof(EmailPlugin));
            string directory = Path.GetDirectoryName(assembly.CodeBase);
            string filename = Path.GetFileName(assembly.CodeBase);
            string assemblyPath = Path.Combine(directory, filename);
            config = ConfigurationManager.OpenExeConfiguration(new Uri(assemblyPath).LocalPath);
        }

        return config;
    }
}

I got this from this question: Custom configuration sections in MEF exporting assemblies. I had actually tried earlier it with no success.

The trick was that I had to move the runtime tag to the bottom of the XML configuration:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section name="EmailConfigurationSection" type="Foo.Plugins.EmailConfigurationSection, EmailPlugin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" allowDefinition="Everywhere" allowExeDefinition="MachineToApplication" restartOnExternalChanges="true"/>
  </configSections>
  <EmailConfigurationSection server="255.255.255.1">
    <EmailSettings>
      <clear />
      <add  keyword="FOO"
                    sender="[email protected]"
                    recipients="[email protected]"
                    subject = "Foo occurred"
                    body = "Hello"
            />
    </EmailSettings>
  </EmailConfigurationSection>
    <runtime>
      <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
         <probing privatePath="Plugins"/>
      </assemblyBinding>
   </runtime>
</configuration>
like image 125
ilitirit Avatar answered Oct 30 '22 13:10

ilitirit