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

本演练介绍如何创建执行简单计算器功能的外接程序的一个管线。 本演练演示的内容是虚构的,用于演示管线的基本功能以及外接程序如何为宿主提供服务。

本演练介绍了以下任务:

  • 创建 Visual Studio 解决方案。

  • 创建管线目录结构。

  • 创建协定和视图。

  • 创建外接程序端适配器。

  • 创建宿主端适配器。

  • 创建宿主。

  • 创建外接程序。

  • 部署管线。

  • 运行宿主应用程序。

此管线在宿主和外接程序之间仅传递可序列化的类型(DoubleString)。 有关演示如何传递复杂数据类型的集合的示例,请参见演练:在宿主和外接程序之间传递集合

此管线的协定定义四种算术运算(加、减、乘和除)的对象模型。 宿主向外接程序提供要计算的等式(例如 2 + 2),而外接程序会向宿主返回结果。

计算器外接程序的第二个版本提供更多的计算可能性并演示版本控制。 演练:在宿主发生变化时启用向后兼容性中对此进行了介绍。

注意注意

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

系统必备

要完成本演练,您需要:

  • Visual Studio.

创建 Visual Studio 解决方案

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

创建管线解决方案

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

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

创建管线目录结构

外接程序模型要求管线段程序集放置在指定的目录结构中。 有关管线结构的更多信息,请参见管线开发要求

创建管线目录结构

  1. 在计算机上的任意位置创建一个应用程序文件夹。

  2. 在该文件夹中,创建以下结构:

    Pipeline
      AddIns
        CalcV1
        CalcV2
      AddInSideAdapters
      AddInViews
      Contracts
      HostSideAdapters
    

    管线文件夹结构并不一定要放在应用程序文件夹中;此处这样做只是为了方便起见。 如果管线文件夹结构位于其他位置,本演练会在相应的步骤中说明如何更改代码。 请参见管线开发要求中对管线目录要求的论述。

    注意注意

    本演练中不使用 CalcV2 文件夹;它在演练:在宿主发生变化时启用向后兼容性中用作占位符。

创建协定和视图

此管线的协定段定义 ICalc1Contract 接口,该接口定义四个方法:add、subtract、multiply 和 divide。

创建协定

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

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

    System.AddIn.Contract.dll

    System.AddIn.dll

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

  4. 在**“解决方案资源管理器”中,使用“接口”模板向项目中添加一个新项。 在“添加新项”**对话框中,将该接口命名为 ICalc1Contract。

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

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

    Imports System.AddIn.Contract
    Imports System.AddIn.Pipeline
    
    Namespace CalculatorContracts
    
        ' The AddInContractAttribute identifes this pipeline segment as a
        ' contract.
        <AddInContract()> _
        Public Interface ICalc1Contract
            Inherits IContract
    
            Function Add(ByVal a As Double, ByVal b As Double) As Double
            Function Subtract(ByVal a As Double, ByVal b As Double) As Double
            Function Multiply(ByVal a As Double, ByVal b As Double) As Double
            Function Divide(ByVal a As Double, ByVal b As Double) As Double
        End Interface
    
    End Namespace
    
    using System.AddIn.Contract;
    using System.AddIn.Pipeline;
    
    namespace CalculatorContracts
    {
        // The AddInContractAttribute identifes this pipeline segment as a 
        // contract.
        [AddInContract]
        public interface ICalc1Contract : IContract
        {
        double Add(double a, double b);
        double Subtract(double a, double b);
        double Multiply(double a, double b);
        double Divide(double a, double b);
        }
    }
    
  7. 生成 Visual Studio 解决方案。(可选) 在完成最后的步骤时此解决方案才能运行,但在每个步骤之后进行生成可确保每个项目都正确。

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

创建外接程序视图

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

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

  3. 在**“解决方案资源管理器”中,排除已添加到新“类库”项目中的默认类,然后使用“接口”模板向项目中添加一个新项。 在“添加新项”**对话框中,将该接口命名为 ICalculator。

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

  5. 使用下面的代码完成此外接程序视图。 请注意,此接口必须具有 AddInBaseAttribute 特性。

    Imports System.AddIn.Pipeline
    
    Namespace CalcAddInViews
    
        ' The AddInBaseAttribute identifes this interface as the basis for the
        ' add-in view pipeline segment.
        <AddInBaseAttribute()> _
        Public Interface ICalculator
    
            Function Add(ByVal a As Double, ByVal b As Double) As Double
            Function Subtract(ByVal a As Double, ByVal b As Double) As Double
            Function Multiply(ByVal a As Double, ByVal b As Double) As Double
            Function Divide(ByVal a As Double, ByVal b As Double) As Double
        End Interface
    
    End Namespace
    
    using System.AddIn.Pipeline;
    
    namespace CalcAddInViews 
    {
        // The AddInBaseAttribute identifes this interface as the basis for
        // the add-in view pipeline segment.
        [AddInBase()]
        public interface ICalculator 
        {
        double Add(double a, double b);
        double Subtract(double a, double b);
        double Multiply(double a, double b);
        double Divide(double a, double b);
        }
    }
    
  6. 生成 Visual Studio 解决方案。(可选)

