使用 .NET Framework 简化部署和解决 DLL 地狱问题

 

Steven Pratschner
Microsoft Corporation

2001 年 11 月更新

总结:本文介绍程序集的概念,并介绍.NET Framework如何使用程序集来解决版本管理和部署问题。 (16 个打印页)

目录

简介
问题陈述
解决方案的特征
程序集:构建基块
版本控制与共享
版本策略
部署
总结

简介

Microsoft® .NET Framework引入了多项新功能,旨在简化应用程序部署并解决 DLL 地狱问题。 最终用户和开发人员都熟悉当今基于组件的系统可能出现的版本管理和部署问题。 例如,几乎每个最终用户在其计算机上安装了一个新的应用程序,却发现现有应用程序神秘地停止工作。 大多数开发人员还花时间使用 Regedit,尝试使所有必要的注册表项保持一致,以激活 COM 类。

.NET Framework中用于解决 DLL 地狱问题的设计准则和实现技术建立在 Microsoft Windows® 2000 中完成的工作之上,如 Rick Anderson 在《DLL Hell 的终结》和 David D'Souza、BJ Whalen 和 Peter Wilson 在应用程序中实现并行组件共享 (扩展) 中所述。 .NET Framework通过为在 .NET 平台上使用托管代码生成的应用程序提供应用程序隔离和并行组件等功能来扩展前面的工作。 另请注意,Windows XP 为非托管代码提供相同的隔离和版本控制功能,包括 COM 类和 Win32 DLL (请参阅 如何为 Windows XP 生成和服务隔离的应用程序和并行程序集 了解详细信息) 。

本文介绍 程序集 的概念,并介绍 .NET 如何使用程序集来解决版本管理和部署问题。 具体而言,我们将讨论如何构建程序集、如何命名程序集,以及编译器和公共语言运行时 (CLR) 如何使用程序集在应用程序的各个部分之间记录和强制实施版本依赖关系。 我们还将讨论应用程序和管理员如何通过我们称之为版本策略来自定义 版本控制行为。

引入和描述程序集后,将呈现多个部署方案,提供.NET Framework中提供的各种打包和分发选项的采样。

问题陈述

版本控制

从客户的角度来看,最常见的版本控制问题是所谓的 DLL 地狱。 简单地说,DLL Hell 是指当多个应用程序尝试共享公共组件(如动态链接库 (DLL) 或组件对象模型 (COM) 类)时导致的问题集。 在最典型的情况下,一个应用程序将安装与计算机上已有的版本不向后兼容的新版本的共享组件。 尽管刚刚安装的应用程序可以正常工作,但依赖于以前版本的共享组件的现有应用程序可能不再工作。 在某些情况下,问题的原因更为微妙。 例如,假设用户下载 Microsoft ActiveX® 控件是访问某些网站的副作用。 下载控件时,它将替换计算机上存在的任何现有控件版本。 如果计算机上安装的应用程序恰好使用此控件,它也可能停止工作。

在许多情况下,在用户发现应用程序已停止工作之前,存在明显的延迟。 因此,通常很难记住何时对计算机进行了可能影响应用的更改。 用户可能还记得在一周前安装过某些内容,但该安装与其现在看到的行为之间没有明显的关联。 更糟的是,目前很少有诊断工具可以帮助用户 (或帮助他们的支持人员) 确定问题所在。

这些问题的原因是系统不会记录或强制实施有关应用程序不同组件的版本信息。 此外,代表一个应用程序对系统进行的更改通常会影响计算机上的所有应用程序, 现在构建完全独立于更改的应用程序并不容易。

难以生成独立应用程序的一个原因是,当前运行时环境通常只允许安装单个版本的组件或应用程序。 此限制意味着组件作者必须以保持向后兼容的方式编写其代码,否则在安装新组件时可能会破坏现有应用程序。 在实践中,编写永远向后兼容的代码是极其困难的,如果不是不可能的话。 在 .NET 中, 并排 的概念是版本控制故事的核心。 并排是能够同时在计算机上安装和运行同一组件的多个版本。 对于支持并行的组件,作者不一定与保持严格的向后兼容性有关,因为不同的应用程序可以自由使用不同版本的共享组件。

