【问题标题】:How to build/deploy project that requires multiple versions of the same assembly?如何构建/部署需要同一程序集的多个版本的项目?
【发布时间】:2010-09-05 19:41:25
【问题描述】:

我正在开发一个使用conflict.dll 6.2 版的项目,但该项目还使用了使用conflict.dll 5.8 版的helper.dll。

我可以将 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?我是创建构建操作还是有其他方法?

谢谢

【问题讨论】:

    标签: .net visual-studio assemblies


    【解决方案1】:

    经过数小时的搜索和诅咒,我找到了一个可行且易于实施且可靠的解决方案。

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

    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 并执行Include in Project(避免您在代码中执行此操作)。如果您打包 Web 应用程序,这对于部署文件是必要的。

    完成!

    【讨论】:

    • 那么您在第 2 步中向 web.config 添加了什么?
    【解决方案2】:

    我相信 .net 会搜索 应用程序箱中的程序集 像这样的目录:\bin\conflict.dll (6.2) \bin\5.8\conflict.dll (5.8)

    不,这是错误的。我建议您阅读这篇文章以了解更多关于 heuristics does the CLR use for probing 的信息。

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

    1. 重新编译helper.dll 以使用最新版本的conflict.dll(如果我有helper.dll 的源代码,我肯定会使用这个)。
    2. 选择您想要的conflict.dll 版本并在您的配置文件中应用&lt;bindingRedirect&gt;。例如,如果您想使用最新版本:

      <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 文件夹中的xcopy 中添加conflict.dll 的单个版本。

    【讨论】:

    • @darin - 抱歉,“您不能在同一个应用程序域中加载同一程序集的两个不同版本”是不正确的。您不仅可以同时加载两个(并独立使用它们),还可以将同一个程序集多次加载到应用程序域中并独立使用它们。尝试使用 Assembly.LoadFrom 从两个不同的位置加载相同的程序集 - 然后调用 AppDomain.CurrentDomain.GetAssemblies()
    • @Andras,你是对的,我会更新我的帖子以反映你的评论。感谢您指出这一点。
    • @Darin +1 在任何情况下都提供了一个解决方案,这也有助于整理房子:)
    • 我明白了为什么这是一个不好的做法,我只是在尝试使用 bindingRedirect 时遇到了运行时异常,因为显然程序集中的接口已经改变并且 helper.dll 吓坏了。
    • 我看看能不能重新编译源代码。非常感谢您的回答。您帮助我更好地了解这里发生了什么。
    【解决方案3】:

    为了支持 Darin 的回答,您当然需要消除此类多版本问题。他使用绑定重定向的解决方案是一个很好的解决方案 - +1。我可以提供一个解决方案,让您在绝对必要时保留两者,但您必须编写一些代码。

    这里唯一真正的问题是两个部署的文件名必须相同才能被加载程序默认拾取。你可以非常可怕地作弊,只需将 5.8 dll 部署为Conflict.exe,这样它就可以并排放置Conflict.dll(和更新版本),你会发现它可以工作。

    此外,通过达林回答中的链接,您来到this topic MSDN topic on probing。根据这个内容,你可以简单地将5.8的dll部署到bin\Content\Content.dll中,当运行时搜索它时,它会自动在这个子文件夹中查找。

    但是 - 这不是一个好的解决方案 :)

    编辑 - 新解决方案

    如果 Conflict.dll 的两个版本都已签名,您是否尝试过部署其中一个名称稍有不同的版本?我刚刚设置了一个 winforms 应用程序,其中包含对同一(签名)程序集的不同版本的两个程序集引用。这会导致构建出现一些问题,因为最后引用的版本将部署到 bin 文件夹,而另一个不会(因此您必须手动复制两者;相应地重命名其中一个)。然后我尝试运行应用程序,它会显示一个包含两个常量字符串的消息框;来自程序集的每个版本。它工作得很好。

    Download a demo of this here - 不要构建它(否则你必须重命名文件);只需打开表单应用程序的 bin\debug 文件夹并运行 exe。

    ClassLibrary1.dll 和 ClassLibary1vanything.dll 是具有相同名称和公钥的程序集的 v1.0.0.0 和 v2.0.0.0。尽管 classlibrary1vanything.dll 的文件名错误,但它仍然有效(可能是因为它已签名)。

    在 app.config 中,我确实输入了代码库提示,并认为这就是它起作用的原因(最初我将它部署为不同的文件名),但后来我将其注释掉,它仍然有效。当程序集必须部署到子文件夹或完全不同的位置时,代码库可能最有用。

    原文

    我试图让提到的第二个选项in this support article from MS 起作用,但它似乎不想这样做。

    毫无疑问,有一些聪明的方法可以开箱即用,但由于我还不够聪明,无法找到它(还),我会改为修饰并使用上述支持中显示的第三个选项主题并挂钩到应用域的AssemblyResolve 事件。

    如果您添加自己的配置(实际上可能只是在 appSettings 中)将程序集的全名绑定到不同的文件名,那么在您的 AssemblyResolve 事件处理程序中,您可以查询要加载的程序集的名称看看它是否在您的配置中。如果是,请抓住该位置并使用 Assembly.LoadFrom 加载它。

    因此,一旦您有了类似的东西,您只需在其中添加 Conflict v5.8 程序集名称的条目以及应用程序应使用的文件名。

    我不知道您正在部署什么类型的应用程序,但在 win 表单中,控制台应用程序和服务 AppDomain.CurrentDomain.BaseDirectory 将等于 bin 文件夹,您可以使用您想要加载的文件名加入它.网站有点棘手。

    应该是一种享受。

    【讨论】:

    • 这个周末可以帮到我。很好的答案.. 直到下周中旬,我才能访问源代码来重新编译 helper.dll,所以希望这能让我保持生产力。非常感谢!
    • @djmc - 好吧,我希望你能在这个地方获得一些牵引力
    • @djmc - 以防万一你没有注意到;我用一个可以帮助你的演示更新了我的答案。
    • 非常感谢!有效。重命名文件可以将其添加到 Visual Studio,并且仍会加载旧版本的程序集。
    • 感谢您花时间解决这个问题。我真的很感激。
    猜你喜欢
    • 2014-06-22
    • 1970-01-01
    • 2012-11-04
    • 1970-01-01
    • 2018-02-12
    • 2010-11-02
    • 1970-01-01
    • 2013-01-29
    相关资源
    最近更新 更多