创建外接程序的宿主视图

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

  2. 在**“解决方案资源管理器”中,排除已添加到新“类库”项目中的默认类,然后使用“接口”模板向项目中添加一个新项。 在“添加新项”**对话框中,将该接口命名为 ICalculator。

  3. 在接口文件中,使用下面的代码完成该外接程序的宿主视图。

    Namespace CalcHVAs
    
        Public Interface ICalculator
            Function Add(ByVal a As Double, ByVal b As Double) As Double
            Function Subtract(ByVal a As Double, ByVal b As Double) As Double
            Function Multiply(ByVal a As Double, ByVal b As Double) As Double
            Function Divide(ByVal a As Double, ByVal b As Double) As Double
        End Interface
    
    End Namespace
    
    namespace CalcHVAs 
    {
        public interface ICalculator 
        {
            double Add(double a, double b);
            double Subtract(double a, double b);
            double Multiply(double a, double b);
            double Divide(double a, double b);
        }
    }
    
  4. 生成 Visual Studio 解决方案。(可选)

创建外接程序端适配器

此外接程序端适配器由一个“视图到协定”适配器组成。 此管线段将类型从外接程序视图转换为协定。

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

创建外接程序端适配器

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

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

    System.AddIn.dll

    System.AddIn.Contract.dll

  3. 添加对相邻管线段项目的项目引用:

    Calc1AddInView

    Calc1Contract

  4. 选择每个项目引用,然后在**“属性”中,将“复制本地”设置为“False”。 在 Visual Basic 中,使用“项目属性”“引用”选项卡将两个项目引用的“复制本地”设置为“False”**。

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

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

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

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

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

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

  11. 若要实现 ICalc1Contract 的成员,只需调用传递给该构造函数的 ICalculator 实例的对应成员,然后返回结果。 这将使视图 (ICalculator) 与协定 (ICalc1Contract) 适配。

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

    Imports System.AddIn.Pipeline
    Imports Calc1AddInView.CalcAddInViews
    Imports Calc1Contract.CalculatorContracts
    
    Namespace CalcAddInSideAdapters
    
        ' The AddInAdapterAttribute identifes this class as the add-in-side 
        ' adapter pipeline segment.
        <AddInAdapter()> _
        Public Class CalculatorViewToContractAddInSideAdapter
            Inherits ContractBase
            Implements ICalc1Contract
    
            Private _view As ICalculator
    
            Public Sub New(ByVal view As ICalculator)
                MyBase.New()
                _view = view
            End Sub
    
            Public Function Add(ByVal a As Double, ByVal b As Double) As Double Implements ICalc1Contract.Add
                Return _view.Add(a, b)
            End Function
    
            Public Function Subtract(ByVal a As Double, ByVal b As Double) As Double Implements ICalc1Contract.Subtract
                Return _view.Subtract(a, b)
            End Function
    
            Public Function Multiply(ByVal a As Double, ByVal b As Double) As Double Implements ICalc1Contract.Multiply
                Return _view.Multiply(a, b)
            End Function
    
            Public Function Divide(ByVal a As Double, ByVal b As Double) As Double Implements ICalc1Contract.Divide
                Return _view.Divide(a, b)
            End Function
    
        End Class
    End Namespace
    
    using System.AddIn.Pipeline;
    using CalcAddInViews;
    using CalculatorContracts;
    
    namespace CalcAddInSideAdapters 
    {
        // The AddInAdapterAttribute identifes this class as the add-in-side adapter
        // pipeline segment.
        [AddInAdapter()]
        public class CalculatorViewToContractAddInSideAdapter :
            ContractBase, ICalc1Contract 
        {
            private ICalculator _view;
    
            public CalculatorViewToContractAddInSideAdapter(ICalculator view) 
            {
                _view = view;
            }
    
            public virtual double Add(double a, double b) 
            {
                return _view.Add(a, b);
            }
    
            public virtual double Subtract(double a, double b) 
            {
                return _view.Subtract(a, b);
            }
    
            public virtual double Multiply(double a, double b) 
            {
                return _view.Multiply(a, b);
            }
    
            public virtual double Divide(double a, double b) 
            {
                return _view.Divide(a, b);
            }
        }
    }
    
  12. 生成 Visual Studio 解决方案。(可选)