部署和安装

现在安装应用程序是一个多步骤过程。 通常,安装应用程序涉及将许多软件组件复制到磁盘,并创建一系列注册表项,以向系统描述这些组件。

注册表中的条目与磁盘上的文件之间的分离使得复制和卸载应用程序变得非常困难。 此外,在注册表中完全描述 COM 类所需的各种条目之间的关系非常松散。 这些条目通常包括 coclasses、接口、typelibs 和 DCOM 应用 ID 的条目,而不是提及为注册文档扩展或组件类别而创建的任何条目。 通常,你最终会手动使这些内容保持同步。

最后,激活任何 COM 类需要此注册表占用空间。 这极大地使部署分布式应用程序的过程变得复杂化,因为必须接触每台客户端计算机才能创建适当的注册表项。

这些问题的主要原因是组件的描述与组件本身分开。 换句话说,应用程序既不是自描述的,也不是自包含的。

解决方案的特征

.NET Framework必须提供以下基本功能来解决上述问题:

  • 应用程序必须是自我描述的。 自描述的应用程序删除了对注册表的依赖关系,从而支持零影响安装并简化卸载和复制。
  • 必须记录并强制实施版本信息。 版本控制支持必须内置于平台中,以确保在运行时加载正确版本的依赖项。
  • 必须记住“上次已知良好”。 当应用程序成功运行时,平台必须记住协同工作的组件集(包括其版本)。 此外,必须提供工具,使管理员能够轻松地将应用程序还原到此“最后已知良好”状态。
  • 支持并行组件。 允许在计算机上同时安装和运行多个版本的组件允许调用方指定要加载的版本,而不是在不知不觉中“强制”的版本。 .NET Framework允许多个版本的框架本身在一台计算机上共存,从而更进一步。 这大大简化了升级过程,因为如果需要,管理员可以选择在不同版本的.NET Framework上运行不同的应用程序。
  • 应用程序隔离。 .NET Framework必须能够轻松地编写不受代表其他应用程序对计算机所做的更改影响的应用程序(实际上是默认应用程序)。

程序集:构建基块

程序集是.NET Framework用于解决上述版本管理和部署问题的构建基块。 程序集是类型和资源的部署单元。 在很多方面,程序集相当于当今世界中的 DLL;从本质上讲,程序集是一个“逻辑 DLL”。

程序集通过称为清单的元数据进行自我描述。 正如 .NET 使用元数据来描述类型一样,它还使用元数据来描述包含这些类型的程序集。

程序集远不止部署。 例如,.NET 中的版本控制是在程序集级别完成的,不会像模块或类型那样进行版本控制。 此外,程序集还用于在应用程序之间共享代码。 类型中包含的程序集是类型标识的一部分。

代码访问安全系统在其权限模型的核心使用程序集。 程序集的作者在清单中记录运行代码所需的权限集,管理员根据包含代码的程序集向代码授予权限。

最后,程序集也是类型系统和运行时系统的核心,因为它们为类型建立可见性边界,并充当用于解析对类型的引用的运行时范围。

程序集清单

具体而言,清单包括有关程序集的以下数据:

  • 标识。 程序集的标识由四个部分组成:简单文本名称、版本号、可选区域性和可选公钥(如果程序集是为共享而构建的 (请参阅下面的共享程序集) 部分)。
  • 文件列表。 清单包含组成程序集的所有文件的列表。 对于每个文件,清单会在生成清单时记录其名称和其内容的加密哈希。 此哈希在运行时进行验证,以确保部署单元保持一致。
  • 引用的程序集。 程序集之间的依赖关系存储在调用程序集的清单中。 依赖项信息包括版本号,该版本号在运行时用于确保加载依赖项的正确版本。
  • 导出的类型和资源。 可用于类型和资源的可见性选项包括“仅在我的程序集内可见”和“对程序集外部的调用方可见”。
  • 权限请求。 程序集的权限请求分为三个集:1 个) 运行程序集所需的权限请求,2 个) 所需的权限请求,但即使未授予程序集,程序集仍将具有一些功能;3 个) 作者不希望授予程序集的权限。

