XSLT存档  

不及格的程序员-八神

 查看分类:  ASP.NET XML/XSLT JavaScripT   我的MSN空间Blog

在.NET开发中,程序集重定向(Assembly Redirect)通常用于解决程序集版本冲突的问题。当一个应用程序依赖于特定版本的程序集,但运行时环境中安装了不同版本的程序集时,就可能出现版本冲突。通过使用程序集重定向,可以指定应用程序应该使用哪个版本的程序集。

程序集重定向在.NET应用程序的配置文件(例如app.config或web.config)中定义。通过在配置文件中添加相应的节(section),可以指定目标应用程序期望的程序集版本,以及在运行时应该使用的实际程序集版本。

下面是一个示例app.config配置文件中的程序集重定向示例:

xml复制代码
  <configuration>
  <runtime>
  <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
  <dependentAssembly>
  <assemblyIdentity name="AssemblyName" publicKeyToken="32ab4ba45e6a835c" culture="neutral" />
  <bindingRedirect oldVersion="1.0.0.0-2.0.0.0" newVersion="3.0.0.0" />
  </dependentAssembly>
  </assemblyBinding>
  </runtime>
  </configuration>

在上面的示例中,assemblyBinding元素定义了程序集绑定信息。dependentAssembly元素指定了需要重定向的程序集,其中name属性指定了程序集的名称,publicKeyTokenculture属性用于识别程序集的标识信息。

bindingRedirect元素指定了期望的程序集版本范围(oldVersion)和实际使用的程序集版本(newVersion)。当应用程序尝试加载旧版本范围内的程序集时,将会自动绑定到指定的新版本。

要使用程序集重定向,只需将上述配置添加到应用程序的配置文件中,然后重新编译和运行应用程序。当应用程序运行时,它将自动根据配置文件中的重定向信息加载正确的程序集版本。

请注意,在.NET Core和.NET 5及以上版本中,app.config文件已被废弃,而使用项目文件(如.csproj.fsproj)中的内容进行配置。在新的项目类型中,可以通过编辑项目文件来添加程序集重定向的配置。


重定向程序集版本

本文内容

  1. 使用发行者策略重定向程序集版本
  2. 跳过发行者策略
  3. 在应用程序级重定向程序集版本
  4. 在计算机级重定向程序集版本

更新:2007 年 11 月

针对特定版本的具有强名称的程序集生成 .NET Framework 应用程序时,该应用程序在运行时使用该版本的程序集。但是,有时可能想要应用程序针对较新版本的程序集运行。应用程序配置文件、计算机配置文件或发行者策略文件可以将一个版本的程序集重定向到另一个版本。有关公共语言运行库如何使用这些文件来确定要使用哪一程序集版本的详细信息,请参见运行库如何定位程序集。可以使用 .NET Framework 配置工具 (Mscorcfg.msc) 来重定向应用程序级和计算机级的程序集版本,或者可以直接编辑配置文件。

7wd6ex19.alert_note(zh-cn,VS.90).gif说明:

不能重定向不具有强名称的程序集的版本。公共语言运行库忽略不具有强名称的程序集的版本。

使用发行者策略重定向程序集版本

程序集的供应商会通过将发行者策略文件与升级的程序集包括在一起,声明应用程序应使用较新版本的程序集。发行者策略文件位于全局程序集缓存中,它包含程序集重定向设置。

程序集的每个 major.minor 版本都有其自己的发行者策略文件。例如,从版本 1.1.2.222 重定向到版本 1.1.3.000 和从版本 1.1.2.321 重定向到版本 1.1.3.000 都会进入相同的文件。但是,从版本 2.0.0.999 重定向到版本 3.0.0.000 就会进入不同的文件。

如果发行者策略文件存在,那么在检查程序集的清单和应用程序配置文件之后,运行库就会检查该文件。只有在新程序集与正在被重定向的程序集向后兼容时,供应商才能使用发行者策略。

可以通过在应用程序配置文件中指定设置,来跳过发行者策略。

跳过发行者策略

即使是自称向后兼容的新版本程序集,也会中断应用程序。在发生这种情况时,可以在应用程序配置文件中使用下列设置,以使运行库跳过发行者策略:

<publisherPolicy apply="no" />

