哪里可以在类库中处理AssemblyResolve事件? [英] Where to handle AssemblyResolve event in a class library?

查看:208
本文介绍了哪里可以在类库中处理AssemblyResolve事件?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我需要从一个类库动态地解析汇编引用到另一个类库。类库正在从PowerShell脚本加载,因此在可执行文件中查找依赖程序集的默认.NET行为直接失败,因为可执行文件是PowerShell本身。



更详细的说明:


$ b $我有两个实用程序库:一个核心的,另一个执行一些非常具体的解析任务。我想在PowerShell脚本中动态加载它们,而不必在GAC 中安装它们。第二个图书馆依赖于第一个图书馆。在VS解决方案中,解析库具有对核心库的项目引用,其中 Copy Local = true 。 / p>

使用(PowerShell)后,我可以从解析库输出bin(/ Debug | / Release)文件夹中加载和使用这两个库:

  [Reflection.Assembly] :: LoadFile(C:\ ... thefile.dll)
/ pre>

但是,无论何时调用解析(依赖)库中调用某些内核库的方法,都无法解析核心程序集。这是...令人沮丧的...因为文件在同一个文件夹。一个或两个都有强名称键没有区别。



我的解决方法是处理 AssemblyResolve 事件。棘手的事情是找出把它放到一个类库中的位置,因为在一个可执行文件之前,没有一个入口点总是执行任何东西,比如$ code> Main()方法(请参阅是在c#中有一个类库的Application_Start)。



现在,我已经使用一个静态构造函数静态的 Resolver $ c> AssemblyResolve ,然后在每个解析类中都有一个静态构造函数,它引用静态解析器类,强制解析器类的静态构造函数执行。结果是AssemblyResolve事件被附加一次,并使用通用的中央代码进行处理。所以它的工作。但是我讨厌不得不为我所有的解析类添加一个时髦的静态构造函数。



有更好的方法来处理吗?

解决方案

我想出了一个遵循消费者应该解决模式的解决方案,适用于PowerShell和正常的.NET应用程序消费者。



想法:




  • 使用内部的AssemblyResolve事件处理程序和将处理程序附加到AppDomain.CurrentDomain.AssemblyResolve事件的静态构造函数。 (到目前为止这是很熟悉的。)

  • 而不是从同一个或另一个类库调用 Resolver 类,它直接由消费者。当PowerShell是消费者时,从PowerShell调用 Resolver

  • 这是有效的,因为任何消费者(包括PowerShell)与它加载的程序集具有相同的 CurrentDomain 。因此,即使事件处理程序附加在某些动态加载的程序集中,当主消费应用程序中的程序集解析失败时,它仍将被调用。



我的版本 Resolver 具有:




  • 静态属性 AssemblyDirectory 可用于可选地设置要搜索的目录。如果留空,它将使用从 Assembly.GetCallingAssembly()中找到的目录。位置

  • 一个虚拟的 Register()方法,除了确保静态构造函数被调用之外什么都不做。



PowerShell代码:

 #首先,使用Resolver类加载程序集。我实际上把它放在同一个
#的核心库中,但它真的不重要。甚至可以使用
#在使用Add-Type命令行构建的动态编译程序集
[Reflection.Assembly] :: LoadFile([具有Resolver类的程序集.dll))

#调用Register方法强制静态构造函数运行,如果它还没有
[mycorp.mylib.Resolver] :: Register()

$ sourcedir =C:\foo\bar\..some random dir
#设置搜索未解析程序集的路径
[mycorp.mylib.Resolver] :: AssemblyDirectory = $ sourcedir

#加载依赖程序集
[Reflection.Assembly] :: LoadFile($ sourcedir\myparser.dll)

#创建并使用对象从依赖组装。当核心组件
#首先解析失败时,Resolver的处理程序将自动调用,
#,一切都是peachy
$ encoder = New-Object mycorp.mylib.anamespace.aclass
$ result = $ encoder.DoStuff($ x,$ y,$ z ... etc)

如果您想知道如何实际处理AssemblyResolve事件,请查看MSDN上的文档: Register-ObjectEvent(关于注册对象事件)





起初我试图直接在PowerShell中构建一个程序集解析器,并使用 Register-ObjectEvent commandlet。这将会起作用,但是对于一个问题:PowerShell不支持返回值的事件处理程序。 AssemblyResolve处理程序返回一个Assembly对象。这几乎是他们的全部目的。


I need to dynamically resolve assembly references from one class library to another. The class libraries are being loaded from a PowerShell script, so the default .NET behaviour of looking for dependent assemblies in the executable directly fails, as the executable is PowerShell itself. How do I make these dependent assembly references resolve / work correctly?

In more detail:

I have two utility libraries: a core one and another one that does some very specific parsing tasks. I want to load them dynamically in a PowerShell script without installing them in the GAC. The second library depends on the first. In the VS solution, the parsing library has a project reference to the core library, with Copy Local = true.

I can load and use both libraries from the parsing library output bin (/Debug|/Release) folder after using (PowerShell here):

[Reflection.Assembly]::LoadFile("C:\...thefile.dll")

However, whenever calling a method in the parsing (dependent) library that calls something from the core library it fails to resolve the core assembly. This is...frustrating...since the files are in the same folder. It makes no difference whether one or both have strong name keys.

My workaround now is to handle the AssemblyResolve event. The tricky thing is figuring out where to put this in a class library, since there's no single entry point that will always execute before anything else like there is in an executable Main() method (see Is there an equivalent of Application_Start for a class library in c#).

For now I've made a static Resolver class with a static constructor that attaches a handler for AssemblyResolve, and then have a static constructor in each of the parsing classes which refers to the static resolver class, forcing the resolver class's static constructor to execute. The result is that the AssemblyResolve event gets attached exactly once and handled with common, central code. So it works. But I hate having to add a funky static constructor to all of my parsing classes.

Is there a better way to handle this?

解决方案

I figured out a solution that follows the "consumer should resolve" pattern, and which works for both PowerShell and normal .NET application consumers.

The idea:

  • Make a class with an internal AssemblyResolve event handler, and a static constructor that attaches the handler to the AppDomain.CurrentDomain.AssemblyResolve event. (So far this is familiar.)
  • Instead of invoking the Resolver class from the same or another class library, invoke it directly by the consumer. When PowerShell is the consumer, invoke the Resolver from PowerShell.
  • This works because any consumer--including PowerShell--has the same CurrentDomain as the assemblies it loads. So even if the event handler is attached in some dynamically loaded assembly, it will still be invoked when a assembly resolve fails in the main consuming application.

My version of Resolver has:

  • A static property AssemblyDirectory that can be used to optionally set the directory to search from. If left blank, it will use the directory found from Assembly.GetCallingAssembly().Location
  • A dummy Register() method which really does nothing except ensure the static constructor has been called.

PowerShell Code:

# 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 )

If you want to know how to actually handle the AssemblyResolve event, check out the documentation on MSDN: AppDomain.AssemblyResolve Event

Regarding Register-ObjectEvent:

At first I tried to build an assembly resolver directly in PowerShell, and register it using the Register-ObjectEvent commandlet. This would work, but for one problem: PowerShell doesn't support event handlers that return a value. AssemblyResolve handlers return an Assembly object. That's almost their whole purpose.

这篇关于哪里可以在类库中处理AssemblyResolve事件?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