加载程序集和版本控制

2023-11-30

我正在考虑通过提供一些预定义的接口来向现有应用程序添加一些可扩展性,这些接口可以通过放置在特定位置并由应用程序拾取的“插件”来实现。应用程序的核心很少更新,而插件更新和部署则更加频繁。

基本上,有这样的设置:

// in core assembly (core.dll)
public interface IReportProvider{
     string GenerateReport();
}

// in other assembly(plugin.dll)
public class AwesomeReport : IReportProvider {
     string GenerateReport(){ ... }
}

这两个项目都是同一构建过程的一部分,核心应用程序自行部署,插件在稍后阶段放入。

我的问题是随着时间的推移程序集版本控制和解决方案。假设 core.dll v1 已部署,我想添加一个插件。如果plugin.dll 引用core.dll v1,这非常有效。但是,如果 plugin.dll 是针对较新版本的 core.dll 进行编译的(作为构建的一部分,例如 v2),则该插件将无法加载,因为它引用的是 core.dll v2,但部署的版本只有 core.dll v1。

这是合理且预期的行为,但它给了我这个项目设置方式的一些痛点,即插件的开发/更新不能仅仅通过再次运行构建并放入新插件(现在有较新版本的依赖项)。

(我知道将较新的程序集解析为较旧的程序集的潜在问题以及类型定义中潜在的不匹配。问题只是解决高级程序集解析问题,而不是解决与类型定义不匹配相关的问题。)

我看到了一些让事情正常运转的选择,但没有一个像我真正想要的那么简单:

  1. 将绑定重定向添加到 web.config,指示所有 core.dll v1+ 引用解析为 core.dll v1 引用
  2. 创建包含接口定义的“contracts.dll”程序集,并在构建过程中保持该特定程序集的版本号不变
  3. 针对 core.dll 的部署版本构建插件(以某种方式引用开发中的部署版本)

如前所述,这些对我来说都不是真正容易实现的目标,我希望有人有更好的解决方案?


我在这个领域进行了相当广泛的工作,并且始终发现您需要围绕范围内和范围外的内容划定一些界限。想要最大程度的可扩展性是有风险的,但这一切都是有代价的。该成本要么是编译时(以约定、最佳实践和可能是自动构建检查的形式),要么是复杂的运行时。

要解决您列出的选项:

  1. Binding redirects are only a partial tool to achieve a solution. They will allow your program to 'slip' one version of a DLL in place of another, however, it will not magically solve the problem of what happens when methods change. MissingMethodException? Something here that you may not have though of aswell, is the dependency chain. dependency chains You can see that while the application is treating dependency 'A' as a version 1 object, internally it is creating something from a later version which is being passed back to the application and cast to v1.0 - causing an exception. This can be tricky to handle - and is just one of the risks.

  2. 跨构建保持合约程序集版本相同 这可以优雅地工作,但是,它只是将复杂性从构建时推迟到运行时。 您需要努力确保您的更改不会破坏跨版本的兼容性。更不用说,随着您的应用程序老化,您将在这些合同中收集大量您想要弃用的声明。最终这个文件将变得庞大、繁琐并且让开发人员感到困惑——这甚至还没有考虑到您拥有的所有实体合约!

  3. 我不太清楚你的意思是什么,以及它如何涵盖你的问题空间。

您可以采取的另一种方法(我们就是这样做的)是为“SDK”的每个主要版本创建一个新合同。这具有一些政治优势,因为它在一段时间内修复了主要功能,这意味着我们可以将功能请求保持在合理的预期水平,超出此范围的任何内容(需要新一代合同)都将推迟到“下一个主要版本” '。 然而,这确实需要在设计功能时勤奋——在编写合同时要深思熟虑,这样你就可以抢占最明显的需求——但我真的觉得这无论如何都是不言而喻的……它们被称为“合同”因为某种原因。 每个新合同都将存在于版本命名空间(Company.Product.Contracts.v1_0、Company.Product.Contracts.v1_1)中。

我不会链接我的合同(每个新版本的合同都会继承上一个版本)。这样做会让您回到保持版本号相同的问题,在不完全破坏链条的情况下,您永远无法完全摆脱功能。

当您的插件加载时,它可以询问主机它支持什么级别的功能(合同版本) - 以及它是否是旧主机/新插件场景:或者,对插件进行编程以减少其运行时功能以处理较小的主机功能,或者干脆拒绝加载。 无论如何,您可能应该执行这些检查,因为没有什么魔法可以让您的插件利用主机中根本不存在的功能! Microsoft 的 MAF 框架尝试通过使用 shim 基础架构来实现这一目标,但它给大多数人带来了巨大的复杂性。