跳过发行者策略,以便使应用程序对用户保持正常运行,但要确保向程序集的供应商报告该问题。一旦程序集有发行者策略,供应商应确保程序集能够向后兼容,并且确保客户端可以尽可能地多使用新版本。

在应用程序级重定向程序集版本

假设程序集的供应商发布了一个程序集新版本供您的应用程序使用,但不提供发行者策略,因为该供应商不要保证新程序集与原始版本向后兼容。可以在应用程序的配置文件中放置程序集绑定信息,来指定您的应用程序使用新版本程序集。

在计算机级重定向程序集版本

可能有一些不常见的情况,即,计算机管理员希望计算机上的所有应用程序都使用特定版本的程序集。例如,您可能希望每个应用程序都使用特定的程序集版本,因为它能堵住安全漏洞。如果程序集在计算机的配置文件中重定向,那么使用旧版本的所有应用程序都将使用新版本。计算机配置文件会重写应用程序配置文件和发行者策略。

在配置文件中指定程序集绑定

应用程序配置文件、计算机配置文件和发行者策略文件都使用相同的 XML 架构来处理程序集重定向。

程序集绑定

为程序集指定信息的方法是将每个程序集的信息放在 <dependentAssembly> 元素内。<assemblyIdentity> 元素包含标识程序集的信息。在配置文件中可以有多个 <dependentAssembly> 元素,但在每个 <dependentAssembly> 元素内必须正好有一个 <assemblyIdentity> 元素。

若要绑定程序集,必须在 <assemblyBinding> 标记中用 xmlns 属性指定字符串“urn:schemas-microsoft-com:asm.v1”。

指定发行者策略

若要使运行库跳过特定程序集的发行者策略,请将 <publisherPolicy> 元素置于 <dependentAssembly> 元素内。若要使运行库跳过应用程序使用的所有程序集的发行者策略,请将该设置置于 <assemblyBinding> 元素内。还可以使用 .NET Framework 配置工具 (Mscorcfg.msc) 来跳过发行者策略。

apply 属性的默认设置为 yes。将 apply 属性设置为 no 会重写任何以前的 yes 设置。例如,如果 apply 在应用程序级设置为 no,那么将忽略任何程序集特定的 apply 设置,即使它声明该值为 yes,也是如此。因此,no 设置是唯一的有用状态,因为该值更改默认值。

重定向程序集版本

若要将一个版本重定向到另一个版本,请使用 <bindingRedirect> 元素。oldVersion 属性可以指定单个版本,也可以指定多个版本范围。例如,<bindingRedirect oldVersion="1.1.0.0-1.2.0.0" newVersion="2.0.0.0"/> 指定运行库应使用版本 2.0.0.0 代替 1.1.0.0 和 1.2.0.0 之间的程序集版本。

示例

下面的示例说明如何将 myAssembly 的一个版本重定向到另一个版本,并对 mySecondAssembly 关闭发行者策略。

<configuration>
  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <dependentAssembly>
        <assemblyIdentity name="myAssembly"
          publicKeyToken="32ab4ba45e0a69a1"
          culture="en-us" />
        <!-- Assembly versions can be redirected in application, 
          publisher policy, or machine configuration files. -->
        <bindingRedirect oldVersion="1.0.0.0" newVersion="2.0.0.0" />
      </dependentAssembly>
      <dependentAssembly>
      <assemblyIdentity name="mySecondAssembly"
        publicKeyToken="32ab4ba45e0a69a1"
        culture="en-us" />
        <!-- Publisher policy can be set only in the application 
          configuration file. -->
        <publisherPolicy apply="no" />
      </dependentAssembly>
    </assemblyBinding>
  </runtime>
</configuration>

重定向 .NET Framework 程序集绑定

您可以在应用程序配置文件中使用 <assemblyBinding> 元素的 appliesTo 属性,将程序集绑定引用重定向到特定的 .NET Framework 版本。此可选属性使用 .NET Framework 版本号指示其适用的版本。如果没有指定 appliesTo 属性,<assemblyBinding> 元素将适用于 .NET Framework 的所有版本。