创建宿主端适配器

此宿主端适配器由一个“协定到视图”适配器组成。 此管线段使协定与外接程序的宿主视图适配。

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

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

创建宿主端适配器

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

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

    System.AddIn.dll

    System.AddIn.Contract.dll

  3. 添加对相邻管线段项目的项目引用:

    Calc1Contract

    Calc1HVA

  4. 选择每个项目引用,然后在**“属性”中,将“复制本地”设置为“False”。 在 Visual Basic 中,使用“项目属性”“引用”选项卡将两个项目引用的“复制本地”设置为“False”**。

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

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

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

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

  9. 使 CalculatorContractToViewHostSideAdapter 类实现表示外接程序宿主视图的接口:Calc1HVAs.ICalculator(在 Visual Basic 中为 Calc1HVA.CalcHVAs.ICalculator)。

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

    重要说明重要事项

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

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

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

    Imports System.AddIn.Pipeline
    Imports Calc1Contract.CalculatorContracts
    Imports Calc1HVA.CalcHVAs
    
    Namespace CalcHostSideAdapters
    
        ' The HostAdapterAttribute identifes this class as the host-side adapter
        ' pipeline segment.
        <HostAdapterAttribute()> _
        Public Class CalculatorContractToViewHostSideAdapter
            Implements ICalculator
    
            Private _contract As ICalc1Contract
            Private _handle As System.AddIn.Pipeline.ContractHandle
    
            Public Sub New(ByVal contract As ICalc1Contract)
                    MyBase.New()
                _contract = contract
                _handle = New ContractHandle(contract)
            End Sub
    
            Public Function Add(ByVal a As Double, ByVal b As Double) As Double _
                    Implements ICalculator.Add
                Return _contract.Add(a, b)
            End Function
    
            Public Function Subtract(ByVal a As Double, ByVal b As Double) As Double _
                    Implements ICalculator.Subtract
                Return _contract.Subtract(a, b)
            End Function
    
            Public Function Multiply(ByVal a As Double, ByVal b As Double) As Double _
                    Implements ICalculator.Multiply
                Return _contract.Multiply(a, b)
            End Function
    
            Public Function Divide(ByVal a As Double, ByVal b As Double) As Double _
                    Implements ICalculator.Divide
                Return _contract.Divide(a, b)
            End Function
    
        End Class
    
    End Namespace
    
    using System.AddIn.Pipeline;
    using CalcHVAs;
    using CalculatorContracts;
    
    namespace CalcHostSideAdapters 
    {
        // The HostAdapterAttribute identifes this class as the host-side adapter
        // pipeline segment.
        [HostAdapterAttribute()]
        public class CalculatorContractToViewHostSideAdapter : ICalculator 
        {
            private ICalc1Contract _contract;
            private System.AddIn.Pipeline.ContractHandle _handle;
    
            public CalculatorContractToViewHostSideAdapter(ICalc1Contract contract) 
            {
                _contract = contract;
                _handle = new ContractHandle(contract);
            }
    
            public double Add(double a, double b) 
            {
                return _contract.Add(a, b);
            }
    
            public double Subtract(double a, double b) 
            {
                return _contract.Subtract(a, b);
            }
    
            public double Multiply(double a, double b) 
            {
                return _contract.Multiply(a, b);
            }
    
            public double Divide(double a, double b) 
            {
                return _contract.Divide(a, b);
            }
        }
    }
    
  12. 生成 Visual Studio 解决方案。(可选)

创建宿主

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

  • 更新管线和外接程序信息的缓存。

  • 在指定的管线根目录下找到宿主视图类型为 ICalculator 的外接程序。

  • 提示用户指定要使用的外接程序。

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

  • 运行自定义 RunCalculator 方法,该方法调用外接程序的宿主视图指定的外接程序的方法。

