And*_*tan 29 .net c# reflection assemblies
更新:我现在有了一个解决方案,我对此更加满意,虽然没有解决我提出的所有问题,但它确实让我们明白了.我已经更新了自己的答案以反映这一点.
原始问题
给定App域,Fusion(.Net程序集加载器)将针对给定程序集进行探测,有许多不同的位置.显然,我们认为这个功能是理所当然的,因为探测似乎嵌入在.Net运行时Assembly._nLoad
内部(内部方法似乎是反射加载时的入口点 - 我假设隐式加载可能由同一个底层覆盖算法),作为开发人员,我们似乎无法访问这些搜索路径.
我的问题是我有一个组件可以执行大量动态类型解析,并且需要能够确保在开始工作之前预先加载给定AppDomain的所有用户部署的程序集.是的,它减慢了启动速度 - 但我们从这个组件中获得的好处完全超出了这一点.
我已经编写的基本加载算法如下.它深扫描任何.dll文件(.EXE文件被排除一组文件夹的那一刻),并使用Assembly.LoadFrom如果它的AssemblyName不能在集已经加载到AppDomain中组件可以找到加载DLL(这实现效率低下,但可以在以后进行优化):
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);
}
}
Run Code Online (Sandbox Code Playgroud)
使用LoadFrom是因为我发现使用Load()会导致Fusion加载重复的程序集,如果它在探测它时,它找不到从它希望找到它的地方加载的程序集.
因此,有了这个,我现在要做的就是获得Fusion在搜索程序集时将要使用的搜索路径的优先顺序(从高到低)的列表.然后我可以简单地遍历它们.
GAC与此无关,我对Fusion可能使用的任何环境驱动的固定路径不感兴趣 - 只有那些可以从AppDomain收集的路径,其中包含为应用程序明确部署的程序集.
我的第一次迭代只使用了AppDomain.BaseDirectory.这适用于服务,表单应用程序和控制台应用程序.
但是,它不适用于Asp.Net网站,因为至少有两个主要位置 - AppDomain.DynamicDirectory(其中Asp.Net放置动态生成的页面类和Aspx页面代码引用的任何程序集),以及然后是站点的Bin文件夹 - 可以从AppDomain.SetupInformation.PrivateBinPath属性中找到它.
所以我现在有最基本类型的应用程序的工作代码(自文件系统虚拟化以来,Sql Server托管的AppDomains是另一个故事) - 但是我几天前遇到了一个有趣的问题,这个代码根本不起作用:nUnit测试运行器.
这使用了Shadow Copying(因此我的算法需要从shadow-copy drop文件夹中发现并加载它们,而不是从bin文件夹中加载)并且它将PrivateBinPath设置为相对于基本目录.
当然还有很多其他托管方案,我可能没有考虑过; 但必须有效,否则Fusion会加载装配.
我想停止感觉并引入黑客入侵以适应这些新场景,因为它们突然出现 - 我想要的是,给定AppDomain及其设置信息,能够生成我应扫描的文件夹列表以便选择所有要加载的DLL; 无论AppDomain如何设置.如果Fusion可以看到它们都一样,那么我的代码也应如此.
当然,如果.Net更改其内部结构,我可能不得不改变算法 - 这只是我必须承受的交叉.同样,我很高兴将SQL Server和任何其他类似的环境视为现在仍然不受支持的边缘情况.
有任何想法吗!?
And*_*tan 19
我现在能够得到更接近最终解决方案的东西,除了它仍然没有正确处理私有bin路径.我用这个替换了我之前的实时代码,并且还解决了我讨价还价的一些讨厌的运行时错误(C#代码的动态编译引用了太多的dll).
我发现的黄金法则总是使用加载上下文,而不是LoadFrom上下文,因为Load上下文总是第一个.Net在执行自然绑定时看起来.因此,如果您使用LoadFrom上下文,只有在实际从自然绑定它的地方加载它时才会受到影响 - 这并不总是很容易.
此解决方案适用于Web应用程序,考虑到bin文件夹与"标准"应用程序的区别.PrivateBinPath
一旦我能够准确地掌握它的读取方式(!),它就可以轻松扩展以适应问题
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);
}
}
}
Run Code Online (Sandbox Code Playgroud)
首先,我们有用于检索我们选择的"app文件夹"的方法.这些是已部署用户部署的程序集的位置.这是一个IEnumerable因为的PrivateBinPath
边缘的情况下(这可以是一系列的位置),但实际上它是目前唯一曾经在一个文件夹:
下一个方法是PreLoadDeployedAssemblies()
,在做任何事情之前被调用(这里它被列为private static
- 在我的代码中,这是从一个更大的静态类中获取的,该静态类具有公共端点,它总是会在第一次执行任何操作之前触发此代码.
最后是肉和骨头.这里最重要的是获取一个汇编文件并获取它的汇编名称,然后传递给它Assembly.Load(AssemblyName)
- 而不是使用它LoadFrom
.
我以前认为这LoadFrom
更可靠,你必须手动去网页应用程序中找到临时的Asp.Net文件夹.你没有.您所需要的只是知道应该确定加载的程序集的名称 - 并将其传递给Assembly.Load
.毕竟,这几乎就是.Net的参考加载例程:)
同样,这种方法也适用于通过挂起AppDomain.AssemblyResolve
事件实现的自定义程序集探测:将应用程序的bin文件夹扩展到您可能拥有的任何插件容器文件夹,以便对它们进行扫描.你可能已经处理过这个AssemblyResolve
事件,以确保在正常探测失败时它们被加载,所以一切都像以前一样工作.
归档时间: |
|
查看次数: |
19040 次 |
最近记录: |