演练:在宿主发生变化时启用向后兼容性

本演练描述演练:创建可扩展的应用程序中介绍的管线版本 2。 版本 2 向宿主提供它所支持的算术运算的逗号分隔字符串,从而提供更多计算功能。 然后宿主可以选择一个运算并提交等式,以供外接程序计算。

该管线拥有新的宿主和新的协定。 为了使外接程序的版本 1 能够与新宿主和协定一起工作,管线包含了用于版本 1 的外接程序视图和外接程序端适配器,该适配器可以将数据从旧外接程序视图转换为新协定。 下面的插图显示了两个外接程序如何与同一宿主一起工作。

新宿主、旧外接程序

管线方案:新主机、旧外接程序。

外接程序管线方案中也介绍了此管线。

本演练介绍了以下任务:

  • 创建 Visual Studio 解决方案。

  • 创建管线目录结构。

  • 创建协定和视图。

  • 创建外接程序端适配器,包括新版本外接程序的适配器和版本 1 外接程序的适配器。

  • 创建宿主端适配器。

  • 创建宿主。

  • 创建外接程序。

  • 部署管线。

  • 运行宿主应用程序。

本演练还演示如何使用抽象基类来定义视图,并且演示这类视图与接口所定义的视图是兼容的。 建议使用接口。

备注

本演练中所示的某些代码包含外部命名空间引用。演练步骤准确反映了 Visual Studio 中所需要的引用。

可以在 Managed Extensibility and Add-In Framework site on CodePlex(CodePlex 上的托管扩展性和外接程序框架站点)上找到更多代码示例,以及有关用于生成外接程序管线的工具的客户技术预览。

系统必备

您需要以下组件来完成本演练:

  • Visual Studio.

  • 演练:创建可扩展的应用程序中介绍了版本 1 管线。 由于版本 2 使用版本 1 中开发的管线段,因此,在执行本主题中的步骤之前,必须先开发并部署版本 1 管线。

创建 Visual Studio 解决方案

使用 Visual Studio 中的解决方案来包含您的管线段的项目。

创建管线解决方案

  1. 在 Visual Studio,新建一个名为 Calc2Contract 的项目。 使该项目基于**“类库”**模板。

  2. 将解决方案命名为 CalculatorV2。

创建管线目录结构

外接程序模型要求管线段程序集放置在指定的目录结构中。

创建管线目录结构

  • 如果还未完成此操作,请将 CalcV2 文件夹添加到在演练:创建可扩展的应用程序中创建的管线文件夹结构中。 CalcV2 文件夹将用于保存新版本外接程序。

    Pipeline
      AddIns
        CalcV1
        CalcV2
      AddInSideAdapters
      AddInViews
      Contracts
      HostSideAdapters
    

    不必将管线文件夹结构放到应用程序文件夹中;这样做只是为了方便在这些演练中的操作。 如果在第一个演练中将管线文件夹结构放在了其他位置,则在本演练中,请遵循相同的模式。 请参见管线开发要求中对管线目录要求的论述。

创建协定和视图

该管线的协定段定义具有以下两个方法的 ICalc2Contract 接口:

  • GetAvailableOperations 方法。

    此方法将外接程序支持的数学运算的字符串返回到宿主。 版本 2 支持五种运算,此方法返回字符串“+,-,*,/,**”,其中“**”表示 Pow 运算。

    在外接程序和宿主视图中,此方法命名为 Operations,而不是 GetAvailableOperations。

    通过将方法调用转换为适配器的属性,可以将协定上的方法公开为视图的属性。

  • Operate 方法。

    宿主调用此方法来提交等式,以供外接程序计算,并返回结果。

创建协定

  1. 在名为 CalculatorV2 的 Visual Studio 解决方案中,打开 Calc2Contract 项目。

  2. 在**“解决方案资源管理器”**中,将对下面程序集的引用添加到 Calc2Contract 项目中:

    System.AddIn.Contract.dll

    System.AddIn.dll

  3. 在**“解决方案资源管理器”中,排除添加到新“类库”**项目中的默认类。

  4. 使用**“接口”模板将新项添加到项目中。 在“添加新项”**对话框中,将该接口命名为 ICalc2Contract。

  5. 在接口文件中,添加对 System.AddIn.ContractSystem.AddIn.Pipeline 的命名空间引用。

  6. 使用下面的代码完成该协定段。 请注意,此接口必须具有 AddInContractAttribute 特性。

    
    Imports Microsoft.VisualBasic
    Imports System
    Imports System.Collections.Generic
    Imports System.Text
    Imports System.AddIn.Contract
    Imports System.AddIn.Pipeline
    
    Namespace CalculatorContracts
        <AddInContract()> _
        Public Interface ICalc2Contract
            Inherits IContract
            Function GetAvailableOperations() As String
            Function Operate(ByVal operation As String, ByVal a As Double, ByVal b As Double) As Double
        End Interface
    End Namespace
    
    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.AddIn.Contract;
    using System.AddIn.Pipeline;
    
    namespace CalculatorContracts
    {
        [AddInContract]
        public interface ICalc2Contract : IContract
        {      
            string GetAvailableOperations();
            double Operate(String operation, double a, double b);
        }
    }
    