IL 反汇编程序 (Ildasm) SDK 工具可用于查看程序集中的代码和元数据。 图 1 是 Ildasm 显示的示例清单。 .assembly 指令标识程序集,.assembly extern 指令包含有关此程序集所依赖的其他程序集的信息。

图 1. IL 反汇编程序显示的示例清单

程序集结构

到目前为止,程序集主要被描述为一个逻辑概念。 本部分通过描述程序集的物理表示方式,帮助使程序集更加具体。

通常,程序集由四个元素组成:程序集元数据 (清单) 、描述类型的元数据、实现类型的中间语言 (IL) 代码,以及一组资源。 并非所有这些都存在于每个程序集中。 严格要求只有清单,但需要类型或资源才能为程序集提供任何有意义的功能。

可通过多个选项来“打包”这四个元素。例如,图 2 显示了包含整个程序集的单个 DLL:清单、类型元数据、IL 代码和资源。

图 2. 包含所有程序集元素的 DLL

或者,程序集的内容可以分布在多个文件中。 在图 3 中,作者已选择将某些实用工具代码分离到不同的 DLL 中,并保留大型资源文件 (在本例中 JPEG 在其原始文件中) 。 这样做的原因之一是优化代码下载。 .NET Framework仅在引用文件时下载文件,因此,如果程序集包含不经常访问的代码或资源,将它们分解为单个文件将提高下载效率。 使用多个文件的另一种常见方案是生成由多种语言的代码组成的程序集。 在这种情况下,你将单独 (模块) 生成每个文件,然后使用 .NET Framework SDK (al.exe) 中提供的程序集链接器工具将它们分组到程序集中。

图 3. 跨多个文件分布的程序集元素

版本控制与共享

DLL Hell 的主要原因之一是当前在基于组件的系统中使用的共享模型。 默认情况下,计算机上的多个应用程序共享单个软件组件。 例如,每次安装程序将 DLL 复制到系统目录或在 COM 注册表中注册类时,该代码都可能对计算机上运行的其他应用程序产生影响。 具体而言,如果现有应用程序使用了该共享组件的以前版本,该应用程序将自动开始使用新版本。 如果共享组件严格向后兼容,这也许可以,但在许多情况下,维护向后兼容性是困难的,即使不是不可能。 如果未维护或无法维护向后兼容性,这通常会导致应用程序因安装其他应用程序而遭到破坏。

.NET 中的原则设计准则是独立组件 (或程序集) 。 隔离程序集意味着程序集只能由一个应用程序访问,它不由计算机上的多个应用程序共享,并且不会受到其他应用程序对系统所做的更改的影响。 隔离使开发人员可以绝对控制其应用程序使用的代码。 独立程序集或应用程序专用程序集是 .NET 应用程序中的默认程序集。 独立组件的趋势始于 Microsoft Windows 2000 中引入 .local 文件。 此文件用于使 OS 加载器和 COM 在尝试查找请求的组件时先查找应用程序目录。 (请参阅 MSDN 库中的相关文章, 在 Applications 中实现并行组件共享。)

但是,在某些情况下,需要在应用程序之间共享程序集。 显然,每个应用程序都携带自己的 System.Windowns.Forms、System.Web 或通用Web Forms控件副本是没有意义的。

在 .NET 中,在应用程序之间共享代码是一个明确的决定。 共享的程序集有一些额外的要求。 具体而言,共享程序集应并行支持,以便同一程序集的多个版本可以同时安装并在同一台计算机上,甚至在同一进程中运行。 此外,共享程序集具有更严格的命名要求。 例如,共享程序集的名称必须全局唯一。

隔离和共享的需求使我们想到了两种“类型”程序集。 这是一个相当松散的分类,两者之间没有真正的结构差异,但区别在于它们的使用方式:是专用于一个应用程序,还是在多个应用程序之间共享。

Application-Private 程序集

应用程序专用程序集是仅对一个应用程序可见的程序集。 我们预计这是 .NET 中最常见的情况。 专用程序集的命名要求很简单:程序集名称在应用程序中必须唯一。 不需要全局唯一的名称。 保持名称唯一不是问题,因为应用程序开发人员可以完全控制哪些程序集与应用程序隔离。

