装载程序集和版本控制

4
我正在考虑通过提供一些预定义的接口,将可由“插件”实现并放置在特定位置并被应用程序拾取的可扩展性添加到现有应用程序中。应用程序的核心很少更新,而插件的更新和部署更加频繁。
因此,基本上是这样设置的:
// in core assembly (core.dll)
public interface IReportProvider{
     string GenerateReport();
}

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

两个项目都是同一构建过程的一部分,核心应用程序会单独部署,而插件则在后期添加。我的问题出在组装版本和随时间解决。假设已部署核心.dll v1且我想添加一个插件。如果plugin.dll引用core.dll v1,则这很好。然而,如果plugin.dll针对更高版本的core.dll进行编译(例如v2),插件无法加载,因为它引用core.dll v2,但已部署的版本仅有core.dll v1。这是合理且预期的行为,但在项目设置方面给我带来了几个问题,即不能通过重新运行生成并添加新插件(现在有较新版本依赖项)来进行插件的开发/更新。我可以看到解决问题的几个选项,但没有一个像我希望的那样简单:向web.config添加绑定重定向,指示所有核心.dll v1+引用解析为core.dll v1引用;创建包含接口定义的“contracts.dll”程序集,并使该特定程序集的版本号在构建之间保持不变;对插件进行部署版的核心.dll编译(以某种方式在开发中引用已部署版本)。如上所述,这些选项都不是我真正期望的低效果方法,希望有人有更巧妙的解决方案?

没有版本控制和 DLL Hell 的魔法解决方案。CLR 已经有了很好的保护措施,绕过它应该是最后考虑的事情。相反,应该专注于一个稳定的部署方案。 - Hans Passant
1
为什么 "Contracts.dll" 不简单呢?我认为这是一个好的解决方案。合约库永远不会改变,core.dll 和 plugin.dll 将使用它作为通信的 "中间件"。 - Roberto Conte Rosito
3个回答