由于外接程序视图和宿主视图具有相同的代码,因此可以轻松地同时创建这两个视图。 它们只在一个方面不同:外接程序视图需要 AddInBaseAttribute 特性;而外接程序的宿主视图不需要任何特性。

创建版本 2 的外接程序视图

  1. 将一个名为 Calc2AddInView 的新项目添加到 CalculatorV2 解决方案中。 使该项目基于**“类库”**模板。

  2. 在**“解决方案资源管理器”**中,将对 System.AddIn.dll 的引用添加到 Calc2AddInView 项目中。

  3. 将该类重命名为 Calculator2。

  4. 在类文件中,添加对 System.AddIn.Pipeline 的命名空间引用。

  5. 使 Calculator2 成为 abstract 类(在 Visual Basic 中为 MustInherit 类)。

  6. 对此外接程序视图使用下面的代码。 请注意,此类必须具有 AddInBaseAttribute 特性。

    
    Imports Microsoft.VisualBasic
    Imports System
    Imports System.Collections.Generic
    Imports System.Text
    Imports System.AddIn.Pipeline
    
    Namespace CalcAddInViews
        <AddInBase()> _
        Public MustInherit Class Calculator2
            Public MustOverride ReadOnly Property Operations() As String
    
            Public MustOverride Function Operate(ByVal operation As String, ByVal a As Double, ByVal b As Double) As Double
        End Class
    End Namespace
    
    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.AddIn.Pipeline;
    
    namespace CalcAddInViews
    {
        [AddInBase]
        public abstract class Calculator2
        {
            public abstract string Operations
            {
                get;
            }
    
            public abstract double Operate(string operation, double a, double b);
        }
    }
    

创建外接程序的宿主视图

  1. 将一个名为 Calc2HVA 的新项目添加到 CalculatorV2 解决方案中。 使该项目基于**“类库”**模板。

  2. 将该类重命名为 Calculator。

  3. 使 Calculator 成为 abstract 类(在 Visual Basic 中为 MustInherit 类)。

  4. 在类文件中,使用下面的代码创建外接程序的宿主视图。

    Imports Microsoft.VisualBasic
    Imports System
    Namespace CalcHVAs
    
        Public MustInherit Class Calculator
    
            Public MustOverride ReadOnly Property Operations() As String
    
            Public MustOverride Function Operate(ByVal operation As String, ByVal a As Double, ByVal b As Double) As Double
        End Class
    End Namespace
    
    namespace CalcHVAs {
    
    
        public abstract class Calculator {
    
            public abstract string Operations
            {
                get;
            }
    
            public abstract double Operate(string operation, double a, double b);
        }
    }
    

为了演示版本 1 外接程序如何用于新宿主,解决方案必须包含为版本 1 的计算器外接程序创建的外接程序视图。

从版本 1 添加外接程序视图项目

  1. 在**“解决方案资源管理器”**中,右击 CalculatorV2 解决方案。

  2. 单击**“添加”,然后单击“现有项目”**。

  3. 转至包含 CalculatorV1 解决方案的文件夹,并选择 Calc1AddInView 项目的项目文件。

创建外接程序端适配器

此外接程序端适配器由两个“视图到协定”适配器组成:一个将版本 2 外接程序视图与版本 2 协定适配,另一个将版本 1 外接程序视图与版本 2 协定适配。

在此管线中,外接程序向宿主提供服务,类型从外接程序流向宿主。 由于没有类型从宿主流向外接程序,因此不必包含“协定到视图”适配器。

