Custom config section: Could not load file or asse

2019-01-23 12:34发布

问题:

I'm having a very hard time trying to access a custom configuration section in my config file.

The config file is being read from a .dll that is loaded as a plug-in. I created the Configuration and necessary code using the Configuration Section Designer VS addin.

The namespace is 'ImportConfiguration'. The ConfigurationSection class is 'ImportWorkflows'. The assembly is ImportEPDMAddin.

The xml:

  <configSections>
    <section name="importWorkflows" type="ImportConfiguration.ImportWorkflows, ImportEPDMAddin"/>
  </configSections>

Whenever I try to read in the config, I get the error:

An error occurred creating the configuration section handler for importWorkflows: Could not load file or assembly 'ImportEPDMAddin.dll' or one of its dependencies. The system cannot find the file specified.

The dll will not reside in the same directory as the executable as the software that loads the plugin places the dll and it's dependencies in it's own directory. (I can't control that.)

I edited the code for the singleton instance to the following:

string path = System.Reflection.Assembly.GetCallingAssembly().CodeBase;
path = path.Replace("file:///", "");
System.Configuration.Configuration configuration = System.Configuration.ConfigurationManager.OpenExeConfiguration(path);
return configuration.GetSection(ImportWorkflowsSectionName) as ImportConfiguration.ImportWorkflows;

I have also tried using a simple NameValueFileSectionHandler as well, but I get an exception saying that it can't load file or assembly 'System'.

I have read numerous blog posts and articles and it sounds like it is possible to read a config file in for a dll, but I just can't get it to work. Any ideas? Thanks.

回答1:

Unfortunately, you will need to either have the ImportEPDMAddin assembly residing in the same folder as your executable, residing in the .Net framework folder related to the .Net framework you are using (i.e., C:\Windows\Microsoft.NET\Framework\v2.0.50727), or registered in the Global Assembly Cache.

The only other option is, if you know the path to the assembly that contains the configuration handler's defining class, you can load it without a reference with something like this:

//Class global
private Assembly configurationDefiningAssembly;

protected TConfig GetCustomConfig<TConfig>(string configDefiningAssemblyPath, 
    string configFilePath, string sectionName) where TConfig : ConfigurationSection
{
    AppDomain.CurrentDomain.AssemblyResolve += new 
        ResolveEventHandler(ConfigResolveEventHandler);
    configurationDefiningAssembly = Assembly.LoadFrom(configDefiningAssemblyPath);
    var exeFileMap = new ExeConfigurationFileMap();
    exeFileMap.ExeConfigFilename = configFilePath;
    var customConfig = ConfigurationManager.OpenMappedExeConfiguration(exeFileMap, 
        ConfigurationUserLevel.None);
    var returnConfig = customConfig.GetSection(sectionName) as TConfig;
    AppDomain.CurrentDomain.AssemblyResolve -= ConfigResolveEventHandler;
    return returnConfig;
}

protected Assembly ConfigResolveEventHandler(object sender, ResolveEventArgs args)
{
    return configurationDefiningAssembly;
}

Make sure you handle the AssemblyResolve event, as this will throw an exception without it.



回答2:

In your main applications config file, add the following (where plugins is the folder for your assembly to load from. You can use multiple paths semi-colon separated.

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

Taken from http://msdn.microsoft.com/en-us/library/823z9h8w%28v=vs.90%29.aspx



回答3:

To expand on AJ's excellent answer, here is a custom class to assist with the overhead of registering and removing the global event.

public sealed class AddinCustomConfigResolveHelper : IDisposable
{
    public AddinCustomConfigResolveHelper(
        Assembly addinAssemblyContainingConfigSectionDefinition)
    {
        Contract.Assert(addinAssemblyContainingConfigSectionDefinition != null);

        this.AddinAssemblyContainingConfigSectionDefinition =
            addinAssemblyContainingConfigSectionDefinition;

        AppDomain.CurrentDomain.AssemblyResolve +=
            this.ConfigResolveEventHandler;
    }

    ~AddinCustomConfigResolveHelper()
    {
        this.Dispose(false);
    }

    public void Dispose()
    {
        this.Dispose(true);
        GC.SuppressFinalize(this);
    }

    private void Dispose(bool isDisposing)
    {
        AppDomain.CurrentDomain.AssemblyResolve -= this.ConfigResolveEventHandler;
    }

    private Assembly AddinAssemblyContainingConfigSectionDefinition { get; set; }

    private Assembly ConfigResolveEventHandler(object sender, ResolveEventArgs args)
    {
        // often the name provided is partial...this will match full or partial naming
        if (this.AddinAssemblyContainingConfigSectionDefinition.FullName.Contains(args.Name))
        {
            return this.AddinAssemblyContainingConfigSectionDefinition;
        }

        return null;
    }
}

I would suggest creating an instance in a using statement, like so:

// you'll need to populate these two variables
var configuration = GetConfiguration();
var assembly = GetAssemblyContainingConfig();

using(new AddinCustomConfigResolveHelper(assembly))
{
    return (MyConfigSection)configuration.GetSection("myConfigSection");
}


回答4:

Could you verify that the probing paths are setup correctly in your Host application's config file? It is possible that a needed reference is not being loaded in your current application domain.

Assembly Binding ->Probing



回答5:

Have you made sure that DLL is loaded first? Perhaps with Assembly.LoadFile("PATH")?

If you can't get the classes in System.Configuration to work properly, you can always fall back on using XmlDocument to manually parse the configuration file. Use XPaths to make getting the data easier. For example (assuming your path variable above):

var document = new XmlDocument();
document.Load(path);
var node = document.SelectSingleNode("configuration/importWorkflows/add[@name='KEY']");
// Do whatever with node


回答6:

I tried AJ's answer, with rileywhite's supplement but I found that did not work for me.

In my scenario, the custom ConfigurationSection class was already in the currently-executing assembly, and trying to load it causes a stack overflow. I also did not want to put it in GAC even though it did resolve the problem as reported by the OP.

In end, I found this to work well enough for my purpose. Maybe others will find it useful:

public class CustomConfigurationSection : ConfigurationSection {
  public CustomConfigurationSection()
  {
    var reader = XmlReader.Create(<path to my dll.config>);
    reader.ReadToDescendant("CustomConfigurationSection");
    base.DeserializeElement(reader,false);
  }

  // <rest of code>
}


回答7:

Had to use the fully qualified type string of my module/plugin assembly, which is in a probing directory, so it could be located. Using EntityFramework as an example...

Incorrect:

type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework"

Correct

type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"


标签: c# config