appliesTo 属性是在 .NET Framework 1.1 版中引入的;.NET Framework 1.0 版将忽略该属性。这意味着,即使指定了 appliesTo 属性,在使用 .NET Framework 1.0 版时所有的 <assemblyBinding> 元素也都适用。

例如,若要重定向 .NET Framework 1.0 版程序集 Regcode 的程序集绑定,必须在应用程序配置文件中包含下面这段 XML 代码。

<runtime>
  <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1" 
    appliesTo="v1.0.3705">
    <dependentAssembly> 
      <!-- assembly information goes here -->
    </dependentAssembly>
  </assemblyBinding>
</runtime>

<assemblyBinding> 元素是区分顺序的。首先应为任何 .NET Framework 1.0 版程序集输入程序集绑定重定向信息,然后为任何 .NET Framework 1.1 版程序集输入程序集绑定重定向信息。最后,为任何因不使用 appliesTo 属性而适用于所有 .NET Framework 版本的 .NET Framework 程序集重定向输入程序集绑定重定向信息。当重定向中有冲突时,将使用配置文件中第一个匹配的重定向语句。

例如,要将一个引用重定向到 .NET Framework 1.0 版程序集,并将另一个引用重定向到 .NET Framework 1.1 版程序集,可以使用以下伪代码中的模式。

<assemblyBinding xmlns="..." appliesTo="v1.0.3705"> 
  <!—.NET Framework version 1.0 redirects here --> 
</assemblyBinding> 

<assemblyBinding xmlns="..." appliesTo="v1.1.5000"> 
  <!—.NET Framework version 1.1 redirects here --> 
</assemblyBinding> 

<assemblyBinding xmlns="..."> 
  <!-- redirects meant for all versions of the runtime --> 
</assemblyBinding>

C#程序集重定向新版本

 

1. 单个程序集配置重定向

当单个依赖程序集,比如A,依赖程序集B,比如使用B的1.0版本。现在B从1.0升级到了2.0。那么此时可以在程序集A中手动重定向B的版本,这样在程序集A运行后会自动使用更新的2.0版本的程序集B。

 

程序集的版本重定向须写在程序集自己的配置文件中(App.config)。在<configuration> – <runtime> – <assemblyBinding> – <dependentAssembly> 元素下。首先必须需要一个<assemblyIdentity>元素来指定依赖的(引用)程序集。包括它的名称,公钥标记等……。接着就是<bindingRedirect>元素代表重定向信息,使用<bindingRedirect>的oldVersion和newVersion属性来指定新旧版本的信息。

比如如下配置文件代码(应用在程序集A的配置文件:a.exe.config中):

<configuration>
   <runtime>
      <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
       <dependentAssembly>
         <!-- 依赖程序集标识 -->
         <assemblyIdentity name="B"
                           publicKeyToken="39e0cba45e0a8bc2"/>
         <!-- 重定向信息 -->
         <bindingRedirect oldVersion="1.0.0.0"
                          newVersion="2.0.0.0"/>
       </dependentAssembly>
      </assemblyBinding>
   </runtime>
</configuration>

 

 

返回目录

2. 发行者策略程序集

还是上面的实例,如果用标题1的方法的话,每一个引用B旧版本的程序集,都需要手动配置自己的把依赖的版本1的B程序集重定向成版本2的B程序集。显然不是个好的方法。.NET还提供发行者策略(Publisher Policy)。其实就是把针对某个程序集的配置直接跟随程序集编译,然后在部署时,程序集和发行者策略都会存储在GAC里,这样任何引用该程序集的对象都会受到影响,而不用一个一个慢慢配置了。

创建开发者策略程序集需要使用al.exe的/link参数来制定一个发行者策略文件。具体可以参考MSDN:http://msdn.microsoft.com/zh-cn/library/dz32563a.aspx

 

接下来还有一个问题,既然发行者策略程序集创建成功,那么所有使用它的程序集都会根据发行者策略而受到影响,某些时候程序集不需要受到发行者策略影响。则需要在<assemblyBinding>元素下加入,<publisherPolicy>元素,用apply属性来控制是否应用发行者策略配置。
如下代码:

<configuration>
   <runtime>
       <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
           <!-- 不应用发行者策略配置 -->
           <publisherPolicy apply="no"/>
       </assemblyBinding>
   </runtime>