创建外接程序端适配器

  1. 将一个名为 Calc2AddInSideAdapter 的新项目添加到 CalculatorV2 解决方案中。 使该项目基于**“类库”**模板。

  2. 在**“解决方案资源管理器”**中,将对下面程序集的引用添加到 Calc2AddInSideAdapter 项目中:

    System.AddIn.dll

    System.AddIn.Contract.dll

  3. 添加对下列项目的项目引用:

    Calc2AddInView

    Calc2Contract

  4. 选择每个项目引用,然后在**“属性”中将“复制本地”设置为“False”,以禁止将引用的程序集复制到本地生成文件夹。 这些程序集将放置于管线目录中,如本演练后面部分中的“部署管线”过程中所述。 在 Visual Basic 中,使用“项目属性”“引用”选项卡将两个项目引用的“复制本地”设置为“False”**。

  5. 将项目的默认类重命名为 CalculatorViewToContractAddInSideAdapter。

  6. 在类文件中,添加对 System.AddIn.Pipeline 的命名空间引用。

  7. 在类文件中,添加以下相邻管线段的命名空间引用:CalcAddInViews 和 CalculatorContracts。 (在 Visual Basic 中,除非已在 Visual Basic 项目中关闭了默认命名空间,否则这两个命名空间引用为 Calc2AddInView.CalcAddInViews 和 Calc2Contract.CalculatorContracts。)

  8. 对此外接程序端适配器使用下面的代码。 其实现模式类似于版本 1 的外接程序端适配器,尽管协定接口有很大差异。

    
    Imports Microsoft.VisualBasic
    Imports System
    Imports System.Collections.Generic
    Imports System.Text
    Imports System.AddIn.Pipeline
    Imports System.AddIn.Contract
    Imports Calc2Contract.CalculatorContracts
    Imports Calc2AddInView.CalcAddInViews
    
    Namespace CalculatorContractsAddInAdapters
    
        <AddInAdapterAttribute()> _
        Public Class CalculatorViewToContractAddInAdapter
            Inherits ContractBase
            Implements ICalc2Contract
    
            Private _view As Calculator2
    
            Public Sub New(ByVal calculator As Calculator2)
                _view = calculator
            End Sub
    
            Public Function GetAvailableOperations() As String Implements ICalc2Contract.GetAvailableOperations
                Return _view.Operations
            End Function
    
            Public Function Operate(ByVal operation As String, ByVal a As Double, ByVal b As Double) As Double Implements ICalc2Contract.Operate
                Return _view.Operate(operation, a, b)
            End Function
        End Class
    End Namespace
    
    using System.AddIn.Pipeline;
    using CalcAddInViews;
    using CalculatorContracts;
    
    
    namespace CalcAddInSideAdapters {
    
    
        [AddInAdapterAttribute]
        public class CalculatorViewToContractAddInAdapter : ContractBase, ICalc2Contract {
    
            private Calculator2 _view;
    
            public CalculatorViewToContractAddInAdapter(Calculator2 calculator)
            {
                _view = calculator;
            }
    
            public string GetAvailableOperations()
            {
                return _view.Operations;
            }
    
            public double Operate(string operation, double a, double b)
            {
                return _view.Operate(operation, a, b);
            }
    
        }
    }
    

为了使版本 1 外接程序能够与新宿主进行通信,版本 1 外接程序的管线需要一个将数据从旧外接程序视图转换到新协定的外接程序端适配器。

创建版本 1 到版本 2 外接程序端适配器

  1. 将一个名为 Calc2V1toV2AddInSideAdapter 的新项目添加到 CalculatorV2 解决方案中。 使该项目基于**“类库”**模板。

  2. 在**“解决方案资源管理器”**中,将对下面程序集的引用添加到 Calc2V1toV2AddInSideAdapter 项目中:

    System.AddIn.dll

    System.AddIn.Contract.dll

  3. 添加对下列项目的项目引用:

    Calc1AddInView

    Calc2Contract

  4. 选择每个项目引用,然后在**“属性”中将“复制本地”设置为“False”,以禁止将引用的程序集复制到本地生成文件夹。 在 Visual Basic 中,使用“项目属性”“引用”选项卡将两个项目引用的“复制本地”设置为“False”**。

  5. 将项目的默认类重命名为 Calc2V1ViewToV2ContractAddInSideAdapter。

  6. 在类文件中,添加对 System.AddIn.Pipeline 的命名空间引用。

  7. 在类文件中,添加以下相邻管线段的命名空间引用:CalcAddInViews 和 CalculatorContracts。 (在 Visual Basic 中,除非已在 Visual Basic 项目中关闭了默认命名空间,否则这两个命名空间引用为 Calc1AddInView.CalcAddInViews 和 Calc2Contract.CalculatorContracts。)请注意,视图命名空间来自版本 1,而协定来自版本 2。

  8. AddInAdapterAttribute 特性应用于 Calc2V1ViewToV2ContractAddInSideAdapter 类,以将其标识为外接程序端适配器。

  9. 使 Calc2V1ViewToV2ContractAddInSideAdapter 类继承 ContractBase(后者提供 IContract 接口的默认实现),并实现管线的版本 2 协定接口 ICalc2Contract。

  10. 添加一个公共构造函数,它接受一个 ICalculator,将其缓存在私有字段中,并调用基类构造函数。

  11. 若要实现 ICalc2Contract 的成员,必须调用传递给构造函数的 ICalculator 实例的相应成员,然后返回结果。 由于版本 1 和版本 2 协定接口之间存在差异,必须使用 switch 语句(Visual Basic 中为 Select Case 语句)将视图 (ICalculator) 与协定 (ICalc2Contract) 适配。

    下面的代码显示已完成的外接程序端适配器。

    Imports System
    Imports System.Collections.Generic
    Imports System.Text
    Imports System.AddIn.Pipeline
    Imports Calc1AddInView.CalcAddInViews
    Imports Calc2Contract.CalculatorContracts
    
    Namespace AddInSideV1toV2Adapter
    
    
        <AddInAdapter()> _
        Public Class Calc2V1ViewToV2ContractAddInSideAdapter
            Inherits ContractBase
            Implements ICalc2Contract
    
            Private _view As ICalculator
    
            Public Sub New(ByVal calc As ICalculator)
                MyBase.New()
                _view = calc
            End Sub
    
            Public Function GetAvailableOperations() As String Implements ICalc2Contract.GetAvailableOperations
                Return "+, -, *, /"
            End Function
    
            Public Function Operate(ByVal operation As String, ByVal a As Double, ByVal b As Double) _
             As Double Implements ICalc2Contract.Operate
                Select Case (operation)
                    Case "+"
                        Return _view.Add(a, b)
                    Case "-"
                        Return _view.Subtract(a, b)
                    Case "*"
                        Return _view.Multiply(a, b)
                    Case "/"
                        Return _view.Divide(a, b)
                    Case Else
                        Throw New InvalidOperationException(("This add-in does not support: " + operation))
                End Select
            End Function
        End Class
    End Namespace
    
    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.AddIn.Pipeline;
    using CalcAddInViews;
    using CalculatorContracts;
    
    
    namespace AddInSideV1toV2Adapter
    {
        [AddInAdapter]
        public class Calc2V1ViewToV2ContractAddInSideAdapter : ContractBase, ICalc2Contract
        {
            ICalculator _view;
    
            public Calc2V1ViewToV2ContractAddInSideAdapter(ICalculator calc)
            {
                _view = calc;
            }
    
            public string GetAvailableOperations()
            {
                return  "+, -, *, /" ;
            }
    
            public double Operate(string operation, double a, double b)
            {
                switch (operation)
                {
                    case "+":
                        return _view.Add(a, b);
                    case "-":
                        return _view.Subtract(a, b);
                    case "*":
                        return _view.Multiply(a, b);
                    case "/":
                        return _view.Divide(a, b);
                    default:
                        throw new InvalidOperationException("This add-in does not support: " + operation);
                }
            }
    
        }
    }
    