应用程序专用程序集部署在使用它们的应用程序的目录结构中。 专用程序集可以直接放置在应用程序目录或其子目录中。 CLR 通过名为 “探测”的过程查找这些程序集。 探测只是程序集名称到包含清单的文件的名称的映射。

具体而言,CLR 采用程序集引用中记录的程序集的名称,追加“.dll”,并在应用程序目录中查找该文件。 此方案有一些变体,其中运行时将查找程序集命名的子目录或程序集区域性命名的子目录。 例如,开发人员可以选择在名为“de”的子目录中将包含本地化为德语的资源的程序集部署到名为“es.”的目录中的西班牙语 (有关详细信息,请参阅.NET Framework SDK 指南。)

如前所述,每个程序集清单都包含有关其依赖项的版本信息。 不对专用程序集强制实施此版本信息,因为开发人员可以完全控制部署到应用程序目录的程序集。

共享程序集

.NET Framework还支持共享程序集的概念。 共享程序集是由计算机上的多个应用程序使用的程序集。 使用 .NET 时,在应用程序之间共享代码是一个明确的决定。 共享程序集有一些额外的要求,旨在避免我们今天遇到的共享问题。 除了对前面并排描述的支持外,共享程序集还有更严格的命名要求。 例如,共享程序集的名称必须全局唯一。 此外,系统必须提供“名称保护”,即防止某人重用他人的程序集名称。 例如,假设你是网格控件的供应商,并且已发布程序集的版本 1。 作为作者,你需要确保其他人无法释放声明为版本 2 或网格控件的程序集。 .NET Framework通过称为强名称的技术支持这些命名要求, (下一节) 对此进行详细介绍。

通常,应用程序作者对应用程序使用的共享程序集的控制程度不同。 因此,每次引用共享程序集时都会检查版本信息。 此外,.NET Framework允许应用程序和管理员通过指定版本策略替代应用程序使用的程序集版本。

共享程序集不一定以私密方式部署到一个应用程序,尽管该方法仍然可行,尤其是在需要 xcopy 部署的情况下。 除了专用应用程序目录外,只要应用程序配置文件中提供了描述程序集位置的代码库,共享程序集也可以部署到全局程序集缓存或任何 URL。 全局程序集缓存是一个计算机范围的存储,适用于多个应用程序使用的程序集。 如前所述,部署到缓存不是必需的,但这样做有一些好处。 例如,自动提供程序集的多个版本的并行存储。 此外,管理员可以使用存储来部署他们希望计算机上的每个应用程序使用的 bug 修复或安全修补程序。 最后,还有一些与部署到全局程序集缓存相关的性能改进。 第一个涉及到验证强名称签名,如下面的强名称部分所述。 第二个性能改进涉及工作集。 如果多个应用程序同时使用同一程序集,则从磁盘上的同一位置加载该程序集会利用 OS 提供的代码共享行为。 相比之下,从多个不同位置加载同一程序集 (应用程序目录) 会导致加载相同代码的多个副本。 将程序集添加到最终用户计算机上的缓存通常是使用基于 Windows Installer 或其他一些安装技术的安装程序完成的。 程序集永远不会作为运行某些应用程序或浏览到网页的副作用而最终出现在缓存中。 相反,将程序集安装到缓存需要用户执行显式操作。 Windows XP 和 Visual Studio .NET 附带的 Windows Installer 2.0 已得到增强,可以完全了解程序集、程序集缓存和隔离应用程序的概念。 这意味着你将能够将所有 Windows Installer 功能(例如按需安装和应用程序修复)与 .NET 应用程序一起使用。

每次要将程序集添加到开发和测试计算机上的缓存时,生成安装包通常不切实际。 因此,.NET SDK 包含一些用于处理程序集缓存的工具。 第一种是一个名为 gacutil 的工具,可用于将程序集添加到缓存并在以后删除它们。 使用 /i 开关将程序集添加到缓存:

gacutil /i:myassembly.dll 
See the .NET Framework SDK documentation for a full description of the 
      options supported by gacutil.

