AppDomain.CurrentDomain.SetupInformation.PrivateBinPath is null - c#

When I start my application that only has one AppDomain, AppDomain.CurrentDomain.SetupInformation.PrivateBinPath is null. Even though I have probing paths set in MyApp.exe.config as shown below.
I would have expeceted that AppDomain.CurrentDomain.SetupInformation.PrivateBinPath contains the string "Dir1;Dir2;Dir3".
How can I access the probing path as configured in the MyApp.exe.config?
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<appSettings>
<add key="Foo" value="Bar" />
</appSettings>
<startup>
<!-- supportedRuntime version="v1.1.4322" / -->
</startup>
<runtime>
<gcConcurrent enabled="true" />
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<publisherPolicy apply="yes" />
<!-- Please add your subdirectories to the probing path! -->
<probing privatePath="Dir1;Dir2;Dir3" />
</assemblyBinding>
</runtime>
<system.windows.forms jitDebugging="true" />
</configuration>
Update
As Hans Passant pointed out the comment below, SetupInformation.PrivateBinPath is not set for the primary appdomain. So the above doesn't work. What would be your suggestion to simulate the way fusion searches for assemblies in the probing path or at least take <probing privatePath="" /> from the current application configuration into account? The best thing I can come up with is to read <probing privatePath="" /> from App.config manually when the current domain is the primary appdomain (AppDomain.CurrentDomain.IsDefaultAppDomain() is true). Is there a better way?
Update 2
Here some additional background information what this is needed for: This problem occured in AppDomainAssemblyTypeScanner.GetAssemblyDirectories() of the Nancy framework.
Nancy autodiscovers and loads 3rd party modules and other "plugins". By default this is supposed to be done same way as normally linked assemblies would be loaded (i.e. as fusion would do it) by looking through the probing paths. Assemblies are loaded using Assembly.Load (as opposed to Assembly.LoadFrom) so as I understand it, all the dependent assemblies of the loaded assemblies must be reachable in the probing path of the application/appdomain too.

How can I access the probing path as configured in the MyApp.exe.config
To remain compatible what fusion will do, you can read the config file in effect to get the current probing paths:
private static string GetProbingPath()
{
var configFile = XElement.Load(AppDomain.CurrentDomain.SetupInformation.ConfigurationFile);
var probingElement = (
from runtime
in configFile.Descendants("runtime")
from assemblyBinding
in runtime.Elements(XName.Get("assemblyBinding", "urn:schemas-microsoft-com:asm.v1"))
from probing
in assemblyBinding.Elements(XName.Get("probing", "urn:schemas-microsoft-com:asm.v1"))
select probing)
.FirstOrDefault();
return probingElement?.Attribute("privatePath").Value;
}
Supposing the config file sample in your question it returns:
"Dir1;Dir2;Dir3"

Not an answer, did not fit as a comment
As said above, default appdomain doesn't use AppDomainSetup for path probing config.
Instead of this the probing path is read from .appconfig file and is not exposed into managed code.
* self-hosted app on the full clr can override the behavior with custom ICustomAppDomainManager (or IHostAssemblyManager) but that's out of scope of the question.
So there're only three approaches possible:
First, you can call Nancy.Bootstrapper.AppDomainAssemblyTypeScanner.LoadAssemblies(somedir, "*.dll") by yourself
Second, you can wrap nancy host into secondary appdomain with custom private bin path set.
Third: wait for https://github.com/NancyFx/Nancy/pull/1846 and use custom IResourceAssemblyProvider.
In any case you'll need the list of assembly' directories. If you do not want to store the copy as <appSettings> value you'll had to parse the appconfig file by yourself.