创建宿主端适配器

此宿主端适配器由一个“协定到视图”适配器组成。 一个“协定到视图”适配器足以支持两个版本的外接程序,因为每个外接程序端适配器都能将其对应的视图转换为版本 2 协定。

在此管线中,外接程序向宿主提供服务,类型从外接程序流向宿主。 由于没有类型从宿主流向外接程序,因此不必包含“视图到协定”适配器。

若要实现生存期管理,请使用 ContractHandle 对象为协定附加一个生存期标记。 为了使生存期管理能够工作,必须保留对此句柄的引用。 应用标记后,由于外接程序系统在不再使用对象时可以释放这些对象,并使其可作为垃圾回收,因此不需要再进行额外的编程。 有关更多信息,请参见生存期管理

创建宿主端适配器

  1. 将一个名为 Calc2HostSideAdapter 的新项目添加到 CalculatorV2 解决方案中。 使该项目基于**“类库”**模板。

  2. 在**“解决方案资源管理器”**中,将对下面程序集的引用添加到 Calc2HostSideAdapter 项目中:

    System.AddIn.dll

    System.AddIn.Contract.dll

  3. 添加对下列项目的项目引用:

    Calc2Contract

    Calc2HVA

  4. 选择每个项目引用,然后在**“属性”中将“复制本地”设置为“False”,以禁止将引用的程序集复制到本地生成文件夹。 在 Visual Basic 中,使用“项目属性”“引用”选项卡将两个项目引用的“复制本地”设置为“False”**。

  5. 将项目的默认类重命名为 CalculatorContractToViewHostSideAdapter。

  6. 在类文件中,添加对 System.AddIn.Pipeline 的命名空间引用。

  7. 在类文件中,添加以下相邻管线段的命名空间引用:CalcHVAs 和 CalculatorContracts。 (在 Visual Basic 中,除非已在 Visual Basic 项目中关闭了默认命名空间,否则这两个命名空间引用为 Calc2HVA.CalcHVAs 和 Calc2Contract.CalculatorContracts。)

  8. HostAdapterAttribute 特性应用于 CalculatorContractToViewHostSideAdapter 类,以将其标识为宿主端适配器。

  9. 使 CalculatorContractToViewHostSideAdapter 类继承表示外接程序宿主视图的抽象基类:CalcHVAs.Calculator(在 Visual Basic 中为 Calc2HVA.CalcHVAs.Calculator)。 注意与版本 1 的区别,版本 1 中的外接程序宿主视图是一个接口。

  10. 添加一个接受管线协定类型 ICalc2Contract 的公共构造函数。 该构造函数必须缓存对协定的引用。 它还必须创建并缓存协定的新 ContractHandle,以管理外接程序的生存期。

    重要

    ContractHandle 对于生存期管理至关重要。如果无法保留对 ContractHandle 对象的引用,则垃圾回收功能会回收该对象,而管线会在程序不需要时关闭。这可能会导致难以诊断的错误,如 AppDomainUnloadedException。关闭是管线生存期中的正常阶段,因此生存期管理代码无法检测此情况是否为错误。

  11. 重写 Calculator 的成员时,只需调用传递给构造函数的 ICalc2Contract 实例的对应成员,然后返回结果。 这将使协定 (ICalc2Contract) 与视图 (Calculator) 适配。

    下面的代码显示已完成的宿主端适配器段。

    
    Imports Microsoft.VisualBasic
    Imports System
    Imports System.Collections.Generic
    Imports System.Text
    Imports System.AddIn.Pipeline
    Imports Calc2HVA.CalcHVAs
    Imports Calc2Contract.CalculatorContracts
    
    Namespace CalculatorContractsHostAdapers
        <HostAdapter()> _
        Public Class CalculatorContractToViewHostAdapter
            Inherits Calculator
    
        Private _contract As ICalc2Contract
        Private _handle As ContractHandle
    
        Public Sub New(ByVal contract As ICalc2Contract)
            _contract = contract
            _handle = New ContractHandle(contract)
        End Sub
    
        Public Overrides ReadOnly Property Operations() As String
            Get
                Return _contract.GetAvailableOperations()
            End Get
        End Property
    
        Public Overrides Function Operate(ByVal operation As String, ByVal a As Double, ByVal b As Double) As Double
            Return _contract.Operate(operation, a, b)
        End Function
    End Class
    End Namespace
    
    using System.AddIn.Pipeline;
    using CalcHVAs;
    using CalculatorContracts;
    
    namespace CalcHostSideAdapters {
    
    
    [HostAdapter]
    public class CalculatorContractToViewHostAdapter : Calculator {
    
        private CalculatorContracts.ICalc2Contract _contract;
    
        private System.AddIn.Pipeline.ContractHandle _handle;
    
        public CalculatorContractToViewHostAdapter(ICalc2Contract contract) {
            _contract = contract;
            _handle = new System.AddIn.Pipeline.ContractHandle(contract);
        }
    
    
        public override string Operations
        {
            get 
            { 
                return _contract.GetAvailableOperations(); 
            }
        }
    
        public override double Operate(string operation, double a, double b)
        {
            return _contract.Operate(operation, a, b);
        }
     }
    }
    

