如何pre-负载部署的所有组件的一个AppDomain负载、组件、pre、AppDomain

2023-09-02 10:42:46 作者:会推开就别深拥我!

更新:我现在有一个解决方案,我有这么大的快乐,而不是解决所有我问的问题,但它留下了清晰的方式来做到这一点。我已经更新了我自己的答案,以反映这一点。

UPDATE: I now have a solution I'm much happier with that, whilst not solving all the problems I ask about, it does leave the way clear to do so. I've updated my own answer to reflect this.

原始的问题的

给出一个应用程序域,也有融合(.NET程序集加载器)将探测一个给定的装配许多不同的地点。显然,我们需要这个功能是理所当然的,而且由于探测似乎是嵌入在.NET运行时中( Assembly._nLoad 内部方法似乎在反映是入口点-Loading - 我以为隐加载可能受相同的底层算法),作为开发人员,我们似乎并没有能够获得这些搜索路径访问

Given an App Domain, there are many different locations that Fusion (the .Net assembly loader) will probe for a given assembly. Obviously, we take this functionality for granted and, since the probing appears to be embedded within the .Net runtime (Assembly._nLoad internal method seems to be the entry-point when Reflect-Loading - and I assume that implicit loading is probably covered by the same underlying algorithm), as developers we don't seem to be able to gain access to those search paths.

我的问题是,我有做了很多动态类型分辨率的组成部分,并需要能够确保所有用户部署的组件对于一个给定的AppDomain是pre-加载之前就开始工作。是的,它会减慢启动 - 但是我们从这个组件获得的收益完全outweight这种

My problem is that I have a component that does a lot of dynamic type resolution, and which needs to be able to ensure that all user-deployed assemblies for a given AppDomain are pre-loaded before it starts its work. Yes, it slows down startup - but the benefits we get from this component totally outweight this.

基本负载算法我已经写如下。它深扫描一组文件夹中的所有.dll文件(.EXE文件都被排除在外的的那一刻的),并使用Assembly.LoadFrom加载DLL,如果它的AssemblyName不能在该组组件的发现已装入的AppDomain(这是低效率地实现,但它可在以后被优化):

The basic loading algorithm I've already written is as follows. It deep-scans a set of folders for any .dll (.exes are being excluded at the moment), and uses Assembly.LoadFrom to load the dll if it's AssemblyName cannot be found in the set of assemblies already loaded into the AppDomain (this is implemented inefficiently, but it can be optimized later):

void PreLoad(IEnumerable<string> paths)
{
  foreach(path p in paths)
  {
    PreLoad(p);
  }
}

void PreLoad(string p)
{
  //all try/catch blocks are elided for brevity
  string[] files = null;

  files = Directory.GetFiles(p, "*.dll", SearchOption.AllDirectories);

  AssemblyName a = null;
  foreach (var s in files)
  {
    a = AssemblyName.GetAssemblyName(s);
    if (!AppDomain.CurrentDomain.GetAssemblies().Any(
        assembly => AssemblyName.ReferenceMatchesDefinition(
        assembly.GetName(), a)))
      Assembly.LoadFrom(s);
  }    
}

LoadFrom是因为我发现,使用Load()可以导致重复加载通过融合组件如果,当它探测它,它没有找到从那里它应该会找到一个被载入。

LoadFrom is used because I've found that using Load() can lead to duplicate assemblies being loaded by Fusion if, when it probes for it, it doesn't find one loaded from where it expects to find it.

因此​​,考虑到这地方,我现在所要做的是让在precedence顺序列表(最高到最低),那融合是将要使用时,它会搜索程序集的搜索路径。然后,我可以通过这些简单的迭代。

So, with this in place, all I now have to do is to get a list in precedence order (highest to lowest) of the search paths that Fusion is going to be using when it searches for an assembly. Then I can simply iterate through them.

GAC是无关紧要的这个,我不感兴趣的融合可能会使用任何环境驱动的固定路径 - 只有那些能够从包含组件EX pressly部署的应用程序在AppDomain中收集路径

The GAC is irrelevant for this, and I'm not interested in any environment-driven fixed paths that Fusion might use - only those paths that can be gleaned from the AppDomain which contain assemblies expressly deployed for the app.

我的第一本只是用AppDomain.BaseDirectory的迭代。这适用于服务,表单应用程序和控制台应用程序。

My first iteration of this simply used AppDomain.BaseDirectory. This works for services, form apps and console apps.