因此,您需要考虑的一些事情是:

  1. 确定您的可扩展性需求范围!您想要完成的一切都会花费您持续的维护费用。
  2. 考虑一下您将如何弃用功能
  3. 不要忘记您的合约将包含实体和逻辑合约,这些与逻辑合约的考虑因素略有不同,因为它们通常传递更多。
  4. 仔细考虑每个兼容性检查是否在编译时而不是运行时执行得更好(反之亦然)
  5. 版本编号!程序集版本号对于运行时行为非常有用,文件版本号可以帮助您在版本控制中跟踪 DLL 直至源代码 - 充分利用它们!
  6. 如果您正在进行自定义 DLL 解析,请使用 app.config 来定义自定义 DLL 位置,而不是程序集解析事件。配置方法更加可预测,而且,嘿,它是在易于阅读的 Xml 中声明的! Fusion Log Viewer 还将很好地报告 DLL 在探测链中插入的位置,而程序集解析事件将隐藏代码中的所有逻辑和规则。 唯一真正的缺点是,使用 app.config 意味着更改不会生效,直到您的应用程序重新读取配置文件(通常是应用程序重新启动),但如果您正在对插件进行 AppDomain 隔离,您甚至可以解决这个问题。

关于您的“core.dll”问题... 我想,对于你来说,这是一个比较简单的问题。核心合约 DLL 中的所有内容都应该存在于版本命名空间下(参见上文,Company.Product.v1_0 等),因此您的 DLL 也包含版本号实际上是有意义的。这将消除部署到 bin 文件夹时 DLL 相互覆盖的问题。 不要依赖 GAC——从长远来看,这将是一个痛苦。令人遗憾的是,开发人员似乎总是忘记 GAC 会覆盖所有内容,这可能会成为调试噩梦 - 它还会影响您在权限方面的部署场景。

如果您确实需要保持 DLL 名称相同 - 您可以在应用程序中创建一个“本地 gac”,这将使您能够以不会互相覆盖的方式存储 DLL,但仍然可以通过以下方式解析:运行时。查看 app.config 中的“绑定重定向”(在这里查看我的答案)。这可以与应用程序的 bin 文件夹下的“伪 GAC 文件夹结构”结合使用。然后,您的应用程序将能够找到所需的任何版本的 DLL,而无需任何自定义程序集解析代码逻辑。 我将使用您的应用程序部署所有以前支持的 core.dll 版本。 如果您的应用程序达到版本 9,并且您决定也支持版本 7 和 8 的插件,那么您只需要包含 Core.7.dll、Core.8.dll 和 Core.9.dll。您的插件加载逻辑应该检测对旧版本 Core 的依赖关系,并提醒用户该插件不兼容。

这个话题有很多内容,如果我想到任何与你的事业相关的其他内容,我会回来查看......

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