创建宿主

宿主应用程序通过宿主视图与外接程序交互。 它使用由 AddInStoreAddInToken 类提供的外接程序发现和激活方法执行以下操作:

  • 重新生成管线和外接程序信息的缓存。

  • 在指定管线根目录下找到 Calculator 类型的外接程序。

  • 提示用户指定要使用的外接程序。 在本示例中,将看到两个可用外接程序。

  • 激活具有指定安全信任级别的新应用程序域中选定的外接程序。

  • 运行 RunCalculator 方法,该方法可以调用外接程序的宿主视图提供的外接程序的方法。

创建宿主

  1. 将一个名为 MathHost2 的新项目添加到 CalculatorV2 解决方案中。 使该项目基于**“控制台应用程序”**模板。

  2. 在**“解决方案资源管理器”**中,将对 System.AddIn.dll 程序集的引用添加到 MathHost2 项目中。

  3. 添加对 Calc2HVA 项目的项目引用。 选择该项目引用,然后在**“属性”中将“复制本地”设置为“False”,以禁止将引用的程序集复制到本地生成文件夹。 在 Visual Basic 中,使用“项目属性”“引用”选项卡将“复制本地”设置为“False”**。

  4. 将类文件(在 Visual Basic 中为模块)重命名为 MathHost2。

  5. 在 Visual Basic 中,使用**“项目属性”对话框的“应用程序”选项卡将“启动对象”设置为“Sub Main”**。

  6. 在类文件或模块文件中,添加对 System.AddIn.Hosting 的命名空间引用。

  7. 在类文件或模块文件中,添加外接程序宿主视图的命名空间引用:CalcHVAs。 (在 Visual Basic 中,除非已在 Visual Basic 项目中关闭了默认命名空间,否则此命名空间引用为 Calc2HVA.CalcHVAs。)

  8. 在**“解决方案资源管理器”中选择该解决方案,然后从“项目”菜单上选择“属性”。 在“解决方案属性页”对话框中,将“单启动项目”**设置为此宿主应用程序项目。

  9. 使用以下代码创建宿主应用程序。

    
    Imports Microsoft.VisualBasic
    Imports System
    Imports System.Collections.Generic
    Imports System.Collections.ObjectModel
    Imports System.Text
    Imports System.AddIn.Hosting
    Imports Calc2HVA.CalcHVAs
    
    Namespace Mathhost
    
        Module MathHost2
    
            Sub Main()
                ' Assume that the current directory is the application folder, 
                ' and that it contains the pipeline folder structure. 
                Dim pipeRoot As String = Environment.CurrentDirectory & "\Pipeline"
    
                ' Rebuild the cache of pipline and add-in information.
                AddInStore.Rebuild(pipeRoot)
    
                ' Find add-ins of type Calculator under the specified pipeline root directory.
                Dim tokens As Collection(Of AddInToken) = AddInStore.FindAddIns(GetType(Calculator), pipeRoot)
    
                ' Determine which add-in to use.
                Dim calcToken As AddInToken = ChooseCalculator(tokens)
    
                ' Activate the selected AddInToken in a new  
                ' application domain with a specified security trust level.
                Dim calculator As Calculator = calcToken.Activate(Of Calculator)(AddInSecurityLevel.Internet)
    
                ' Run the calculator.
                RunCalculator(calculator)
            End Sub
    
            Private Function ChooseCalculator(ByVal tokens As Collection(Of AddInToken)) As AddInToken
                If tokens.Count = 0 Then
                    Console.WriteLine("No calculators are available")
                    Return Nothing
                End If
                Console.WriteLine("Available Calculators: ")
                ' Show the token properties for each token 
                ' in the AddInToken collection (tokens),
                ' preceded by the add-in number in [] brackets.
    
                Dim tokNumber As Integer = 1
                For Each tok As AddInToken In tokens
                    Console.WriteLine(vbTab & "[{0}]: {1} - {2}" & _
                            vbLf & vbTab & "{3}" & _
                            vbLf & vbTab & "{4}" & _
                            vbLf & vbTab & "{5} - {6}", _
                            tokNumber.ToString, tok.Name, _
                            tok.AddInFullName, tok.AssemblyName, _
                            tok.Description, tok.Version, tok.Publisher)
                    tokNumber = tokNumber + 1
                Next
                Console.WriteLine("Which calculator do you want to use?")
                Dim line As String = Console.ReadLine()
                Dim selection As Integer
                If Int32.TryParse(line, selection) Then
                    If selection <= tokens.Count Then
                        Return tokens(selection - 1)
                    End If
                End If
                Console.WriteLine("Invalid selection: {0}. Please choose again.", line)
                Return ChooseCalculator(tokens)
            End Function
    
            Private Sub RunCalculator(ByVal calc As Calculator)
    
                If calc Is Nothing Then
                    'No calculators were found, read a line and exit
                    Console.ReadLine()
                End If
                Console.WriteLine("Available operations: " & calc.Operations)
                Console.WriteLine("Request a calculation , such as: 2 + 2")
                Console.WriteLine("Type ""exit"" to exit")
                Dim line As String = Console.ReadLine()
                Do While Not line.Equals("exit")
                    ' Parser  
                    Try
                        Dim c As Parser = New Parser(line)
                        Console.WriteLine(calc.Operate(c.action, c.A, c.B))
                    Catch
                        Console.WriteLine("Invalid command: {0}. Commands must be formated: [number] [operation] [number]", line)
                        Console.WriteLine("Available operations: " & calc.Operations)
                    End Try
    
                    line = Console.ReadLine()
                Loop
            End Sub
        End Module
    
    
        Friend Class Parser
    
            Public partA As Double
    
            Public partB As Double
    
            Public action As String
    
            Friend Sub New(ByVal line As String)
                MyBase.New()
                Dim parts() As String = line.Split(" ")
                partA = Double.Parse(parts(0))
                action = parts(1)
                partB = Double.Parse(parts(2))
            End Sub
    
            Public ReadOnly Property A() As Double
                Get
                    Return partA
                End Get
            End Property
    
            Public ReadOnly Property B() As Double
                Get
                    Return partB
                End Get
            End Property
    
            Public ReadOnly Property CalcAction() As String
                Get
                    Return action
                End Get
            End Property
        End Class
    End Namespace
    
    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.Text;
    using System.AddIn.Hosting;
    using CalcHVAs;
    
    namespace MathHost
    {
        class Program
        {
            static void Main()
            {
                // Assume that the current directory is the application folder, 
                // and that it contains the pipeline folder structure. 
                String addInRoot = Environment.CurrentDirectory + "\\Pipeline";
    
                //Check to see if new add-ins have been installed.
                AddInStore.Rebuild(addInRoot);
    
                //Search for Calculator add-ins.
                Collection<AddInToken> tokens = AddInStore.FindAddIns(typeof(Calculator), addInRoot);
    
                //Ask the user which add-in they would like to use.
                AddInToken calcToken = ChooseCalculator(tokens);
    
                //Activate the selected AddInToken in a new
                //application domain with the Internet trust level.
                Calculator calculator = calcToken.Activate<Calculator>(AddInSecurityLevel.Internet);
    
                //Run the add-in.
                RunCalculator(calculator);
            }
    
            private static AddInToken ChooseCalculator(Collection<AddInToken> tokens)
            {
                if (tokens.Count == 0)
                {
                    Console.WriteLine("No calculators are available");
                    return null;
                }
                Console.WriteLine("Available Calculators: ");
                // Show the token properties for each token 
                // in the AddInToken collection (tokens),
                // preceded by the add-in number in [] brackets.
                int tokNumber = 1;
                foreach (AddInToken tok in tokens)
                {
                    Console.WriteLine(String.Format("\t[{0}]: {1} - {2}\n\t{3}\n\t\t {4}\n\t\t {5} - {6}",
                        tokNumber.ToString(), 
                        tok.Name,
                        tok.AddInFullName,
                        tok.AssemblyName,
                        tok.Description,
                        tok.Version,
                        tok.Publisher));
                    tokNumber++;
                }
                Console.WriteLine("Which calculator do you want to use?");
                String line = Console.ReadLine();
                int selection;
                if (Int32.TryParse(line, out selection))
                {
                    if (selection <= tokens.Count)
                    {
                        return tokens[selection - 1];
                    }
                }
                Console.WriteLine("Invalid selection: {0}. Please choose again.", line);
                return ChooseCalculator(tokens);
            }
    
            private static void RunCalculator(Calculator calc)
            {
    
                if (calc == null)
                {
                    //No calculators were found, read a line and exit.
                    Console.ReadLine();
                }
                Console.WriteLine("Available operations: " + calc.Operations);
                Console.WriteLine("Type \"exit\" to exit");
                String line = Console.ReadLine();
                while (!line.Equals("exit"))
                {
                    // The Parser class parses the user's input.
                    try
                    {
                        Parser c = new Parser(line);
                        Console.WriteLine(calc.Operate(c.Action, c.A, c.B));
                    }
                    catch
                    {
                        Console.WriteLine("Invalid command: {0}. Commands must be formated: [number] [operation] [number]", line);
                        Console.WriteLine("Available operations: " + calc.Operations);
                    }
    
                    line = Console.ReadLine();
                }
            }
        }
    
    
        internal class Parser
        {
            internal Parser(String line)
            {
                String[] parts = line.Trim().Split(' ');
                a = Double.Parse(parts[0]);
                action = parts[1];
                b = Double.Parse(parts[2]);
            }
    
            double a;
    
            public double A
            {
                get { return a; }
            }
            double b;
    
            public double B
            {
                get { return b; }
            }
            String action;
    
            public String Action
            {
                get { return action; }
            }
        }
    }
    

    备注

    这段代码假设管线文件夹结构位于应用程序文件夹中。如果它位于其他位置,请更改设置 addInRoot 变量的代码行,以使该变量包含管线目录结构的路径。