</configuration>

 

 

返回目录

3. 开发测试中频繁版本更新

第三个情景就是在程序集的开发测试中,往往频繁更新版本,此时则需要设置发行者策略配置并将程序集安装到GAC内。那么可以将程序集所在目录设置到Windows环境变量DEVPATH中。然后在machine.config(注意是machine.config,不是app.config)中打开开发模式(通过<developmentMode>节点),这样CLR在寻找程序集时不仅要浏览GAC,还是探索DEVPATH中的目录,程序集就不必总安装到GAC中了。

在machine.config中加入:

<configuration>
    <runtime>
        <developmentMode developerInstallation="true"/>
    </runtime>
</configuration>

 


程序集引用不匹配0x80131040问题探究

 

进行插件式编程的时候,经常性地弹出这么个东西找到的程序集清单定义与程序集引用不匹配。 (异常来自 HRESULT:0x80131040),往往这种问题特别难以解决,搞定了一个还要出另外一个。得研究一下怎么处理。

引用不匹配

这里提示需要加载一个4.2.0.0版本的dll,我先看看文件夹下面有没有对应的dll,查看文件dll的详细信息。

这个版本号4.6.27818.1和4.2.0.0也差的有点太远了吧,是这个问题?其实不是的,这个地方显示的版本和程序集的版本不是一回事。

程序集版本

.NET程序有很多版本的说法,官方对这个有解释,通过文件管理器获得的版本是AssemblyFileVersion,而程序集加载器定位的版本使用的是AssemblyVersion,这两个东西完全不是一回事。通过右键,我们看不到AssemblyVersion,比较简单的方式,可以通过Powershell脚本来查看程序集版本。

ls *.dll -r |
    ForEach-Object {
        try {
            $_ | Add-Member NoteProperty FileVersion ($_.VersionInfo.FileVersion)
            $_ | Add-Member NoteProperty AssemblyVersion (
                [Reflection.AssemblyName]::GetAssemblyName($_.FullName).Version
            )
        } catch {}
        $_
    } |
    Select-Object Name,FileVersion,AssemblyVersion

可以看到,我这边的程序集是4.2.0.1版本的,不是4.2.0.0版本的,因此,程序集不能正常加载。

我自己的项目是使用nuget进行包管理的,引用的包的版本号是4.5.3(又多一个版本...),程序集版本是4.2.0.1。我找遍了整个项目,都没有找到我dll项目中关于4.2.0.0版本的引用,苦思良久,打盹的时候忽然想起来,是不是那个exe的问题?

绑定重定向

我的主exe程序是使用.NET Framework 4.6.1进行编译,然后单独编译dll作为插件放入一个文件夹,由exe程序进行加载。那有可能是exe引用了4.2.0.0这个版本,或者是其他dll插件引用了这个版本,造成版本不兼容。

这种情况可以有很多种解决方案,这篇文章写的非常详细,推荐读一读。而我这里使用了最简单也是作者比较推荐的办法,程序集引用的绑定重定向。

使用这个方法有一个前提,你需要完全了解其他程序引用这个程序版本的时候不会出问题,一般小版本号的变动,是相对比较安全的。

一直以来,我写.NET Framework程序貌似就很少有这种问题,是因为微软会自动给程序设置重定向,我们可以通过在项目上右键,点选自动生成绑定重定向。

之后,会出现一个app.config文件,它会在生成程序的时候,变成程序名称.dll.config的形式,里面大概是这个样子的:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <dependentAssembly>
        <assemblyIdentity name="System.Threading.Tasks.Extensions" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
        <bindingRedirect oldVersion="0.0.0.0-4.2.0.1" newVersion="4.2.0.1" />
      </dependentAssembly>
    </assemblyBinding>
  </runtime>
</configuration>

可以发现,这个东西对我们引用的版本内容进行了限制,强制某个范围内的版本重定向为一个固定的版本号。不过,虽然dll中已经有这个内容,但是exe不会理会它的,它只会看自己主程序的.exe.config文件。所以我们需要做的,就是把这一个部分内容搬到主程序的.exe.config中。

修改后重新运行,然后有又提示找不到System.Buffers.dll了,如法炮制,问题解决。

Fusion Log