加载程序集和版本控制 的相关文章

  • GetType() 在 Type 实例上返回什么?

    我在一些调试过程中遇到了这段代码 private bool HasBaseType Type type out Type baseType Type originalType type GetType baseType GetBaseTyp
  • 查找进程的完整路径

    我已经编写了 C 控制台应用程序 当我启动应用程序时 不使用cmd 我可以看到它列在任务管理器的进程列表中 现在我需要编写另一个应用程序 在其中我需要查找以前的应用程序是否正在运行 我知道应用程序名称和路径 所以我已将管理对象搜索器查询写入
  • 如何填充 ToolStripComboBox?

    我发现它很难将数据绑定到ToolStripComboBox 好像没有这个ValueMember and DisplayMember特性 怎么绑定呢 访问toolstripcombobox中包装的组合框并访问其ValueMember Disp
  • 为什么在 WebApi 上下文中在 using 块中使用 HttpClient 是错误的?

    那么 问题是为什么在 using 块中使用 HttpClient 是错误的 但在 WebApi 上下文中呢 我一直在读这篇文章不要阻止异步代码 https blog stephencleary com 2012 07 dont block
  • 使用可变参数包类型扩展的 C++ 函数调用者包装器

    我绑定了一些 API 并且绑定了一些函数签名 如下所示 static bool WrapperFunction JSContext cx unsigned argc JS Value vp 我尝试将对象和函数包装在 SpiderMonkey
  • 为什么可以通过ref参数修改readonly字段?

    考虑 class Foo private readonly string value public Foo Bar ref value private void Bar ref string value value hello world
  • 打破 ReadFile() 阻塞 - 命名管道 (Windows API)

    为了简化 这是一种命名管道服务器正在等待命名管道客户端写入管道的情况 使用 WriteFile 阻塞的 Windows API 是 ReadFile 服务器已创建启用阻塞的同步管道 无重叠 I O 客户端已连接 现在服务器正在等待一些数据
  • 如何在 Qt 应用程序中通过终端命令运行分离的应用程序?

    我想使用命令 cd opencv opencv 3 0 0 alpha samples cpp cpp example facedetect lena jpg 在 Qt 应用程序中按钮的 clicked 方法上运行 OpenCV 示例代码
  • 在mysql连接字符串中添加应用程序名称/程序名称[关闭]

    Closed 这个问题需要细节或清晰度 help closed questions 目前不接受答案 我正在寻找一种解决方案 在连接字符串中添加应用程序名称或程序名称 以便它在 MySQL Workbench 中的 客户端连接 下可见 SQL
  • 使 Guid 属性成为线程安全的

    我的一个类有一个 Guid 类型的属性 该属性可以由多个线程同时读写 我的印象是对 Guid 的读取和写入不是原子的 因此我应该锁定它们 我选择这样做 public Guid TestKey get lock testKeyLock ret
  • 打印大型 WPF 用户控件

    我有一个巨大的数据 我想使用 WPF 打印 我发现WPF提供了一个PrintDialog PrintVisual用于打印派生的任何 WPF 控件的方法Visual class PrintVisual只会打印一页 因此我需要缩放控件以适合页面
  • String.Empty 与 "" [重复]

    这个问题在这里已经有答案了 可能的重复 String Empty 和 有什么区别 https stackoverflow com questions 151472 what is the difference between string
  • OpenGL:仅获取模板缓冲区而没有深度缓冲区?

    我想获取一个模板缓冲区 但如果可能的话 不要承受附加深度缓冲区的开销 因为我不会使用它 我发现的大多数资源表明 虽然模板缓冲区是可选的 例如 排除它以利于获得更高的深度缓冲区精度 但我还没有看到任何请求并成功获取仅 8 位模板缓冲区的代码
  • 实体框架中的“it”是什么

    如果以前有人问过这个问题 请原谅我 但我的任何搜索中都没有出现 它 我有两个数据库表 Person 和 Employee 对每个类型的表进行建模 例如 Employee is a Person 在我的 edmx 设计器中 我定义了一个实体
  • 这个可变参数模板示例有什么问题?

    基类是 include
  • 堆栈是向上增长还是向下增长?

    我在 C 中有这段代码 int q 10 int s 5 int a 3 printf Address of a d n int a printf Address of a 1 d n int a 1 printf Address of a
  • GCC 的“-Wl,option”和“-Xlinker option”语法之间有区别吗?

    我一直在查看一些配置文件 并且看到它们都被使用 尽管在不同的体系结构上 如果您在 Linux 机器上使用 GCC 将选项传递给链接器的两种语法之间有区别吗 据我所知 阅读 GCC 手册时 他们的解释几乎相同 From man gcc Xli
  • 如何减少具有多个单元的 PdfPTable 的内存消耗

    我正在使用 ITextSharp 创建一个 PDF 它由单个 PdfTable 组成 不幸的是 对于特定的数据集 由于创建了大量 PdfPCell 我遇到了内存不足异常 我已经分析了内存使用情况 我有近百万个单元格的 1 2 在这种情况下有
  • 如何将十六进制字符串转换为无符号长整型?

    我有以下十六进制值 CString str str T FFF000 如何将其转换为unsigned long 您可以使用strtol作用于常规 C 字符串的函数 它使用指定的基数将字符串转换为 long long l strtol str
  • OpenCV SIFT 描述符关键点半径

    我正在深入研究OpenCV的SIFT描述符提取的实现 https github com Itseez opencv blob master modules nonfree src sift cpp 我发现了一些令人费解的代码来获取兴趣点邻域