创建外接程序

外接程序实现由外接程序视图指定的方法。 在此外接程序中,Operations 方法返回一个列出外接程序支持的数学运算的字符串。 Operate 方法提供基于宿主选择的一个运算和两个数字来计算结果的代码。

创建外接程序

  1. 将一个名为 AddInCalcV2 的新项目添加到 CalculatorV2 解决方案中。 使该项目基于**“类库”**模板。

  2. 在**“解决方案资源管理器”**中,将对下列程序集的引用添加到 AddInCalcV2 项目中:

    System.AddIn.dll

    System.AddIn.Contract.dll

  3. 添加对 Calc2AddInView 项目的项目引用。 选择该项目引用,然后在**“属性”中将“复制本地”设置为“False”,以禁止将引用的程序集复制到本地生成文件夹。 在 Visual Basic 中,使用“项目属性”“引用”选项卡将“复制本地”设置为“False”**。

  4. 将类文件重命名为 SampleV2AddIn。

  5. 在类文件中,添加对 System.AddInSystem.AddIn.Pipeline 的命名空间引用。 需要 System.AddIn.Pipeline 只是因为代码包含 QualificationDataAttribute 特性的示例。

  6. 在类文件中,添加对版本 2 外接程序视图段的命名空间引用:CalcAddInViews(Visual Basic 中为 Calc2AddInView.CalcAddInViews)。

  7. AddInAttribute 特性应用于 SampleV2AddIn 类,以将该类标识为外接程序。

  8. QualificationDataAttribute 特性应用于 SampleV2AddIn 类,并指定宿主可以从 AddInToken 检索的信息。 本示例中,这些信息建议外接程序应加载到其自己的应用程序域中。 请参见如何:使用限定数据

  9. 使 SampleV2AddIn 类继承表示外接程序视图的抽象基类:Calculator2。

  10. 重写 Calculator2 的成员,并返回相应计算的结果。

    下面的代码显示已完成的外接程序。

    
    Imports Microsoft.VisualBasic
    Imports System
    Imports System.Collections.Generic
    Imports System.Text
    Imports System.AddIn
    Imports System.AddIn.Pipeline
    Imports Calc2AddInView.CalcAddInViews
    
    Namespace CalculatorAddIns
    ' This pipeline segment has
    ' two attributes:
    ' 1 - An AddInAttribute to identify
    '     this segment as an add-in.
    '
    ' 2 - A QualificationDataAttribute to
    '     indicate that the add-in should
    '     be loaded into a new application domain.
    
    <AddIn("Calculator Add-in", Version:="2.0.0.0")> _
    <QualificationData("Isolation", "NewAppDomain")> _
        Public Class SampleV2AddIn
        Inherits Calculator2
    Public Overrides ReadOnly Property Operations() As String
        Get
            Return "+, -, *, /, **"
        End Get
    End Property
    
    Public Overrides Function Operate(ByVal operation As String, _
            ByVal a As Double, ByVal b As Double) As Double
        Select Case operation
            Case "+"
                Return a + b
            Case "-"
                Return a - b
            Case "*"
                Return a * b
            Case "/"
                Return a / b
            Case "**"
                Return Math.Pow(a, b)
            Case Else
                Throw New InvalidOperationException("This add-in does not support: " & operation)
        End Select
    End Function
    
    End Class
    End Namespace
    
    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.AddIn;
    using System.AddIn.Pipeline;
    using CalcAddInViews;
    namespace CalcAddIns
    {
    // This pipeline segment has
    // two attributes:
    // 1 - An AddInAttribute to identify
    //     this segment as an add-in.
    //
    // 2 - A QualificationDataAttribute to
    //     indicate that the add-in should
    //     be loaded into a new application domain.
    
        [AddIn("Calculator Add-in",Version="2.0.0.0")]
        [QualificationData("Isolation", "NewAppDomain")]
        public class SampleV2AddIn : Calculator2
        {
            public override string Operations
            {
                get
                {
                    return "+, -, *, /, **";
                }
            }
    
            public override double Operate(string operation, double a, double b)
            {
                switch (operation)
                {
                    case "+":
                        return a + b;
                    case "-":
                        return a - b;
                    case "*":
                        return a * b;
                    case "/":
                        return a / b;
                    case "**":
                        return Math.Pow(a, b);
                    default:
                        throw new InvalidOperationException("This add-in does not support: " + operation);
                }
            }
    
        }
    }
    