创建宿主

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

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

  3. 添加对 Calc1HVA 项目的项目引用。 选择该项目引用,然后在**“属性”中,将“复制本地”设置为“False”。 在 Visual Basic 中,使用“项目属性”“引用”选项卡将“复制本地”设置为“False”**。

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

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

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

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

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

  9. 在类文件或模块文件中,使用 AddInStore.Update 方法更新缓存。 使用 AddInStore.FindAddIn 方法获取标记的集合,并使用 AddInToken.Activate 方法激活外接程序。

    下面的代码显示已完成的宿主应用程序。

    Imports System.Collections.Generic
    Imports System.Collections.ObjectModel
    Imports System.AddIn.Hosting
    Imports Calc1HVA.CalcHVAs
    
    Namespace MathHost
    
        Module MathHost1
    
            Sub Main()
                ' Assume that the current directory is the application folder, 
                ' and that it contains the pipeline folder structure.
                Dim addInRoot As String = Environment.CurrentDirectory & "\Pipeline"
    
                ' Update the cache files of the pipeline segments and add-ins.
                Dim warnings() As String = AddInStore.Update(addInRoot)
                For Each warning As String In warnings
                    Console.WriteLine(warning)
                Next
    
                ' Search for add-ins of type ICalculator (the host view of the add-in).
                Dim tokens As System.Collections.ObjectModel.Collection(Of AddInToken) = _
                    AddInStore.FindAddIns(GetType(ICalculator), addinRoot)
    
                ' Ask the user which add-in they would like to use.
                Dim calcToken As AddInToken = ChooseCalculator(tokens)
    
                ' Activate the selected AddInToken in a new application domain 
                ' with the Internet trust level.
                Dim calc As ICalculator = _
                    calcToken.Activate(Of ICalculator)(AddInSecurityLevel.Internet)
    
                ' Run the add-in.
                RunCalculator(calc)
            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 ICalculator)
                If IsNothing(calc) Then
                    'No calculators were found, read a line and exit.
                    Console.ReadLine()
                End If
                Console.WriteLine("Available operations: +, -, *, /")
                Console.WriteLine("Request a calculation , such as: 2 + 2")
                Console.WriteLine("Type 'exit' to exit")
                Dim line As String = Console.ReadLine
    
                While Not line.Equals("exit")
                    ' The Parser class parses the user's input.
                    Try
                        Dim c As Parser = New Parser(line)
                        Select Case (c.action)
                            Case "+"
                                Console.WriteLine(calc.Add(c.a, c.b))
                            Case "-"
                                Console.WriteLine(calc.Subtract(c.a, c.b))
                            Case "*"
                                Console.WriteLine(calc.Multiply(c.a, c.b))
                            Case "/"
                                Console.WriteLine(calc.Divide(c.a, c.b))
                            Case Else
                                Console.WriteLine("{0} is an invalid command. Valid commands are +,-,*,/", c.action)
                        End Select
                    Catch Ex As System.Exception
                        Console.WriteLine("Invalid command: {0}. Commands must be formated: [number] [operation] [number]", line)
                    End Try
                    line = Console.ReadLine
    
                End While
            End Sub
        End Module
    
        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.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";
    
                // Update the cache files of the pipeline segments and add-ins.
                string[] warnings = AddInStore.Update(addInRoot);
                foreach (string warning in warnings)
                {
                    Console.WriteLine(warning);
                }
    
                // Search for add-ins of type ICalculator (the host view of the add-in).
                Collection<AddInToken> tokens = 
                    AddInStore.FindAddIns(typeof(ICalculator), 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.
                ICalculator calc = 
                    calcToken.Activate<ICalculator>(AddInSecurityLevel.Internet);
    
                // Run the add-in.
                RunCalculator(calc);
            }
    
            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(ICalculator calc)
            {
    
                if (calc == null)
                {
                    //No calculators were found; read a line and exit.
                    Console.ReadLine();
                }
                Console.WriteLine("Available operations: +, -, *, /");
                Console.WriteLine("Request a calculation , such as: 2 + 2");
                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);
                        switch (c.Action)
                        {
                            case "+":
                                Console.WriteLine(calc.Add(c.A, c.B));
                                break;
                            case "-":
                                Console.WriteLine(calc.Subtract(c.A, c.B));
                                break;
                            case "*":
                                Console.WriteLine(calc.Multiply(c.A, c.B));
                                break;
                            case "/":
                                Console.WriteLine(calc.Divide(c.A, c.B));
                                break;
                            default:
                                Console.WriteLine("{0} is an invalid command. Valid commands are +,-,*,/", c.Action);
                                break;
                        }
                    }
                    catch
                    {
                        Console.WriteLine("Invalid command: {0}. Commands must be formated: [number] [operation] [number]", line);
                    }
    
                    line = Console.ReadLine();
                }
            }
        }
    
        internal class Parser
        {
            double a;
            double b;
            string action;
    
            internal Parser(string line)
            {
                string[] parts = line.Split(' ');
                a = double.Parse(parts[0]);
                action = parts[1];
                b = double.Parse(parts[2]);
            }
    
            public double A
            {
                get { return a; }
            }
    
            public double B
            {
                get { return b; }
            }
    
            public string Action
            {
                get { return action; }
            }
        }
    }
    
    注意注意

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

    该代码使用 ChooseCalculator 方法来列出标记,并提示用户选择一个外接程序。 RunCalculator 方法提示用户提供一个简单数学表达式,然后使用 Parser 类分析该表达式,并显示外接程序返回的结果。

  10. 生成 Visual Studio 解决方案。(可选)