3
我在这个领域工作了很长时间,一直发现需要明确界定范围内和范围外的内容。想要最大程度地扩展性是有风险的,因为这些都是有代价的。这个代价可能会体现在编译时,例如约定、最佳实践和自动构建检查;或者在运行时变得更加复杂。
对于你列出的选项,有以下解决方案:
1. 绑定重定向只是部分解决方案。它们可以让你的程序将一个DLL的版本替换为另一个版本,但它并不能神奇地解决方法改变的问题。MissingMethodException?还有一些你可能没有想到的东西,比如依赖链。 dependency chains 你可以看到,虽然应用程序把依赖“A”视为版本1对象,但在内部创建的是从较新版本创建的对象,该对象被传回应用程序并转换为v1.0,导致异常。这可能很难处理,而且只是其中的一个风险。
2. 在构建过程中保持合同程序集版本相同 这可能很优雅,但它只是将复杂性从构建时延迟到运行时。 你需要勤奋地确保你的更改不会在不同版本之间破坏兼容性。更不用说随着应用程序的老化,你将收集大量声明在这些合同中,你将想要废弃它们。最终,这个文件将变得庞大、笨重,并且对开发人员来说非常令人困惑——这还没有考虑所有实体合同!
3. 我不太确定你是什么意思,以及它如何涵盖你的问题空间。
你可以采取另一种方法,我们就是为每个“SDK”的主要发布创建一个新的合同。这在政治上有一些优势,因为它在一段时间内固定了主要功能,这意味着我们可以保持对功能请求的合理期望水平,而超出这个范围(需要新一代合同)的任何东西都被推迟到“下一个主要发布”。 然而,这确实需要在功能设计方面保持勤奋——在编写合同时要事先考虑一些明显的要求,但我真的认为这是不言而喻的……它们被称为“合同”是有原因的。 每个新合同都将存在于一个版本命名空间中(Company.Product.Contracts.v1_0,Company.Product.Contracts.v1_1)。
我不会链接我的合同(每个新版本的合同都继承了上一个版本的合同)。这样做会让你回到保持版本号相同的问题,你永远无法完全摆脱功能而不完全破坏链条。
当您的插件加载时,它可以询问主机支持的功能级别(合同版本),并根据是旧主机/新插件场景:要么编写插件以减少其运行时功能以处理较低的主机能力,要么拒绝加载。 您可能应该进行这些检查,因为没有什么魔法可以让您的插件利用主机中不存在的功能!微软的MAF框架试图通过使用shim基础设施来实现这一点,但对于大多数人而言,这会导致大量复杂性。
所以,您需要考虑以下几点: 1.界定您的可扩展性要求!您想要实现的每个内容都需要不断的维护成本。 2.考虑如何废弃功能 3.不要忘记,您的合同将包含实体以及逻辑合同,这些与逻辑合同有稍微不同的考虑,因为它们经常传递。 4.仔细考虑每个兼容性检查是否最好在编译时执行而不是运行时(或反之亦然) 5.版本编号!程序集版本号非常适合运行时行为,文件版本号可帮助您将DLL跟踪回版本控制源 - 利用两者! 6.如果您正在执行自定义DLL解析,请使用app.config来定义自定义DLL位置,而不是程序集解析事件。配置方法更加可预测,而且声明在易于阅读的Xml中!融合日志查看器还将很好地报告您的DLL在探测链中插入的位置,而程序集解析事件将在代码中隐藏所有逻辑和规则。 唯一真正的缺点是使用app.config意味着更改不会生效,直到您的应用程序重新读取配置文件(通常是应用程序重新启动),但如果您正在执行插件的AppDomain隔离,甚至可以避免此问题。
关于您的“core.dll”问题... 我认为对于您来说,这是一个相对简单的问题。您的核心合同DLL中的所有内容都应存在版本命名空间下(请参见上文,公司.产品.v1_0等),因此它实际上包含一个版本号也是有道理的。这将消除部署到bin文件夹时DLL互相覆盖的问题。 不要依赖GAC - 这将在长期运行中带来痛苦。虽然这很遗憾,但开发人员似乎总是忘记GAC会覆盖一切,这可能会成为调试噩梦 - 它还将影响您的权限相关的部署场景。
如果你需要保持DLL名称不变 - 你可以在应用程序中创建一个'本地gac',这将使你能够以这样的方式存储你的DLL,使它们不会相互覆盖,但仍然可以被运行时解析。请在app.config中查看'binding redirect' (请看我的答案)。这可以与应用程序bin文件夹下的“伪GAC文件夹结构”相结合。你的应用程序将能够定位所需的任何DLL版本,而无需任何自定义的程序集解析代码逻辑。
我将部署所有先前支持的core.dll版本,并随同你的应用程序一起使用。
如果你的应用程序已经到达9.0版本,并决定同时支持插件的7.0和8.0版本,则只需包含Core.7.dll、Core.8.dll和Core.9.dll。你的插件加载逻辑应该检测对旧版Core的依赖关系,并提示用户该插件不兼容。
这个主题很大,如果我想到任何与你的事业相关的内容,我会再回来检查...

不错的绘画。用的是平板电脑吗? - Wernight
哈哈哈 - 不是,我们使用Wacom平板电脑 - 我们对文件有快速交付的需求,因此不必在PowerPoint等软件上浪费时间 - 只需快速完成,如果需要修改,就可以进行修订...更加高效。 - Adam

0
无论您因何原因避免使用您的3个点(我认为它们更可靠和恰当),您都可以利用程序集解析事件
在您的core.dll中包含以下代码。
static Assembly AppDomain_AssemblyResolve(object sender, ResolveEventArgs args)
       {
            //replace the if check with more reliable check such as matching the public key as well
            if (args.Name.Contains(Assembly.GetExecutingAssembly().GetName().Name))
            {
                Console.WriteLine("Resolved " + args.Name + " as " + Assembly.GetExecutingAssembly().GetName());
                return Assembly.GetExecutingAssembly();
            }

            return null;
       }

在尝试加载插件之前,请绑定上述处理程序。如果您在当前Appdomain中加载插件,则将其绑定到当前域的AssemblyResolve

例如:

[SecurityPermission(SecurityAction.Demand, ControlAppDomain = true)]
public static void LoadPlugins()
{
    AppDomain.CurrentDomain.AssemblyResolve += AppDomain_AssemblyResolve;
    Assembly pluginAssembly = AppDomain.CurrentDomain.Load("MyPlugin");
}

0

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