如何构建/部署需要多个版本相同程序集的项目?

8
我正在处理一个使用conflict.dll版本6.2的项目,但该项目还使用helper.dll,该文件使用conflict.dll版本5.8。
我可以将6.2和5.8安装到GAC中,但我希望这个项目可以进行xcopy部署。我相信.NET会在应用程序bin目录中搜索程序集,如下所示: \bin\conflict.dll(6.2) \bin\5.8\conflict.dll(5.8)
但此时,如何在项目中添加对两个版本的conflict.dll的引用,然后如何确保旧的conflict.dll部署到\bin\5.8?我需要创建一个构建操作,还是有其他方法?
谢谢。
3个回答

14

经过多个小时的搜寻与咒骂,我找到了一个解决方案,它易于实现并且可靠。

问题 就像所有其他答案指出的一样,必须满足以下所有条件:

  1. 两个版本的 DLL 必须具有相同的名称,否则运行时会抱怨名称与清单不匹配。
  2. 运行时必须能够在搜索路径中找到这两个程序集。
  3. 由于存在破坏性变更,无法进行版本重定向。
  4. 在此示例中,AppDomain.ResolveAssembly 永远不会被调用,因为程序集已经被加载了一次。

解决方案 如下所示,步骤如下:

  1. 在您的解决方案目录下创建一个目录,例如 lib\,其中包含以下层次结构:

    lib\Conflict\v1\Conflict.dll
    lib\Conflict\v2\Conflict.dll

  2. 将以下内容添加到您的 app/web.config 文件中:

<runtime>
  <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
    <dependentAssembly>
      <assemblyIdentity name="Conflict" publicKeyToken="111111111111" />
      <codeBase version="1.0.0.0" href="bin\Conflict\v1\Conflict.dll" />
      <codeBase version="2.0.0.0" href="bin\Conflict\v2\Conflict.dll" />
    </dependentAssembly>
  </assemblyBinding>
</runtime>
  1. 添加一个后构建事件,使用xcopy命令:

    xcopy $(SolutionDir)\lib $(TargetDir) /Y /S

  2. 构建一次,以便复制文件。点击“项目->显示所有文件”。右键单击bin\Conflict,选择包含在项目中(这样可以避免在代码中进行操作)。如果要打包Web应用程序,则必须这样做才能部署文件。

完成!


在第二步中,您要将什么添加到web.config文件中? - Aaronontheweb

3

我认为 .net 会在应用程序 bin 目录中搜索程序集,例如:\bin\conflict.dll (6.2) \bin\5.8\conflict.dll (5.8)

不,这是错误的。我建议您阅读这篇文章以了解 CLR 使用何种启发式方法进行探测。heuristics does the CLR use for probing

话虽如此,您不能在同一个应用程序域中加载相同程序集的两个不同版本,您可以将同一程序集的不同版本加载到同一个应用程序域中,但这被认为是considered bad practice,应该避免。在您的情况下,这意味着您必须选择要使用的冲突程序集的版本。您有几个选择:

  1. Recompile helper.dll to use the latest version of conflict.dll (I will definetely go with this one if Ihave the source code for helper.dll).
  2. Choose which version of conflict.dll you want and apply a <bindingRedirect> in your config file. For example if you want to use the latest version:

    <runtime>
        <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
            <dependentAssembly>
                <assemblyIdentity name="conflict"
                                  publicKeyToken="xxxxxxxxx"
                                  culture="neutral" />
                <bindingRedirect oldVersion="5.8.0.0" 
                                 newVersion="6.2.0.0" />
            </dependentAssembly>
        </assemblyBinding>
    </runtime>
    
这将有效地指示CLR在尝试解析helper.dll的引用时加载版本6.2的conflict.dll。请注意,如果这两个版本使用不同的密钥进行强签名,此技术将无法使用。显然,由于helper.dll已经编译成5.8版,如果存在任何差异(缺少方法、不同的方法签名),则调用冲突方法时会导致运行时异常,因此只有在确定自己在做什么时才能这样做。

结论:无论选择哪种路径,您都需要在bin文件夹中复制一个版本的conflict.dll