其他工具是 Windows Shell 扩展,可用于使用 Windows 资源管理器和.NET Framework配置工具操作缓存。 可以通过导航到 Windows 目录下的“assembly”子目录来访问 Shell 扩展。 可在控制面板的“管理工具”部分找到.NET Framework配置工具。

图 4 显示了使用 Shell 扩展的全局程序集缓存的视图。

图 4。 全局程序集缓存

强名称

强名称用于启用与共享程序集关联的更严格的命名要求。 强名称有三个目标:

  • 名称唯一性。 共享程序集的名称必须全局唯一。
  • 防止名称欺骗。 开发人员不希望其他人发布你的程序集的后续版本,并错误地声称它来自你,无论是偶然的还是故意的。
  • 在引用时提供标识。 解析对程序集的引用时,强名称用于保证加载的程序集来自预期的发布者。

强名称是使用标准公钥加密实现的。 通常,该过程的工作方式如下:程序集的作者 (生成一个密钥对或使用现有的密钥对) ,使用私钥对包含清单的文件进行签名,并使公钥可供调用方使用。 对程序集进行引用时,调用方将记录与用于生成强名称的私钥对应的公钥。 图 5 概述了此过程在开发时的工作原理,包括密钥如何存储在元数据中以及如何生成签名。

该方案是一个名为“Main”的程序集,它引用名为“MyLib”的程序集。MyLib 具有共享名称。 重要步骤如下所述。

图 5。 实现共享名称的过程

  1. 开发人员调用编译器,该编译器传入程序集的密钥对和源文件集。 密钥对是使用名为 SN 的 SDK 工具生成的。 例如,以下命令生成新的密钥对并将其保存到文件中:

    Sn –k MyKey.snk
    The key pair is passed to the compiler using the custom attribute 
            System.Reflection.AssemblyKeyFileAttribute as follows:
    
       <assembly:AssemblyKeyFileAttribute("TestKey.snk")>
    
  2. 当编译器发出程序集时,公钥将记录在清单中作为程序集标识的一部分。 将公钥作为标识的一部分提供程序集全局唯一的名称。

  3. 发出程序集后,使用私钥对包含清单的文件进行签名。 生成的签名存储在 文件中。

  4. 当编译器生成 Main 时,MyLib 的公钥作为对 MyLib 的引用的一部分存储在 Main 的清单中。

在运行时,.NET Framework需要执行两个步骤来确保强名称为开发人员提供所需的优势。 首先,仅当程序集安装到全局程序集缓存中时,才会验证 MyLib 的强名称签名-当应用程序加载文件时,不会再次验证签名。 如果共享程序集未部署到全局程序集缓存,则每次加载文件时都会验证签名。 验证签名可确保自生成程序集以来,MyLib 的内容未被更改。 第二步是验证作为 Main 对 MyLib 引用的一部分存储的公钥是否与作为 MyLib 标识一部分的公钥匹配。 如果这些密钥相同,则 Main 的作者可以确定加载的 MyLib 版本来自创建用于生成 Main 的 MyLib 版本的同一发布者。 当解析从 Main 到 MyLib 的引用时,此密钥等效检查是在运行时完成的。

“签名”一词经常让人们想到 Microsoft Authenticode®。 请务必了解强名称和 Authenticode 没有任何关联。 这两种技术的目标不同。 具体而言,Authenticode 表示与发布者关联的信任级别,而强名称则不然。 没有与强名称关联的证书或第三方签名机构。 此外,强名称签名通常由编译器本身作为生成过程的一部分完成。

另一个值得注意的注意事项是“延迟签名”过程。 通常,程序集的作者无权访问执行完全签名所需的私钥。 大多数公司将这些密钥保存在受到良好保护的商店中,这些商店只能由少数人访问。 因此,.NET Framework提供了一种称为“延迟签名”的技术,使开发人员能够仅使用公钥生成程序集。在此模式下,文件实际上未签名,因为未提供私钥。 相反,稍后会使用 SN 实用工具对文件进行签名。 有关如何使用延迟签名的详细信息,请参阅 .NET Framework SDK 中的延迟对程序集进行签名。