有时候,通过这个方法找下去,处理完了,也不一定能够解决,因为这个错误提示的是“未能加载文件或程序集或它的某一个依赖项。”,也可能是某个引用的依赖项出了问题,这样就不是很好找了。

好在.NET提供了一个程序集绑定日志的工具,可以帮助我们查看绑定的问题。一般情况下,这个东西是关闭的,系统会提示:

警告: 程序集绑定日志记录被关闭。
要启用程序集绑定失败日志记录,请将注册表值 [HKLM\Software\Microsoft\Fusion!EnableLog] (DWORD)设置为 1。
注意: 会有一些与程序集绑定失败日志记录关联的性能损失。
要关闭此功能,请移除注册表值 [HKLM\Software\Microsoft\Fusion!EnableLog]。

调试的话,可以打开这个注册表键值。或者简单点,直接使用Fuslogvw.exe(程序集绑定日志查看器),用管理员账号启动,在设置中设置好记录的日志信息,然后就可以记录了,详细的使用方法,见这里。就能看到详细的信息了,可以帮助我们深入分析内部绑定的问题。(用完记得关,有性能损失。)

=== 预绑定状态信息 ===
日志: DisplayName = System.Threading.Tasks.Extensions, Version=4.2.0.1, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51
 (Fully-specified)
日志: Appbase = file:///C:/Temp/360zip$Temp/360$0/
日志: 初始 PrivatePath = NULL
调用程序集: DependencyWalker, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null。
===
日志: 此绑定从 default 加载上下文开始。
日志: 未找到应用程序配置文件。
日志: 使用主机配置文件: 
日志: 使用 C:\Windows\Microsoft.NET\Framework\v4.0.30319\config\machine.config 的计算机配置文件。
日志: 策略后引用: System.Threading.Tasks.Extensions, Version=4.2.0.1, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51
日志: 相同的绑定已出现过,因 hr = 0x80070002 而失败。

Dependency Walker

说到dll的引用,就有必要提一提非常有名的一个工具Dependency Walker,它可以查看PE文件的引用情况,但是这个程序很久没有更新了,对.NET很不友好。我找了一下,发现几个替代:

  1. Dependencies

这个工具可以认为是Dependency Walker的升级版,可以查看PE文件的引用信息,对.NET也可以支持,不过看的信息太少了,也无法显示缺失的情况。

  1. DependencyWalker.Net

这个工具是专门为.NET设计的,可以查看程序集的引用情况,我删除了我插件的几个dll,然后看就是这个样子,一目了然,版本号也非常清楚。

通过这些工具,可以帮我们找一找到底是dll的问题,也许会对“试图加载格式不正确的程序。”、“引用不匹配。”等引用相关问题有所帮助。

这些工具处理的大多是静态引用,对于使用动态引用的,一般是不支持的。

总结

插件式编程极大增强了程序的拓展能力,不过,在处理程序集引用的时候,需要非常小心不同插件带来的引用问题。程序设计的时候,可以使用不同文件夹隔离不同的插件dll,并通过一些技巧来加载(可以查看之前的文章)和隔离,这样,就可以避免出现不匹配的错误了。

参考资料


Some problems in programming seem to stay and bother us forever. Much like cockroaches, these problems resist technological advancements and increasing human knowledge. One such problem is the infamous DLL Hell (and variations of it).

The original DLL hell issue was this: Several applications use a shared DLL file. Then, one of the applications updated the DLL file and now the other applications no longer work. In .NET, this issue is solved. We will usually ship DLL files separately for each application. Alternatively, we can use the GAC for shared DLL’s, which supports versioning. This means we can store different versions of the same DLL and the different applications will load their intended version.

In the modern world, we are dependent on dozens of libraries. These in turn, depend on dozens more and we end up with hundreds or thousands of dependencies. So now the problem arises for a single application. What to do when several of its projects depend on different version of the same assembly?

Here’s an example: project A might use log4net V1.1 and project B uses log4net V1.2. Both DLL files are copied to the output folder, but there can be only one log4net.dll file. As a result, our application will fail at runtime when trying to load the version that wasn’t copied.

Here’s another scenario: We reference project A with NuGet which references System.Net.Http v4.5. We also reference project B with NuGet which references System.Net.Http v4.0. This phenomenon is knows as NuGet Hell. The result is the same and our application will fail with:

Could not load file or assembly or one of its dependencies. The located assemly’s manifest definition does not match the assembly reference.

If you ran into this exception, you are in the right place.

Luckily, Microsoft put a lot of thought into this issue and there are a lot of things we can do about it.

If possible, resolve to a single reference version

The best solution is to remove the need for different reference versions. Sometimes it’s as easy as going to NuGet manager and changing the versions. This is widely referred as DLL Hell and isn’t in the scope of this article. Some resources to help with DLL Hell are: [1] , [2] , [3]

Sometimes tough, you can’t depend on a single reference version due to all kind of reasons. The references might be referenced by a 3rd party that you can’t edit or you might have limitations like .NET framework target. If you’re in one of those times, this article is for you.

Use a single reference versions or load versions side-by-side

First of all, there’s an important decision to make: Do we want to force our projects to use the same reference version? Or do we want to load different versions side by side and use both?

The CLR does supports side-by-side loading. which means multiple versions of the same assembly are loaded and act as different modules. This can be problematic. Each assembly version doesn’t expect there’s another instance of it loaded. The different versions might fight for resources or get in each other’s way somehow .

Having said that, you can’t always use a single version, since the referencing projects might rely on features that exist only in their respective referenced versions.

In solution #1, we will how to force a specific version with Binding Redirects. Solution #2 through #4 will show various methods to achieve side-by-side loading.

If possible, prefer Binding Redirect to side-by-side loading

Solution 1: Use a single assembly version with Binding Redirect

In our log4net example, project A uses log4net 1.2.11 and project B uses log4net 1.2.15. We’ve noticed from the exception screenshot above that log4net v1.2.15 fails to load. When the debugger breaks on the Exception, we can open the Modules window (Debug -> Windows -> Modules) and see which version is actually loaded (If any)

The Modules shows that log4net 1.02.11 is loaded.

We can then force our project to use the loaded assembly with binding redirect . To make this work, we’ll need to add the following code to App.config of the assembly referencing log4net 1.02.15. If App.config doesn’t exist, create it.

<pre class="lang:default mark:6-13 decode:true"><?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <startup> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" />
    </startup>
  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <dependentAssembly>
        <assemblyIdentity name="log4net" publicKeyToken="669e0ddf0bb1aa2a" culture="neutral" />
        <bindingRedirect oldVersion="0.0.0.0-5.0.0.0" newVersion="1.2.11" />
      </dependentAssembly>
    </assemblyBinding>
  </runtime>
</configuration>

Note that the runtime section is the one to be added.

We basically tell the runtime that whenever log4net in versions 0.0 – 5.0 is required, use version 1.2.11 instead.

Strong names and the GAC

As mentioned, the CLR program has the ability to load different versions of the same assembly. It’s easier to do that by signing the assembly with a strong name. This creates a unique identity to the assembly and allows to use some elegant solutions to work with different versions.

When using strong-named assemblies, they can be registered to the Global Assembly Cache (GAC). This is a sharing mechanism for common assemblies. It’s also very convenient for storing and loading different versions.

Strong name signing has some disadvantages as well.

We’ll see how to use the GAC to load different assemblies side-by-side later on in Solution 4. If you can’t or don’t want to sign the references with a strong name you can use AssemblyResolve.

Solution 2: Override AssemblyResolve for side-by-side loading (No need for strong names)

Let’s say we have 2 assemblies called Lib1 with different versions 1.0 and 1.1, which are referenced by different projects.

Let’s set CopyLocal to False for both references. We’ll need to copy the assemblies to different subfolders in the output folder, say to V10\Lib1.dll and V11\Lib1.dll. It’s important to have the DLL files in the output folder since this is the folder that will be deployed.

Copying the .dll files can be done with a post-build event (see explanation and some examples ). The output folder should look like this:

Since the assemblies aren’t in the output folder (they are in sub-folders), the assemblies will fail to load. Let’s register to AssemblyResolve event, which fires when an assembly’s failed to load, like this:

static void Main(string[] args)
{
    AppDomain.CurrentDomain.AssemblyResolve += (sender, resolveArgs) =>
    {
        string assemblyInfo = resolveArgs.Name;// e.g "Lib1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"
        var parts = assemblyInfo.Split(',');
        string name = parts[0];
        var version = Version.Parse(parts[1].Split('=')[1]);
        string fullName;
        if (name == "Lib1" && version.Major == 1 && version.Minor == 0)
        {
            fullName = new FileInfo(@"V10\Lib1.dll").FullName;
        }
        else if (name == "Lib1" && version.Major == 1 && version.Minor == 1)
        {
            fullName = new FileInfo(@"V11\Lib1.dll").FullName;
        }
        else
        {
            return null;
        }

        return Assembly.LoadFile(fullName);
    };

On each failed assembly load, we will parse the required version and manually load the assembly from the subfolders.

This is the only solution I was able to implement to load multiple version of an unsigned assembly. But, if we are working with strongly named assemblies, there are more elegant solutions.

Solution 3: Copy assemblies to different folders and use <codebase> to load them side-by-side (Requires strong names)

Let’s take a similar scenario. Suppose we have 2 assemblies called StrongNameLib with versions 1.0 and 1.1, which are referenced by different projects.

We’ll need to set CopyLocal to False for both of them and copy them to subfolders in the output folder with a post-build event. So far, exactly like in the previous solution.

We’ll need to find out the public key token, which is easy enough . Now, we can edit App.config (or create it, if it doesn’t exist) and add <codebase> nodes in the following way:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
      <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
        <dependentAssembly>
          <assemblyIdentity name="StrongNameLib" culture="neutral" publicKeyToken="6a770f8bdf3d476a" />
          <codeBase version="1.0.0.0" href="StrongNameLibV10/StrongNameLib.dll"/>
          <codeBase version="1.1.0.0" href="StrongNameLibV11/StrongNameLib.dll"/>
        </dependentAssembly>
      </assemblyBinding>
  </runtime>
</configuration>

We’re basically telling the CLR where to go to resolve each version.

The path can also be an absolute path in the format “FILE://c:/Lib/MyLib.dll”.

Resolving with <codebase> like this only works with strongly typed assemblies. When the assembly isn’t strongly typed, the version property is ignored and the first <codebase> node is taken.

Solution 4: Install assemblies to the Global Assembly Cache (GAC)

This is the recommended solution for common libraries since the GAC will share libraries across applications. You can a install both assemblies to the GAC with gacutil.exe. It’s a matter of a single command prompt execution. For example:

<pre class="theme:cisco-router lang:default decode:true">gacutil.exe -i "c:\Dev\Project\Debug\MyLib.dll"

Read more on using gacutil in this tutorial . Note that there’s a different gacutil for each .NET version.

Once installed, set the references` CopyLocal property to False, since there’s no need for them to be in the output folder. This is it, we’re done. According to the runtime lookup order , the CLR will look in the GAC before checking the output folder. Since both assemblies are installed in the GAC, the runtime will find and load the correct versions.

The only caveat here is that unlike in the other solutions, where the output folder could be deployed as is, you’ll have to take care to install the assemblies in the GAC of the deployed computer. The installation could be included in the installer, installed manually on the cloud or added to the continuous deployment process.

Solution 5: Repack your libraries into a new assembly

Let’s assume you have the following conflict situation:

Version conflict with Newtonsoft.Json

If you want to load both version of Newtonsoft.Json side by side, you can use ILMerge or il-repack to pack one of the branches into a new assembly (I recommend il-repack out of the two). Those solutions will take several input assembly and merge them into a new assembly. In this case, you can take Library A + Newtonsoft.Json 9 and merge into a new assembly called XYZ.dll. Once done, you can reference XYZ.dll and Newtonsoft.Json 8 without any conflict. Alternatively, you can pack Newtonsoft.Json 8 into a new assembly QWE.dll and then reference Newtonsoft.Json 9 and QWE.dll without any conflicts. This solution doesn’t require strongly-named assemblies.

Summary and resources

We saw the new version of DLL Hell and a lot of ways to deal with it. As mentioned, these solutions should be used with caution.

Here are more resources on the subject:

 

posted on 2023-08-03 13:50  不及格的程序员-八神  阅读(233)  评论(0编辑  收藏  举报