它不适合一个Asp.Net网站但是,工作,因为至少有两个主要地点 - 在AppDomain.DynamicDirectory(其中Asp.Net放置它的动态生成的页面类和任何组件的ASPX页面code参考文献),然后该网站的Bin文件夹。 - 可从AppDomain.SetupInformation.PrivateBinPath属性被发现

It doesn't work for an Asp.Net website, however, since there are at least two main locations - the AppDomain.DynamicDirectory (where Asp.Net places it's dynamically generated page classes and any assemblies that the Aspx page code references), and then the site's Bin folder - which can be discovered from the AppDomain.SetupInformation.PrivateBinPath property.

所以,我现在有工作code的最基本类型的应用程序现在(SQL服务器托管的应用程序域是另一回事,因为文件系统虚拟化) - 但我碰到一个有趣的问题,一个前两天在哪里此次来到code根本不起作用:NUnit的测试运行

So I now have working code for the most basic types of apps now (Sql Server-hosted AppDomains are another story since the filesystem is virtualised) - but I came across an interesting issue a couple of days ago where this code simply doesn't work: the nUnit test runner.

此同时使用卷影复制(所以我的算法将需要发现和从影复制投递文件夹加载它们,而不是从bin文件夹),并设置了PrivateBinPath作为相对于基本目录。

This uses both Shadow Copying (so my algorithm would need to be discovering and loading them from the shadow-copy drop folder, not from the bin folder) and it sets up the PrivateBinPath as being relative to the base directory.

和也有,我可能还没有考虑其他托管方案的负载;但它必须是有效的,否则融合会呛加载的程序集。

And of course there are loads of other hosting scenarios that I probably haven't considered; but which must be valid because otherwise Fusion would choke on loading the assemblies.

我想停下来的感觉四周,介绍黑客在破解,以适应这些新的情况下,他们突然出现 - 我想要的,给定一个AppDomain和设置信息,产生的文件夹,我应该扫描在此列表的功能订货到取货所有将要加载的DLL文件;不管如何AppDomain中的设置。如果聚变能看到它们都一样的,那么我应该code。

I want to stop feeling around and introducing hack upon hack to accommodate these new scenarios as they crop up - what I want is, given an AppDomain and its setup information, the ability to produce this list of Folders that I should scan in order to pick up all the DLLs that are going to be loaded; regardless of how the AppDomain is setup. If Fusion can see them as all the same, then so should my code.

当然,我可能要改变算法,如果净改变它的内部 - 这只是一个十字架,我会要承担。同样,我很乐意考虑SQL Server和其它任何类似的环境作为边缘的情况下仍然存在不支持现在。

Of course, I might have to alter the algorithm if .Net changes its internals - that's just a cross I'll have to bear. Equally, I'm happy to consider SQL Server and any other similar environments as edge-cases that remain unsupported for now.

任何想法!?

推荐答案