部署管线

现在已准备好生成外接程序段并将其部署到所需的管线目录结构。

将段部署到管线

  1. 对于解决方案中的每个项目,使用**“项目属性”“生成”选项卡(在 Visual Basic 中为“编译”选项卡)设置“输出路径”(在 Visual Basic 中为“生成输出路径”**)的值。 例如,如果将应用程序文件夹命名为 MyApp,则项目将生成到以下文件夹中:

    Project

    路径

    AddInCalcV2

    MyApp\Pipeline\AddIns\CalcV2

    Calc2AddInSideAdapter

    MyApp\Pipeline\AddInSideAdapters

    Calc2V1toV2AddInSideAdapter

    MyApp\Pipeline\AddInSideAdapters

    Calc1AddInView

    MyApp\Pipeline\AddInViews

    Calc2AddInView

    MyApp\Pipeline\AddInViews

    Calc2Contract

    MyApp\Pipeline\Contracts

    MathHost2

    MyApp

    Calc2HostSideAdapter

    MyApp\Pipeline\HostSideAdapters

    Calc2HVA

    MyApp

    备注

    如果将管线文件夹结构放在应用程序文件夹之外的其他位置,则必须相应地修改表中所示的路径。

  2. 生成 Visual Studio 解决方案。

  3. 检查应用程序和管线目录,以确保程序集复制到了正确的目录,并且没有额外的程序集副本安装在错误的文件夹中。

    备注

    如果没有在 AddInCalcV2 项目中将 Calc2AddInView 项目引用的“复制本地”更改为“False”,则加载器上下文问题将阻止找到该外接程序。

    有关部署到管线的信息,请参见管线开发要求

运行宿主应用程序

现在即可运行宿主并与外接程序交互。

运行宿主应用程序

  1. 确保外接程序的两个版本均已部署。

  2. 在命令提示符下,转至应用程序目录并运行宿主应用程序。 在本例中,宿主应用程序为 MathHost2.exe。

  3. 宿主找到其类型的所有可用外接程序,并提示您选择一个外接程序。 输入 1 或 2。

  4. 为计算器输入一个等式,如 2 + 2。

  5. 键入“exit”,然后按**“Enter”**键关闭应用程序。

  6. 重复步骤 2-5 运行其他外接程序。

请参见

任务

演练:创建可扩展的应用程序

演练:在宿主和外接程序之间传递集合

概念

管线开发要求

协定、视图和适配器

管线开发