版本策略

如前所述,每个程序集清单记录有关生成它所基于的每个依赖项的版本的信息。 但是,在某些情况下,应用程序作者或管理员可能希望在运行时使用不同版本的依赖项运行。 例如,管理员应该能够部署 bug 修复版本,而无需重新编译每个应用程序即可获取修补程序。 此外,管理员必须能够指定在发现安全漏洞或其他严重 bug 时绝不使用特定版本的程序集。 .NET Framework通过版本策略在版本绑定方面实现了这种灵活性。

程序集版本号

每个程序集都有一个由四部分构成的版本号作为其标识的一部分, (即,某些程序集的版本 1.0.0.0 和版本 2.1.0.2 是完全不同的标识,就类加载程序) 而言。 将版本作为标识的一部分包含在标识中对于区分程序集的不同版本至关重要。

版本号的部分包括主要、次要、内部版本和修订。 没有语义应用于版本号的各个部分。 也就是说,CLR 不会根据版本号的分配方式推断程序集的兼容性或任何其他特征。 作为开发人员,你可以根据需要随意更改此数字的任何部分。 即使没有语义应用于版本号的格式,个别组织可能会发现建立关于版本号更改方式的约定很有用。 这有助于在整个组织中保持一致性,并更轻松地确定特定程序集的生成内容。 一个典型的约定如下:

主要或次要。 对版本号的主要或次要部分的更改表示不兼容的更改。 根据此约定,版本 2.0.0.0 将被视为与版本 1.0.0.0 不兼容。 不兼容更改的示例包括更改某些方法参数的类型或完全删除类型或方法。

内部版本号通常用于区分每日内部版本或较小的兼容版本。

修订。 对修订号的更改通常保留给修复特定 bug 所需的增量生成。 有时你会听到这称为“紧急 bug 修复”编号,因为修订是在将特定 bug 的修复交付给客户时经常更改的。

默认版本策略

解析对共享程序集的引用时,CLR 确定在代码中遇到对该程序集的引用时要加载的依赖项版本。 .NET 中的默认版本策略非常简单。 解析引用时,CLR 从调用程序集的清单中获取版本,并使用完全相同的版本号加载依赖项的版本。 这样,调用方将获取生成并测试其所针对的确切版本。 此默认策略具有 属性,它保护应用程序免受不同应用程序安装现有应用程序所依赖的共享程序集新版本的情况的影响。 回想一下,在 .NET 之前,现有应用程序默认会开始使用新的共享组件。 但是,在 .NET 中,新版共享程序集的安装不会影响现有应用程序。

自定义版本策略

有时,绑定到应用程序随附的确切版本可能不是你想要的。 例如,管理员可能会将关键 bug 修复部署到共享程序集,并希望所有应用程序都使用此新版本,而不考虑使用哪个版本生成它们。 此外,共享程序集的供应商可能已将服务版本寄送到现有程序集,并希望所有应用程序开始使用服务版本而不是原始版本。 .NET Framework通过版本策略支持这些方案和其他方案。

版本策略在 XML 文件中声明,只是加载程序集的一个版本而不是另一个版本的请求。 例如,以下版本策略指示 CLR 加载版本 5.0.0.1,而不是名为 MarineCtrl 的程序集的版本 5.0.0.0:

 <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
  <dependentAssembly
   <assemblyIdentity name="MarineCtrl" publicKeyToken="9335a2124541cfb9" />
      <bindingRedirect oldVersion="5.0.0.0" newVersion="5.0.0.1" />   
  </dependentAssembly>
</assemblyBinding>

除了从特定版本号重定向到另一个版本号外,还可以从一系列版本重定向到另一个版本。 例如,以下策略将 MarineCtrl 的 0.0.0.0 到 5.0.0.0 的所有版本重定向到版本 5.0.0.1:

<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
  <dependentAssembly
   <assemblyIdentity name="MarineCtrl" publicKeyToken="9335a2124541cfb9" />
      <bindingRedirect oldVersion="0.0.0.0-5.0.0.0" newVersion="5.0.0.1" />   
  </dependentAssembly>
</assemblyBinding>