现在我已经能够得到的东西更接近最终解决方案,但它仍然不能正确处理私人bin路径。我已经取代我的previously现场code这点,也解决了一些讨厌的运行时错误我已经进入讨价还价(C#的code引用了太多的DLL动态编译)

I have now been able to get something much closer to a final solution, except it's still not processing the private bin path correctly. I have replaced my previously live code with this and have also solved a few nasty runtime bugs I've had into the bargain (dynamic compilation of C# code referencing far too many dlls).

金科玉律,因为我发现已经是始终使用负载情况下< /一>,而不是LoadFrom上下文,由于负载情况下永远是放在第一位进行自然绑定时净看起来。因此,如果你使用的LoadFrom上下文,你只会得到一击,如果你确实来自同一个地方,它会自然地将其绑定从装载它 - 这并不容易。

The golden rule I've since discovered is always use the load context, not the LoadFrom context, since the Load context will always be the first place .Net looks when performing a natural bind. Therefore, if you use the LoadFrom context, you will only get a hit if you actually load it from the same place that it would naturally bind it from - which isn't always easy.

该解决方案既适用Web应用程序,同时考虑到bin文件夹的区别与标准的应用程序。它可以很容易地扩展,以适应 PrivateBinPath 的问题,一旦我能够得到完全可靠的手柄是​​怎么读(!)

This solution works both for web applications, taking into account the bin folder difference versus 'standard' apps. It can easily be extended to accommodate the PrivateBinPath problem, once I can get a reliable handle on exactly how it is read(!)

private static IEnumerable<string> GetBinFolders()
{
  //TODO: The AppDomain.CurrentDomain.BaseDirectory usage is not correct in 
  //some cases. Need to consider PrivateBinPath too
  List<string> toReturn = new List<string>();
  //slightly dirty - needs reference to System.Web.  Could always do it really
  //nasty instead and bind the property by reflection!
  if (HttpContext.Current != null)
  {
    toReturn.Add(HttpRuntime.BinDirectory);
  }
  else
  {
    //TODO: as before, this is where the PBP would be handled.
    toReturn.Add(AppDomain.CurrentDomain.BaseDirectory);
  }

  return toReturn;
}

private static void PreLoadDeployedAssemblies()
{
  foreach(var path in GetBinFolders())
  {
    PreLoadAssembliesFromPath(path);
  }
}

private static void PreLoadAssembliesFromPath(string p)
{
  //S.O. NOTE: ELIDED - ALL EXCEPTION HANDLING FOR BREVITY

  //get all .dll files from the specified path and load the lot
  FileInfo[] files = null;
  //you might not want recursion - handy for localised assemblies 
  //though especially.
  files = new DirectoryInfo(p).GetFiles("*.dll", 
      SearchOption.AllDirectories);

  AssemblyName a = null;
  string s = null;
  foreach (var fi in files)
  {
    s = fi.FullName;
    //now get the name of the assembly you've found, without loading it
    //though (assuming .Net 2+ of course).
    a = AssemblyName.GetAssemblyName(s);
    //sanity check - make sure we don't already have an assembly loaded
    //that, if this assembly name was passed to the loaded, would actually
    //be resolved as that assembly.  Might be unnecessary - but makes me
    //happy :)
    if (!AppDomain.CurrentDomain.GetAssemblies().Any(assembly => 
      AssemblyName.ReferenceMatchesDefinition(a, assembly.GetName())))
    {
      //crucial - USE THE ASSEMBLY NAME.
      //in a web app, this assembly will automatically be bound from the 
      //Asp.Net Temporary folder from where the site actually runs.
      Assembly.Load(a);
    }
  }
}

首先,我们必须用于检索我们的选择应用程序文件夹的方法。这些是其中用户部署的组件将已部署的位置。这是一个IEnumerable的 PrivateBinPath 边缘的情况下,因为(它可以是一系列的位置),但实际上它只是曾经一个在那一刻文件夹:

First we have the method used to retrieve our chosen 'app folders'. These are the places where the user-deployed assemblies will have been deployed. It's an IEnumerable because of the PrivateBinPath edge case (it can be a series of locations), but in practise it's only ever one folder at the moment:

下一个方法是 preLoadDeployedAssemblies(),这将会做什么(在这里它的上市为私有静态 - 在我的$ C C本$取自具有公共端点,将始终触发此code做任何事情的第一次之前,要运行一个更大的静态类

The next method is PreLoadDeployedAssemblies(), which gets called before doing anything (here it's listed as private static - in my code this is taken from a much larger static class that has public endpoints that will always trigger this code to be run before doing anything for the first time.

最后,还有肉和骨头。这里最重要的事情是把一个部件文件和的得到它的程序集名称的,然后您再传递到的Assembly.Load(的AssemblyName) - 不要用 LoadFrom

Finally there's the meat and bones. The most important thing here is to take an assembly file and get it's assembly name, which you then pass to Assembly.Load(AssemblyName) - and not to use LoadFrom.

我previously认为 LoadFrom 更可靠,你不得不手动去发现Web应用程序临时Asp.Net文件夹。你不知道。你所知道的是装配的,你知道一定要加载的名字 - 并把它传递给的Assembly.Load 。毕竟,这是几乎什么。NET的参考加载程序做的:)

I previously thought that LoadFrom was more reliable, and that you had to manually go and find the temporary Asp.Net folder in web apps. You don't. All you have to is know the name of an assembly that you know should definitely be loaded - and pass it to Assembly.Load. After all, that's practically what .Net's reference loading routines do :)

同样,这种方法可以很好的和自定义的程序集探测被挂在 AppDomain.AssemblyResolve 事件以及实现:扩展应用程序的bin文件夹任何插件容器文件夹,你可以必须使他们获得扫描。你已经处理了 AssemblyResolve 事件无论如何,以确保他们在正常探测失败被装载有机会,所以一切都像以前一样工作。

Equally, this approach works nicely with custom assembly probing implemented by hanging off the AppDomain.AssemblyResolve event as well: Extend the app's bin folders to any plugin container folders you may have so that they get scanned. Chances are you've already handled the AssemblyResolve event anyway to ensure they get loaded when the normal probing fails, so everything works as before.