在类库中处理AssemblyResolve事件的位置?
我需要动态地解决从一个类库到另一个类库的程序集引用。 类库是从PowerShell脚本加载的,因此在可执行文件中查找依赖程序集的默认.NET行为会直接失败,因为可执行文件是PowerShell本身。 如何使这些依赖的程序集引用解析/正常工作?
更详细 :
我有两个实用程序库:一个核心库和另一个执行一些非常具体的解析任务的实用程序库。 我想在PowerShell脚本中动态加载它们, 而无需在GAC中安装它们 。 第二个库取决于第一个库。 在VS解决方案中,解析库具有对核心库的项目引用, Copy Local
= true
。
使用后,我可以在解析库输出bin(/ Debug | / Release)文件夹中加载和使用这两个库(PowerShell here):
[Reflection.Assembly]::LoadFile("C:\...thefile.dll")
但是,每当在解析(依赖)库中调用一个从核心库调用某些东西的方法时,它就无法解析核心程序集。 这是令人沮丧的…因为文件在同一个文件夹中。 一个或两个是否具有强名称密钥没有区别。
我现在的解决方法是处理AssemblyResolve
事件。 棘手的是找出将它放在类库中的位置,因为在可执行的Main()
方法之前没有任何单一的入口点总是会先执行其他任何东西(请参阅类库中是否存在等效的Application_Start)在c# )。
现在我已经创建了一个带有静态构造函数的静态Resolver
类,它为AssemblyResolve
附加了一个处理程序,然后在每个解析类中都有一个静态构造函数,它引用了静态解析程序类,强制解析器类的静态构造函数执行。 结果是AssemblyResolve事件只被连接一次,并使用通用的中央代码进行处理。 所以它有效。 但我不想在我的所有解析类中添加一个时髦的静态构造函数。
有没有更好的方法来处理这个?
我找到了一个遵循“消费者应该解决”模式的解决方案,它适用于PowerShell和普通的.NET应用程序使用者。
想法:
- 创建一个具有内部AssemblyResolve事件处理程序的类,以及一个将处理程序附加到AppDomain.CurrentDomain.AssemblyResolve事件的静态构造函数。 (到目前为止,这是熟悉的。)
- 而不是从相同或另一个类库调用
Resolver
类, 而是由使用者直接调用它 。 当PowerShell是使用者时,从PowerShell调用Resolver
。 - 这是有效的,因为任何消费者 – 包括PowerShell – 都与它加载的程序集具有相同的
CurrentDomain
。 因此,即使事件处理程序附加在某个动态加载的程序集中,当主要使用应用程序中的程序集解析失败时,它仍将被调用。
我的Resolver
版本有:
- 一个静态属性
AssemblyDirectory
,可用于选择性地设置要搜索的目录。 如果留空,它将使用从Assembly.GetCallingAssembly().Location
找到的目录 - 一个虚拟的
Register()
方法,除了确保调用静态构造函数之外什么都不做。
PowerShell代码:
# First, load the assembly with the Resolver class. I actually put it in the same # core library, but it really doesn't matter where it is. It could even be by itself # in a dynamically compiled assembly built using the Add-Type commandlet [Reflection.Assembly]::LoadFile("[an assembly that has the Resolver class].dll") # Call the Register method to force the static constructor to run if it hasn't already [mycorp.mylib.Resolver]::Register() $sourcedir = "C:\foo\bar\..some random dir" # Set the path to search for unresolved assemblies [mycorp.mylib.Resolver]::AssemblyDirectory = $sourcedir # Load the dependent assembly [Reflection.Assembly]::LoadFile("$sourcedir\myparser.dll") # Create and use an object from the dependent assembly. When the core assembly # fails at first to resolve, the Resolver's handler is automatically called, # and everything is peachy $encoder = New-Object mycorp.mylib.anamespace.aclass $result = $encoder.DoStuff( $x, $y, $z ... etc )
如果您想知道如何实际处理AssemblyResolve事件,请查看MSDN上的文档: AppDomain.AssemblyResolve Event
关于Register-ObjectEvent
:
起初我尝试直接在PowerShell中构建程序集解析程序,并使用Register-ObjectEvent
命令行开关注册它。 这可行,但是对于一个问题:PowerShell不支持返回值的事件处理程序。 AssemblyResolve处理程序返回一个Assembly对象。 这几乎是他们的全部目的。
您可以使用AppDomain注册事件(请参阅此处 ),但必须在许多地方注册/处理,因为对于每个实例,您无法保证在任何给定的入口点已经进行了注册。
在使用类库而不是库本身的应用程序中处理此问题可能更好。