1
@darin - 抱歉,但“您不能在同一应用程序域中加载相同程序集的两个不同版本”是不正确的。您不仅可以同时加载两个并且独立使用它们,还可以将同一程序集多次加载到应用程序域中,并独立使用它们。尝试使用Assembly.LoadFrom从两个不同位置加载相同的程序集-然后调用AppDomain.CurrentDomain.GetAssemblies() - Andras Zoltan
1
@Andras,你是正确的,我会更新我的帖子以反映你的评论。感谢你指出这一点。 - Darin Dimitrov
@Darin 不管怎样都要加1,因为提供了一个解决方案,也有助于整理房子 :) - Andras Zoltan
我明白为什么这是一种不好的做法,因为我刚试图使用bindingRedirect时遇到了运行时异常,显然程序集中的接口已经发生了变化,helper.dll出现了问题。 - djmc
我会尝试重新编译源代码。非常感谢您的回答,您帮助我更好地理解了这里发生的事情。 - djmc
显示剩余2条评论

1
支持Darin的答案,当然你需要消除这种多版本问题。他使用绑定重定向的解决方案是一个好方法-在此+1。如果绝对必要,我可以提供一个解决方案,允许您保留两个版本,但您需要编写一些代码。
您面临的唯一真正问题是,为了默认情况下被加载程序捕获,两个部署的文件名必须相同。您可以非常糟糕地欺骗并将5.8 dll部署为“Conflict.exe”,以便它可以与“Conflict.dll”(和更新版本)并排放置,并发现它可以工作。
此外,从Darin的答案中跟随链接,您会进入{{link1:此主题MSDN主题探测}}。基于此内容,您可以将5.8 dll简单地部署到bin \ Content \ Content.dll中,当运行时搜索它时,它将自动查找此子文件夹。
然而-那不是一个好的解决方案 :)
编辑-新解决方案
如果Conflict.dll的两个版本已经签名,您是否尝试过使用略有不同名称的版本之一进行部署?我刚刚设置了一个WinForms应用程序,其中包含对同一(已签名)程序集的不同版本的两个程序集引用。这会导致构建出现一些问题,因为最后引用的版本将被部署到bin文件夹中,而另一个版本则不会(因此您必须手动复制两个版本,并相应地重命名其中一个)。然后我尝试运行该应用程序,它会显示一个包含两个常量字符串的消息框;来自程序集的每个版本一个。它完全正常工作。 在此处下载此演示- 不要构建它(否则您必须进行文件重命名);只需打开表单应用程序的bin \ debug文件夹并运行exe。

ClassLibrary1.dll和ClassLibary1vanything.dll是同名且公钥相同的程序集的v1.0.0.0和v2.0.0.0版本。尽管classlibrary1vanything.dll文件名错误,但它仍然可以工作(可能是因为它已签名)。

在app.config中,我放了一个codebase提示,并认为这就是它能够工作的原因(最初我将其部署为不同的文件名),但后来我将其注释掉了,它仍然可以工作。当程序集必须部署到子文件夹或完全不同的位置时,codebase可能是最有用的。

原始文本

我尝试让MS支持文章中提到的第二个选项之一起作用,但似乎不想要。

毫无疑问,有一些聪明的方法可以直接完成它,但由于我还没有找到(而且不够聪明),所以我会使用上述支持主题中显示的第三个选项并连接到应用程序域的AssemblyResolve事件。

如果您添加了自己的配置(可能只是在appSettings中),以便将程序集的完整名称绑定到不同的文件名,则在AssemblyResolve事件处理程序中,您可以查看要加载的程序集的名称,以查看它是否在您的配置中。 如果是,则获取位置并使用Assembly.LoadFrom进行加载。

因此,一旦您有了这样的设置,您只需在其中添加Conflict v5.8程序集名称的条目以及应用程序应使用的文件名即可。

我不知道您部署的是什么类型的应用程序,但在win forms、控制台应用程序和服务中,AppDomain.CurrentDomain.BaseDirectory将等于bin文件夹,您可以将其与要加载的文件名连接起来。网站有点棘手。

应该能很好地工作。


这对我本周末很有帮助。非常好的答案。我要到下周中才能访问源代码重新编译helper.dll,所以这个答案希望能让我保持高效。非常感谢! - djmc
@djmc - 希望你能成功地推进这个计划。 - Andras Zoltan
@djmc - 以防你没有注意到,我更新了我的答案,并附上了一个演示,这可能会对你有所帮助。 - Andras Zoltan
非常感谢!它起作用了。重命名文件使其可以添加到Visual Studio,并仍然加载旧版本的程序集。 - djmc
感谢您抽出时间来解决这个问题。我非常感激。 - djmc
@djmc - 这是个好消息,我很高兴能帮到你!这是一个有趣的问题,我必须要调查一下,所以花费的时间非常值得 :) - Andras Zoltan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接