随机推荐

  • 从 Uri 获取单独的查询参数[重复]

    这个问题在这里已经有答案了 我有一个 uri 字符串 例如 是否有一个现有函数可以将查询参数字符串转换为字典 就像 ASP NET Context Request 那样 我正在编写一个控制台应用程序而不是一个 Web 服务 因此没有 Con
  • 从目录中选择随机文件

    我正在尝试创建一个网站 用户可以提交照片 然后在另一个页面上随机地一张一张地查看其他照片 我有一个名为 uploads 的目录 用于提交图片 我在读取文件中的图片时遇到问题 我只想从上传目录中随机选择一张图片并将其显示在页面上 任何建议表示
  • 使用 Google App 脚本在工作表中创建日历活动并想要添加参与者

    我正在尝试使用 Google App Script 在工作表中创建日历事件 我对此很陌生 该表包含活动的详细信息 日期 时间 活动标题和嘉宾名单 以及日历 ID 这是培训日历 我想让最终用户轻松填写表格上的信息 单击 立即安排 然后运行脚本
  • Hartl 对 config.serve_static_files 的sample_app 警告,并且测试已定义

    我正在尝试测试 Hartl 的sample app 这是我运行后收到的消息bundle exec rake test DEPRECATION WARNING The configuration option config serve sta
  • Lambdify 参数积分

    我有以下问题 我想要lambdify a sympy包含参数积分的表达式 例如Integral tanh a x x 0 1 我尝试进行手动实施像这儿 我们想要的本质上是将积分转换为如下形式 lambda theta quad lambda
  • 用于调整/校准启发式算法属性的软件

    今天看到有一个软件叫WinCalibra 向下滚动一点 它可以将具有属性的文本文件作为输入 然后 该程序可以根据算法的输出值优化输入属性 看这张纸或用户文档以获取更多信息 请参阅上面的链接 遗憾的是 doc 是一个压缩的 exe 您知道在
  • 使用 java 流将两个相同大小(和不同类型)的列表组合成域对象列表

    我有两个相同大小的列表ids and results我想用域对象创建新列表 List
  • IE9 + css:固定标头表的问题

    所以 我认为这是一个 CSS 问题 但基本上 我提供的 HTML 在反应式布局中包含一个固定的标头表 Code http jsfiddle net JpRQh 10 有3行数据 但在IE9中 表格行数似乎疯狂高 并且滚动条已被禁用 我在固定
  • 为什么 SQL 中没有“product()”聚合函数? [复制]

    这个问题在这里已经有答案了 当有 Sum min max avg count 函数时 有人可以帮助理解为什么没有 Product 内置函数 这个聚合函数最有效的用户实现是什么 谢谢 三位一体 如果您有可用的指数函数和对数函数 则 PRODU
  • 是否有一个函数可以检查字符串中的字符是否是字母表中的字母? (迅速)

    我正在将 python 程序转换为 swift 其中一个部分使用 for 循环来保留字符串中的每个字符 如果它是字母 在python中 就像使用 isalpha 一样简单 swift中有什么可以做到这一点吗 python 中的代码 word
  • PHP、PDO、MySQL,注意:尝试获取非对象的属性

    我仍在尝试围绕 php 进行思考 如果这是一个简单的错误 那么很抱歉 我已经搜索了很长一段时间 只能设法得到不同的错误 例如 未定义的索引 我试图做的是有一个函数可以从表中获取数据 目前它不包含很多数据 但最终将包含每个主网页的所有内容 它
  • NSParagraphStyle iOS - 如何检测块和列表?

    我有一个 NSParagraphStyle 对象 当使用 NSLog 检查时 它包含列表和块 但是似乎完全没有办法访问它们 我使用 OS X 创建文档并将其传输到 iOS 的文本视图中 其中列表和表格可以成功重新创建并显示在 NSLog 中
  • JavaScript 正则表达式原型

    为什么 Chrome 的控制台显示 对于 RegExp 的原型 console log RegExp prototype console log a proto 这是特定于实现的吗 IE 正在显示 这只是出于好奇而提出的问题 当我遇到这个问
  • 如何让 IBM DB2 提供程序与 Entity Framework 4.0 一起使用

    有人可以告诉我如何让 DB2 提供程序显示在 更改数据源 对话框窗口中吗 Steps 右键单击 edmx 设计图面 选择 从数据库更新模型 在更新向导中 单击 新连接 在 数据源 文本框旁边 单击 更改 在 更改数据源 窗口中 我仅在列表中
  • 没有主键可供引用的外键

    我有以下两张表 CREATE TABLE parent c1 INTEGER CREATE TABLE child c1 INTEGER c2 INTEGER c3 INTEGER CONSTRAINT fk c3 FOREIGN KEY
  • 读取VC++ CArchive二进制格式(或Java读取(CObArray))

    是否有关于用于序列化各种 MFC 数据结构的二进制格式的明确文档 我已经能够在十六进制编辑器中查看我自己的一些类 并使用 Java 的 ByteBuffer 类来读取它们 通过自动字节顺序转换等 然而 我目前在尝试引入 CObArray 数
  • OpenGL 离屏渲染

    我有一个应用程序 可以创建 3D 模型并从中导出图像 我用这个例子来做到这一点 include
  • 在 Glance Widget 中复制 Canvas 的最佳方式?

    在 Compose 中我们有方法Canvas 画线等 Glance中有类似的功能吗 如果没有 复制 Canvas 功能的最佳方法是什么 None
  • 为什么 sphinx 在同一行上格式化我的文档字符串参数

    我对 sphinx 比较陌生 想为我的项目生成文档 我的函数之一的示例可以在 Predict py 中找到 def arima rolling forecast training set testing set order solver l
  • 加载程序集和版本控制

    我正在考虑通过提供一些预定义的接口来向现有应用程序添加一些可扩展性 这些接口可以通过放置在特定位置并由应用程序拾取的 插件 来实现 应用程序的核心很少更新 而插件更新和部署则更加频繁 基本上 有这样的设置 in core assembly