Question

I have a custom .Net dll component that will be called from MS Access via COM Interop.

Because MSACCESS.EXE is the calling process, it will by default attempt to locate the assemblies .config file, and any referenced ddls, at the location where MS Access is installed.

Because of deployment concerns, we want all of the custom code to run from a separate location, not from within the MS Office folder structure.

I've been able to force the assembly to load it's configuration information from a custom location using:

AppDomain.CurrentDomain.SetData("APP_CONFIG_FILE", customPath);

typeof(ConfigurationManager)
            .GetField("s_initState", BindingFlags.NonPublic |
                                        BindingFlags.Static)
            .SetValue(null, 0);

typeof(ConfigurationManager)
            .GetField("s_configSystem", BindingFlags.NonPublic |
                                        BindingFlags.Static)
            .SetValue(null, null);

typeof(ConfigurationManager)
            .Assembly.GetTypes()
            .Where(x => x.FullName ==
                        "System.Configuration.ClientConfigPaths")
            .First()
            .GetField("s_current", BindingFlags.NonPublic |
                                    BindingFlags.Static)
            .SetValue(null, null);

That seems to work fine and everything seems to work at this point except logging. No matter what, the log4net.dll file will not load from any location other than the directory where MSACCESS.EXE is located.

I tried adding the following to my app.config file (log4net.dll is in the same directory as my .Net component) but it seems to be ignored.

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

I used SysInternals procmon to confirm that the dll is being found when the component is called from Access, and it is successfully located, but then it reverts to trying to load it from the MS Access directory and fails.

Is there anyway to force log4net.dll to load from the location that I want?

Here's the output from the log4net internal debug log:

log4net:ERROR Failed to parse config file. Is the <configSections> specified as: <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,log4net, Version=1.2.11.0, Culture=neutral, PublicKeyToken=669e0ddf0bb1aa2a" />
System.Configuration.ConfigurationErrorsException: An error occurred creating the configuration section handler for log4net: Could not load file or assembly 'log4net' or one of its dependencies. The system cannot find the file specified. (C:\mycomponent\alt.config line 4) ---> System.IO.FileNotFoundException: Could not load file or assembly 'log4net' or one of its dependencies. The system cannot find the file specified.
   at System.Configuration.TypeUtil.GetTypeWithReflectionPermission(IInternalConfigHost host, String typeString, Boolean throwOnError)
   at System.Configuration.RuntimeConfigurationRecord.RuntimeConfigurationFactory.Init(RuntimeConfigurationRecord configRecord, FactoryRecord factoryRecord)
   at System.Configuration.RuntimeConfigurationRecord.RuntimeConfigurationFactory.InitWithRestrictedPermissions(RuntimeConfigurationRecord configRecord, FactoryRecord factoryRecord)
   at System.Configuration.RuntimeConfigurationRecord.CreateSectionFactory(FactoryRecord factoryRecord)
   at System.Configuration.BaseConfigurationRecord.FindAndEnsureFactoryRecord(String configKey, Boolean& isRootDeclaredHere)
   --- End of inner exception stack trace ---
   at System.Configuration.BaseConfigurationRecord.FindAndEnsureFactoryRecord(String configKey, Boolean& isRootDeclaredHere)
   at System.Configuration.BaseConfigurationRecord.GetSectionRecursive(String configKey, Boolean getLkg, Boolean checkPermission, Boolean getRuntimeObject, Boolean requestIsHere, Object& result, Object& resultRuntimeObject)
   at System.Configuration.BaseConfigurationRecord.GetSection(String configKey)
   at System.Configuration.ClientConfigurationSystem.System.Configuration.Internal.IInternalConfigSystem.GetSection(String sectionName)
   at System.Configuration.ConfigurationManager.GetSection(String sectionName)
   at System.Configuration.ConfigurationSettings.GetConfig(String sectionName)
Was it helpful?

Solution

I have used something like this before. Some notes, though:

  • This is pretty generic, so feel free to modify it as needed
  • You can potentially mess up the AppDomain if there are conflicting versions (ie. main app uses log4net 2.0 and your app uses log4net 3.0)

Code:

class AssemblyResolver
  {
    static AssemblyResolver()
    {
      AppDomain.CurrentDomain.AssemblyResolve +=
        (sender, args) =>
          {
            var referencedAssemblies = Assembly.GetExecutingAssembly().GetReferencedAssemblies();
            var instanceName = referencedAssemblies.ToList().First(x => x.FullName == args.Name).Name;
            var loadFile = Assembly.LoadFile(System.IO.Path.GetDirectoryName(Assembly.GetAssembly(typeof(AssemblyResolver)).Location) + @"\" + instanceName + ".dll");
            return loadFile;
          }; 
    }

    public AssemblyResolver()
    {

    }

  }

Then, to use this, just load the AssemblyResolver. Its static constructor will deal with the maintenance

First thing to do when your code is instantiated:

new AssemblyResolver()
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top