21

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.

Community
  • 1
  • 1
bitbonk
  • 48,890
  • 37
  • 186
  • 278
  • 2
    It just doesn't, a side-effect of the CLR using a different api called "Fusion" to search for assemblies. Data flow is app.config => fusion for the primary appdomain and appdomainsetup => fusion for ones you create, not the other way around. – Hans Passant Oct 26 '15 at 22:11
  • 8
    This is shaping up to be a major XY question. That excessive bounty is going to produce a lot of "parse the config file" answers. Fusion is readily available in a .NET program as well, pretty unlikely that you need anything else. But we can't guess, what is the *real* problem, why does it matter that you know the probing path? – Hans Passant Oct 29 '15 at 00:04
  • What are you trying to do this for? Why do you need the PrivateBinPath? "Is there a better way" to do what exactly? – Mick Oct 29 '15 at 04:08
  • I'm in agreement with Hans here, it's a pretty simple task to read the config file manually, but I'm sure there's something better you can do. – DavidG Oct 29 '15 at 09:31
  • I have updated my question. Are your questions properly adressed? – bitbonk Oct 29 '15 at 09:57
  • @bitbonk: in Update 2 you wrote: "Assemblies are loaded using Assembly.Load (as opposed to Assembly.LoadFrom)". When we are using .Load(...) the loader should transparently use the config settings including the probing paths. So it is not clear, if you do not intend to use LoadFrom, then what is the use of knowing the paths explicitely? – g.pickardou Nov 03 '15 at 08:25
  • @DavidG: "...there's something better you can do". Do to achieve what goal? – g.pickardou Nov 03 '15 at 08:29
  • @g.pickardou The system autodiscovers and loads "plugins" by a convention: All assemblies **in the probing path** that reference the Nancy framework assembly are loaded. All other assemblies that are in the probing path are ignored by this particular mechanism. That doesn't mean that they will not be loaded at all, just not by the plugin discovery mechanism. – bitbonk Nov 03 '15 at 09:20
  • @bitbonk: Thanks, all clear. So you need the probing paths for getting a (filtered) list of assembly full names to feed Assembly.Load(..) with them. – g.pickardou Nov 03 '15 at 09:29

5 Answers5

6

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.

Sinix
  • 1,306
  • 9
  • 46
6

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"

g.pickardou
  • 32,346
  • 36
  • 123
  • 268
  • Although the approach to parse App.config manually is just a hack I am accepting it as an answer because no better solution came up. It probably works correctly in most of the cases. – bitbonk Nov 05 '15 at 06:54
  • Thx for the feedback. To lower the feeling of "hack" I am using the current appdomain's SetupInformation to get the config in effect. Also: The probing mechanism and the schema of the config file is a very stable part of .NET since its advent, it is part of the specification. It should not change, because it would be a _horrible_ breaking change. – g.pickardou Nov 05 '15 at 07:03
  • Btw, that question is still open, why PrivateBinPath is not set properly for the primary AppDomain, where most probably (99.99% of the cases) the applications will try to utilize it. – g.pickardou Nov 05 '15 at 07:05
1

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)

Jeff
  • 35,755
  • 15
  • 108
  • 220
  • OP asked about 'howto make nancy to load assemblies from ` ` dirs?', not about 'howto override probing logic?' ; ) – Sinix Oct 29 '15 at 12:19
  • And since it is not possible to see it in AppDomainSetup, this should be an alternative solution – Jeff Oct 29 '15 at 16:13
1

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);
}
Community
  • 1
  • 1
Mick
  • 6,527
  • 4
  • 52
  • 67
0

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;
}
tom.maruska
  • 1,411
  • 16
  • 22