创建外接程序

外接程序实现由外接程序视图指定的方法。 此外接程序实现 Add、Subtract、Multiply 和 Divide 运算并将结果返回给宿主。

创建外接程序

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

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

  3. 添加对 Calc1AddInView 项目的项目引用。 选择该项目引用,然后在**“属性”中,将“复制本地”设置为“False”。 在 Visual Basic 中,使用“项目属性”“引用”选项卡将该项目引用的“复制本地”设置为“False”**。

  4. 将该类重命名为 AddInCalcV1。

  5. 在类文件中,添加对 System.AddIn 的命名空间引用,以及外接程序视图管线段:CalcAddInViews(在 Visual Basic 中为 Calc1AddInView.CalcAddInViews)。

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

  7. 使 AddInCalcV1 类实现表示外接程序视图的接口:CalcAddInViews.ICalculator(在 Visual Basic 中为 Calc1AddInView.CalcAddInViews.ICalculator)。

  8. 通过返回相应计算的结果实现 ICalculator 的成员。

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

    Imports System.AddIn
    Imports Calc1AddInView.CalcAddInViews
    
    Namespace CalcAddIns
    
        ' The AddInAttribute identifies this pipeline segment as an add-in.
        <AddIn("Calculator AddIn", Version:="1.0.0.0")> _
        Public Class AddInCalcV1
            Implements ICalculator
    
            Public Function Add(ByVal a As Double, ByVal b As Double) As Double _
                    Implements ICalculator.Add
                Return (a + b)
            End Function
    
            Public Function Subtract(ByVal a As Double, ByVal b As Double) As Double _
                    Implements ICalculator.Subtract
                Return (a - b)
            End Function
    
            Public Function Multiply(ByVal a As Double, ByVal b As Double) As Double _
                    Implements ICalculator.Multiply
                Return (a * b)
            End Function
    
            Public Function Divide(ByVal a As Double, ByVal b As Double) As Double _
                    Implements ICalculator.Divide
                Return (a / b)
            End Function
        End Class
    
    End Namespace
    
    using System.Collections.Generic;
    using System.AddIn;
    using CalcAddInViews;
    
    namespace CalcAddIns
    {
        // The AddInAttribute identifies this pipeline segment as an add-in.
        [AddIn("Calculator AddIn",Version="1.0.0.0")]
        public class AddInCalcV1 : ICalculator
        {
            public double Add(double a, double b)
            {
                return a + b;
            }
    
            public double Subtract(double a, double b)
            {
                return a - b;
            }
    
            public double Multiply(double a, double b)
            {
                return a * b;
            }
    
            public double Divide(double a, double b)
            {
                return a / b;
            }
        }
    }
    
  9. 生成 Visual Studio 解决方案。(可选)

部署管线

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

将段部署到管线

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

    Project

    路径

    AddInCalcV1

    MyApp\Pipeline\AddIns\CalcV1

    Calc1AddInSideAdapter

    MyApp\Pipeline\AddInSideAdapters

    Calc1AddInView

    MyApp\Pipeline\AddInViews

    Calc1Contract

    MyApp\Pipeline\Contracts

    Calc1Host

    MyApp

    Calc1HostSideAdapter

    MyApp\Pipeline\HostSideAdapters

    Calc1HVA

    MyApp

    注意注意

    如果决定将管线文件夹结构放在应用程序文件夹之外的其他位置,则必须相应地修改表中所示的路径。请参见管线开发要求中对管线目录要求的论述。

  2. 生成 Visual Studio 解决方案。

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

    注意注意

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

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

运行宿主应用程序

现已准备好运行宿主并将其与外接程序交互。

运行宿主应用程序

  1. 在命令提示符下,转至应用程序目录并运行宿主应用程序 Calc1Host.exe。

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

  3. 为计算器输入一个等式,如 2 + 2。 数字和运算符之间必须有空格。

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

请参见

任务

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

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

概念

管线开发要求

协定、视图和适配器

管线开发