加载程序集和版本控制

时间:2012-08-29 12:12:22

标签: c# .net plugins versioning assembly-resolution

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

所以基本上,有这样的设置:

// 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版本构建插件(以某种方式引用开发中的已部署版本)
  4. 如上所述,对我来说,这些都不是真正的低水果,我希望有人有更快的解决方案吗?

3 个答案:

答案 0 :(得分:3)

我在这个领域已经进行了相当广泛的工作,并且一直发现你需要在范围的内部和外部确定一些边界。想要最具可扩展性是一种风险,但这一切都需要付出代价。这个成本要么是编译时间,要么是约定,最佳实践,也许是自动构建检查 - 或者它将是一个复杂的运行时。

要解决您列出的选项:

  1. 绑定重定向只是实现解决方案的部分工具。它们将允许您的程序“滑动”。一个版本的DLL代替另一个版本,但它不会神奇地解决方法改变时发生的问题。 MissingMethodException? 在这里,你可能没有的东西,也就是依赖链。 dependency chains 您可以看到,虽然该应用程序正在处理依赖性,但A'作为版本1对象,在内部它正在从更高版本创建一些东西,该版本将被传递回应用程序并转换为v1.0 - 导致异常。 这可能很难处理 - 而且只是其中一个风险。

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

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

  4. 我们可以采取的另一种方法是为“SDK”的每个主要版本创建一份新合同。这具有一些政治优势,因为它在一段时间内修复了主要功能,这意味着我们可以将特征请求保持在合理的期望水平,并且除此之外的任何事情(需要新一代合同)都会被推迟到'下一个主要版本'。 然而,这需要在设计你的功能时勤勉尽责 - 用一些预先考虑的方式写下你的合同,这样你就可以预先考虑最明显的要求 - 但我真的觉得这无处不在......他们被称为&#39 ;合同'因为某种原因。 每个新合同都将存在于版本名称空间中(Company.Product.Contracts.v1_0,Company.Product.Contracts.v1_1)。

    我不会链接我的合同(每个新版本的合同继承了最后一个)。这样做会让你回到问题,保持版本号相同,你永远不能完全摆脱功能而不会完全破坏链。

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

    所以,你需要考虑的一些事情是:

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

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

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

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

答案 1 :(得分:0)

无论你的理由是什么,以避免你的3分(我认为更可靠和更贴切),你可以利用Assembly Resolve Event

在您的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");
}

答案 2 :(得分:0)

您可以使用Binding redirects