版本策略级别

在 .NET 中应用版本策略有三个级别:特定于应用程序的策略、发布者策略和计算机范围的策略。

特定于应用程序的策略。 每个应用程序都有一个可选的配置文件,该文件可以指定应用程序绑定到依赖程序集的不同版本。 配置文件的名称因应用程序类型而异。 对于可执行文件,配置文件的名称是可执行文件的名称 + “.config” 扩展名。 例如,“myapp.exe”的配置文件为“myapp.exe.config”。 ASP.NET 应用程序的配置文件始终为“web.config”。

发布者策略。 虽然特定于应用程序的策略由应用程序开发人员或管理员设置,但发布者策略由共享程序集的供应商设置。 发布者策略是供应商关于其程序集不同版本的兼容性声明。 例如,假设共享Windows 窗体控件的供应商提供了一个服务版本,其中包含对控件的大量 bug 修复。 原始控件版本为 2.0.0.0,服务版本为 2.0.0.1。 由于新版本仅包含 bug 修复 (没有中断性 API 更改) 控制供应商可能会发布发布者策略与新版本,导致使用 2.0.0.0 的现有应用程序现在开始使用 2.0.0.1。 发布服务器策略以 XML 表示,就像应用程序和计算机范围的策略一样,但与其他策略级别不同,发布者策略本身作为程序集本身进行分发。 这样做的主要原因是确保为特定程序集发布策略的组织与发布程序集本身的组织相同。 这是通过要求为原始程序集和策略程序集提供具有相同键对的强名称来实现的。

计算机范围的策略。 最终的策略级别是计算机范围的策略 (有时称为管理员策略) 。 计算机范围的策略存储在位于 .NET Framework 安装目录下的“config”子目录中的machine.config中。 安装目录为 %windir%\microsoft.net\framework\%runtimeversion%。 machine.config中所做的策略语句会影响计算机上运行的所有应用程序。 管理员使用计算机范围的策略来强制给定计算机上的所有应用程序使用特定版本的程序集。 使用此方法的最注释方案是将安全性或其他关键 bug 修复部署到全局程序集缓存。 部署固定程序集后,管理员将使用计算机范围的版本策略来确保应用程序不使用旧版本的损坏程序集。

策略评估

绑定到强名称程序集时,CLR 首先会确定要绑定到哪个版本的程序集。 该过程首先读取引用程序集清单中记录的所需程序集的版本号。 然后评估策略以确定任何策略级别是否包含到不同版本的重定向。 策略级别按从应用程序策略开始的顺序进行评估,然后是发布者,最后是管理员。

在任何级别找到的重定向将替代上一个级别所做的任何语句。 例如,假设程序集 A 引用程序集 B。A 清单中对 B 的引用是版本 1.0.0.0。 此外,程序集 B 附带的发布者策略会将引用从 1.0.0.0 重定向到 2.0.0.0。 此外,还有版本策略是计算机范围的配置文件,用于将引用定向到版本 3.0.0.0。 在这种情况下,在计算机级别所做的语句将替代在发布者级别所做的语句。

绕过发布服务器策略

由于应用这三种类型的策略的顺序,发布者的版本重定向 (发布者策略) 可以替代在调用程序集的元数据中记录的版本和设置的任何特定于应用程序的策略。 但是,强制应用程序始终接受发布者关于版本控制的建议可能会导致 DLL 地狱。 毕竟,DLL Hell 的主要原因是难以在共享组件中保持向后兼容性。 为了进一步避免应用程序因安装共享组件的新版本而中断的情况,.NET 中的版本策略系统允许单个应用程序绕过发布者策略。 换句话说,应用程序可以拒绝发布者关于使用哪个版本的建议。 应用程序可以使用应用程序配置文件中的“publisherPolicy”元素绕过发布者策略:

<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">

<publisherPolicy apply="no"/>

</assemblyBinding>

使用 .NET 配置工具设置版本策略

幸运的是,.NET Framework附带了图形管理工具,因此你不必担心手动编辑 XML 策略文件。 该工具支持应用程序和计算机范围的版本策略。 可以在控制面板的“管理工具”部分找到该工具。 管理工具的初始屏幕如图 6 所示:

图 6。 管理员工具

以下步骤介绍如何设置特定于应用程序的策略:

  1. 将应用程序添加到树视图中的“应用程序”节点。 右键单击“应用程序”节点,然后单击“ 添加”。 “ 添加 ”对话框显示要从中选取的 .NET 应用程序的列表。 如果应用程序不在列表中,可以通过单击“ 其他”来添加该应用程序。

  2. 选择要为其设置策略的程序集。 右键单击“配置的程序集”节点,然后单击“ 添加”。 其中一个选项是从应用程序引用的程序集列表中选择程序集,并显示以下对话框,如图 7 所示。 选取程序集并单击“ 选择”。

    图 7。 选择程序集

  3. 在“ 属性 ”对话框中,输入版本策略信息。 单击“ 绑定策略 ”选项卡,并在表中输入所需的版本号,如图 8 所示。

    图 8。 “绑定策略”选项卡

部署

部署至少涉及两个不同的方面:打包代码,以及将包分发到将运行应用程序的各种客户端和服务器。 .NET Framework的主要目标是简化部署 (特别是分发方面) ,使零影响安装和 xcopy 部署变得可行。 程序集的自描述性质使我们能够删除对注册表的依赖,从而使安装、卸载和复制变得更加简单。 但是,在某些情况下,xcopy 不足以或不适合作为分发机制。 对于这些情况,.NET Framework提供了广泛的代码下载服务和与 Windows Installer 的集成。

打包

.NET Framework的第一个版本中提供了三个打包选项:

  • 内置 (DLL 和 EXE) 。 在许多情况下,不需要特殊打包。 应用程序可以采用开发工具生成的格式进行部署。 即 DLL 和 EXE 的集合。
  • Cab 文件。 Cab 文件可用于压缩应用程序,以便更高效地下载。
  • Windows Installer 包。 Microsoft Visual Studio .NET 和其他安装工具允许你) 生成 Windows Installer 包 (.msi 文件。 Windows Installer 允许你利用应用程序修复、按需安装和其他 Microsoft Windows 应用程序管理功能。

分发方案

.NET 应用程序可以通过多种方式进行分发,包括 xcopy、代码下载和 Windows Installer。

对于许多应用程序(包括 Web 应用程序和 Web 服务),部署就像将一组文件复制到磁盘并运行它们一样简单。 卸载和复制同样简单,只需删除或复制文件即可。

.NET Framework使用 Web 浏览器提供广泛的代码下载支持。 在此领域进行了多项改进,包括:

  • 零影响。 计算机上没有注册表项。
  • 增量下载。 程序集的各个部分仅在引用时下载。
  • 独立下载到应用程序。 代表一个应用程序下载的代码不会影响计算机上的其他应用程序。 我们的代码下载支持的主要目标是防止出现这样一种情况:用户下载某个共享组件的新版本会因浏览到特定网站而使该新版本对其他应用程序产生负面影响。
  • 没有验证码对话框。 代码访问安全系统用于允许移动代码以部分信任级别运行。 用户永远不会看到要求他们决定是否信任代码的对话框。

最后,.NET 与 Windows 安装程序和 Windows 的应用程序管理功能完全集成。

总结

.NET Framework支持零影响安装,并解决 DLL Hell 问题。 程序集是用于启用这些功能的可版本化自描述部署单元。

创建独立应用程序的能力至关重要,因为它允许生成不受其他应用程序对系统所做的更改影响的应用程序。 .NET Framework通过部署在应用程序的目录结构中的应用程序专用程序集来鼓励此类应用程序。

并排是 .NET 中共享和版本控制故事的核心部分。 并行允许同时在计算机上安装和运行程序集的多个版本,并允许每个应用程序请求该程序集的特定版本。

CLR 记录应用程序各部分之间的版本信息,并在运行时使用该信息来确保加载正确的依赖项版本。 应用程序开发人员和管理员都可以使用版本策略,在选择加载给定程序集的版本时提供一定的灵活性。

.NET Framework提供了多个打包和分发选项,包括 Windows 安装程序、代码下载和简单的 xcopy。