I've always found that the easiest thing to do is intercept the AppDomain.AssemblyResolve event. Then you can load whatever assembly you want from wherever you want and return it. You can still store your settings in the appConfig...You could even probing path section if you particularly want to use it. One thing to note is that assemblies loaded using Assembly.Load don't end up in the same load context as assemblies loaded under the default load context (https://msdn.microsoft.com/en-us/library/dd153782(v=vs.110).aspx). This has the effect of changing how type and assembly resolution occurs for subsequent resolutions (after the initial call to Assembly.Load). Accordingly, you may want to intercept AppDomain.TypeResolve as well as AssemblyResolve...and you'll want to cache the Assemblies you load from AssemblyResolve...otherwise subsequent resolutions MAY actually load the same assembly again (depending on how exactly you call Assembly.Load)

If this is a problem of assemblies not loading, one method I've found to work effectively is to use the AppDomain.AssemblyResolve event which is fired whenever the appdomain fails to load an assembly...
Working with AppDomain.AssemblyResolve event
e.g.
AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(LoadManually);
private Assembly LoadManually(object sender, ResolveEventArgs args)
{
....
return Assembly.LoadFrom(whereEverYouLike);
}

Inspired by g.pickardou's solution, I have created function without necessity to reference System.Xml.Linq:
private static string GetProbingPath()
{
var xmlDoc = new XmlDocument();
xmlDoc.Load(AppDomain.CurrentDomain.SetupInformation.ConfigurationFile);
var privatePathAttribute = xmlDoc.SelectSingleNode("/*[name()='configuration']/*[name()='runtime']/*[name()='assemblyBinding']/*[name()='probing']/#privatePath");
return (privatePathAttribute as XmlAttribute)?.Value;
}

Related

understanding CLR/System.IO.FileNotFoundException "Could not load file or assembly ... or one of its dependencies"

I have an issue with using System.Text.Json insinde my C# class library, which is a SolidWorks addin. It is probably an instance of DLL hell as described here.
Since this approach does not work, I might be able to figure something out if I understand a bit more about this issue. Maybe someone can help?
First - my code.
My 'csproj' file:
<Project Sdk="Microsoft.NET.Sdk">
<!-- general stuff -->
<PropertyGroup>
<TargetFrameworks>net48</TargetFrameworks>
<ImplicitUsings>disable</ImplicitUsings>
</PropertyGroup>
<!-- references: the top two are SolidWorks API (needed for making a SolidWorks addin -->
<ItemGroup>
<PackageReference Include="com.solidworks.core" Version="29.5.1" />
<PackageReference Include="com.solidworks.tools" Version="21.5.0" />
<PackageReference Include="System.Text.Json" Version="6.0.2" />
</ItemGroup>
<!-- In order to have the addin available within SolidWorks,
it's dll needs to be registered in the codebase. For convenience
we automatically register on build and unregister on clean. -->
<Target Name="Register" AfterTargets="AfterBuild">
<Exec Command="%windir%\Microsoft.NET\Framework64\v4.0.30319\RegAsm.exe "$(TargetPath)" /codebase" />
</Target>
<Target Name="Unregister" BeforeTargets="BeforeClean">
<Exec Command="%windir%\Microsoft.NET\Framework64\v4.0.30319\RegAsm.exe "$(TargetPath)" /u" />
</Target>
</Project>
The relevant parts of my cs file:
using System;
using System.Runtime.InteropServices;
using Microsoft.Win32;
using SolidWorks...; // all the SolidWorks usings required
namespace SwxAddin
{
[Guid("acb6f17b-9738-4f11-a324-30e05625ff89")]
[ComVisible(true)]
public class SwxAddinImpl : ISwAddin
{
// will be called on addin load in SolidWorks
public bool ConnectToSW(object swx, int addinId)
{
var jsonText = "{ \"foo\": { \"bar\": 2 } }";
var doc = System.Text.Json.JsonDocument.Parse(jsonText); // exception occurs
return swx != null;
}
// will be called on addin unload in SolidWorks
public bool DisconnectFromSW() { return true; }
// This is run when registering the dll. It writes some stuff into the
// SolidWorks registry to make the addin available.
[ComRegisterFunction]
protected static void RegisterFunction(Type type) { ... }
// This is run when unregistering the dll. It removes the stuff from the
// SolidWorks registry that was written into it by RegisterFunction.
[ComUnregisterFunction]
protected static void UnregisterFunction(Type type) { ... }
}
}
When I run SolidWorks after building (and thus, registering my dll in the codebase) and debug it, I get a runtime error on
var doc = System.Text.Json.JsonDocument.Parse(jsonText);
saying
Exception has occurred: CLR/System.IO.FileNotFoundException An
exception of type 'System.IO.FileNotFoundException' occurred in
System.Text.Json.dll but was not handled in user code: 'Could not load
file or assembly 'System.Runtime.CompilerServices.Unsafe,
Version=4.0.4.1, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' or
one of its dependencies. The system cannot find the file specified.'
. As mentioned above, I did try adding
<PropertyGroup>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<GenerateBindingRedirectsOutputType>true</GenerateBindingRedirectsOutputType>
</PropertyGroup>
to my csproj file, resulting in the following .dll.config file in my bin/Debug folder:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Runtime.CompilerServices.Unsafe" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>
but the runtime error still occurs.
So I'd like to actually understand the issue instead of just following cooking recipes. Here are some things I tried and thoughts:
The error says the issue is inside System.Text.Json.dll. I understand it so that the file System.Text.Json.dll, which lies in location A, expects a file System.Runtime.CompilerServices.Unsafe.dll of version 4.0.4.1 in location B, but in location B there is a different version of file System.Runtime.CompilerServices.Unsafe.dll (or no file of that name at all).
=> Can anyone tell me which locations A and B we are talking about? Is it a certain folder? Is it the GAC? In case it is the GAC, are we actually talking about files, or something else?
I checked the (for me) most probable location, the folder $myProjectPath\bin\Debug\net48. There I can find (amongst others) both dlls System.Text.Json.dll and System.Runtime.CompilerServices.Unsafe.dll. I opened both in some decompilation tool to check their versions and the versions of their references. This is what I found:
System.Text.Json.dll has version 6.0.0.2 and references System.Runtime.CompilerServices.Unsafe.dll of version 6.0.0.0.
System.Runtime.CompilerServices.Unsafe.dll has version 6.0.0.0.
=> So the required version and the present version of System.Runtime.CompilerServices.Unsafe.dll do align. Why do I then get the error? Doesn't this just mean that location A and B are NOT $myProjectPath\bin\Debug\net48? Or is the referenced version ignored under some circumstances? What kind of cirumstances?
I built a standalone console app, just using System.Text.Json and containing the two lines
var jsonText = "{ \"foo\": { \"bar\": 2 } }";
var doc = System.Text.Json.JsonDocument.Parse(jsonText);
inside it Main method. No runtime error occurs there. So SolidWorks must be the culprit somehow, even if it is not mentioned in the runtime error message.
This article covers dll hell and gives suggestions for troubleshooting. I checked the modules (Debug -> Windows -> Modules) in Visual Studio. It turns out that just before the error occurs
System.Text.Json version 6.0.0.0 is already loaded (from my $myProjectPath\bin\Debug\net48 folder).
System.Runtime.CompilerServices.Unsafe is not loaded.
=> But if System.Runtime.CompilerServices.Unsafe has not been loaded before, why does System.Text.Json want to load version 4.0.4.1 instead of the version specified in its own references (6.0.0.0)? Where does the 4.0.4.1 come from?
Thanks to M Kloster's comment, I could work around the issue by manually loading the assembly - although this unfortunately does not help understanding the issue.
First I inserted the line
AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(MyResolveEventHandler);
into the ConnectToSW method (as first line).
Then I implemented MyResolveEventHandler like so:
private static Assembly MyResolveEventHandler(object sender, ResolveEventArgs args)
{
var nameCompilerServicesUnsafe = "System.Runtime.CompilerServices.Unsafe";
if (args.Name == nameCompilerServicesUnsafe + ", Version=4.0.4.1, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")
{
var assemblyPath = Assembly.GetCallingAssembly().Location;
if (Path.GetFileName(assemblyPath) == "System.Text.Json.dll")
{
var assemblyFolder = Path.GetDirectoryName(assemblyPath);
var pathCompilerServicesUnsafe = Path.Combine(assemblyFolder, nameCompilerServicesUnsafe + ".dll");
if (File.Exists(pathCompilerServicesUnsafe))
return Assembly.LoadFile(pathCompilerServicesUnsafe);
}
}
return null;
}
Now, whenever an assembly cannot be loaded by the automatic mechanism, MyResolveEventHandler will be called.
Here I just check if it is System.Text.Json.dll trying to load System.Runtime.CompilerServices.Unsafe version 4.0.4.1, and if yes, I return the assembly System.Runtime.CompilerServices.Unsafe.dll from System.Text.Json.dll's location folder.
Strangely, this allowed me to confirm that the System.Text.Json.dll trying to load System.Runtime.CompilerServices.Unsafe version 4.0.4.1 really is the one located in my $myProjectPath\bin\Debug\net48 folder. Which makes no sense to me, as the decompilation tool told me that the file $myProjectPath\bin\Debug\net48\System.Text.Json.dll references System.Runtime.CompilerServices.Unsafe version 6.0.0.0, not 4.0.4.1.
And as I said in my question, the issue does not occur outside of SolidWorks (e.g. in a standalone console app). So SolidWorks must somehow interfere in the (automatic) assembly resolving mechanism, maybe redirecting bindings? Very mysterious... Is there a way to turn that off?

MEF Composition Error Because of Assembly Names?

Thanks in advance for reviewing this question!
I am using MEF to load some assemblies in a project. Everything was working great until we changed the name of the file that contains the interface.
To make it clearer, I'll summarize the scenario where things worked, then the scenario where things did not work, then specifically show the exception and the code that caused the exception in the scenario that didn't work.
Here's the working scenario:
We've got an interface called IPlugin that is defined in an assembly called Common-1-0.dll.
We have some plugin assemblies that are compiled against Common-1-0.dll.
The application that loads the plugin is compiled against Common-1-0.dll.
Here's the non working scenario:
We've got this an called IPlugin that is defined in an assembly called Common-1-1.dll. The interface has not changed from Common-1-0.dll.
We have some plugin assemblies that are compiled against Common-1-0.dll.
The application that loads the plugin is compiled against Common-1-1.dll.
Now the issue:
When I go to run this code below in the second scenario, I get a CompositionException (shown under the code below). It appears to be caused because the the plugin was compiled against Common-1-0.dll while the application trying to do the composition was compiled against Common-1-1.dll. Nothing within the code has changed between the two files, just the name.
So we'd like to be able to load plugins built against whatever assembly so long as that assembly exports the right interface but I'm not sure if I can do that with MEF or not. That is what I'd like to know as a result of this question.
Code :
private void LoadPlugins(string directory, string searchPattern = "", bool recursive = false)
{
Trace.Agent.Status("Loading plugin(s) from {0}{1}{2}", directory, Path.DirectorySeparatorChar, searchPattern);
try
{
var directoryCatalog = string.IsNullOrEmpty(searchPattern)
? new DirectoryCatalog(directory)
: new DirectoryCatalog(directory, searchPattern);
_container = new CompositionContainer(new AggregateCatalog(directoryCatalog));
_container.ComposeParts(this);
}
catch (CompositionException exc)
{
Trace.Agent.Exception(exc);
}
if (recursive)
{
foreach (string dir in Directory.GetDirectories(directory))
{
LoadPlugins(Path.Combine(directory, dir), recursive:true);
}
}
}
CompositionException :
The export 'TestPlugin.TestPlugin (ContractName="Common.IPlugin")' is not assignable to type 'Common.IPlugin'.
I think I found a solution for your issue, it is a little bit hacked but anyways.
So if you have named your assemblies differently, e.g. assembly1.0 and assembly1.1 this causes issues because you cannot simply redirect the assembly to a new version.
Usually you would just keep the same assembly name and increase the version number. This way you can redirect without any issues (as long as the code supports it).
The solution is to hack into the assembly resolve mechanism by attaching to the AssemblyResolve event of the current AppDomain.
AppDomain currentDomain = AppDomain.CurrentDomain;
currentDomain.AssemblyResolve += new ResolveEventHandler(MyResolveEventHandler);
LoadPlugins(currentDomain.BaseDirectory);
var x = _container.GetExport<IPlugin>().Value;
within the event handler you can simply return the "new" assembly
private static Assembly MyResolveEventHandler(object sender, ResolveEventArgs args)
{
if (args.Name.StartsWith("PluginCore1.0"))
{
return typeof(IPlugin).Assembly;
}
return null;
}
This works, also without signed assemblies (without public key tokens).
To trigger the resolve event you still have to define the assembly redirection within your app.config though:
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="PluginCore1.0"
culture="neutral" />
<bindingRedirect oldVersion="1.0.0.0" newVersion="1.1.0.0" />
</dependentAssembly>
</assemblyBinding>
Again, I would strongly recommend to use the same assembly name (without the version suffix) and simply use the assembly redirection mechanism which should work just fine.

Can Unity Container Load Assemblies Dynamically?

I was developing an application. I want unity to resolve my types WITHOUT having to reference the assemblies in the main project. I tought it loaded assemblies automatically by configuring the type registration by using but doesn't seem to work unless I add a reference to the assembly containing dependencies.
Is there a wat to load types from the assemblies in the current directory?
Thanks!
Sounds like you want MEF and not Unity. MEF is designed for dynamic discovery.
Read the answer to this question: What is different between and purpose of MEF and Unity?
I know this was asked a while ago, but for anyone looking for an answer, you have to make sure that Unity can locate the assemblies at run-time. So you either need to put them in the GAC or drop your assembly dll in the same directory as your executable. If you are using a different directory for dependencies and have a web app, then you have to set the probing element in your web.config:
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<probing privatePath="Dependencies" />
</assemblyBinding>
</runtime>
And for anyone out there looking for a piece of code on how to solve this, the following code will look for all assemblies on your defined directory and register them with Unity:
string DependenciesPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Dependencies");
string[] Dependencies = Directory.GetFiles(DependenciesPath, DLL_PATTERN);
Dictionary<Type, Type> pluginMappings = new Dictionary<Type, Type>();
//Load Dependency Assemblies
foreach (string fileName in Dependencies)
{
string assemblyName = Path.GetFileNameWithoutExtension(fileName);
if (assemblyName != null)
{
Assembly pluginAssembly = Assembly.Load(assemblyName);
foreach (Type pluginType in pluginAssembly.GetTypes())
{
if (pluginType.IsPublic) //Only look at public types
{
if (!pluginType.IsAbstract) //Only look at non-abstract types
{
//Gets a type object of the interface we need the plugins to match
Type[] typeInterfaces = pluginType.GetInterfaces();
foreach (Type typeInterface in typeInterfaces)
{
if (pluginMappings.ContainsKey(typeInterface))
{
throw new DuplicateTypeMappingException(typeInterface.Name, typeInterface, pluginMappings[typeInterface], pluginType);
}
pluginMappings.Add(typeInterface, pluginType);
}
}
}
}
}
}
//Web API resolve dependencies with Unity
IUnityContainer container = new UnityContainer();
foreach (var mapping in pluginMappings)
{
container.RegisterType(mapping.Key, mapping.Value);
}
It might be a bit late to help, but Unity can dynamically load assemblies if you use the XML configuration approach, register each assembly, and then register the types accordingly. I've been using this process for minor extensions to an otherwise very DI-heavy framework for awhile now.
If you're encountering an issue where Unity fails to resolve a type that's registered in the main application but defined in another, unreferenced assembly and referencing said assembly resolves the issue, that most likely means that it just wasn't copied to the application's output directory. By default, only assemblies that are directly referenced are automatically copied. If they're copied manually or by a post-build event, or if you redirect your build paths so that the unreferenced assemblies build to the application's output directory, it should work.

Ninject and XML configuration Binding

i have been searching the internet for any sample or getting start article on how to do binding with Ninject using XML extension but i couldnt find any help ! ,
can any body provide me with a very small sample on how can i do that ?
thanks in advance
I can't find any examples either, but honestly the source code is very small - I would just download (here) and read through the test cases.
The unit test project has some examples, like this:
<module name="basicTest">
<bind name="melee"
service="Ninject.Extensions.Xml.Fakes.IWeapon, Ninject.Extensions.Xml.Test"
to="Ninject.Extensions.Xml.Fakes.Sword, Ninject.Extensions.Xml.Test" />
<bind name="range"
service="Ninject.Extensions.Xml.Fakes.IWeapon, Ninject.Extensions.Xml.Test"
to="Ninject.Extensions.Xml.Fakes.Shuriken, Ninject.Extensions.Xml.Test" />
</module>
It doesn't seem to be very powerful. As someone else pointed out, the point of NInject is to 'free yourself from XML'.
Their only documentation shows what the xml config looks like but they don't give an example of how to load it so here is a simple example which shows both pieces of the puzzle.
Xml Config
This would be in a file called NinjectModules.xml. For it to find the type, I had to give it the assembly qualified name, even though everything was in a single .exe.
<module name="SomeModule">
<bind
service="Birds.IOwl, Birds, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"
to="Birds.SlowOwl, Birds, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
</module>
Loading Config
IKernel kernel = new StandardKernel();
kernel.Load("c:\path\to\NinjectModules.xml");
IOwl owl = kernel.Get<IOwl>();
owl.Fly();
After lost my saturday and review the Ninject.Extensions.Xml Source, I solve my problem:
(...)
var settings = new NinjectSettings { LoadExtensions = false };
kernel = new StandardKernel(settings, new XmlExtensionModule());
kernel.Load(#"C:\DEV\FSENQUETE\invista.xml");
//kernel.GetModules().Count() --> Ok! Result 34 modules... :)
Got the Solution:
Don't forget to set the Copy to Output of your xml file
Directory property of this file to Copy if newer, so that it can be copied to the
output directory automatically.
for more, see this PDF

Custom config section: Could not load file or assembly

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.
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.
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
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");
}
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
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
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"
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>
}

Categories