Walkthrough: Passing Collections Between Hosts and Add-Ins

This walkthrough describes how to create a pipeline that passes a collection of custom objects between an add-in and a host. Because the types in the collection are not serializable, additional classes that define view-to-contract and contract-to-view adapters must be added to the adapter segments so that the flow of types can cross the isolation boundary.

In this scenario, the add-in updates a collection of book objects for the host. Each book object contains methods that get and set the book's title, publisher, price, and other data.

As a demonstration, the host creates a book collection; the add-in decreases the price of all computer books by 20 percent and removes all horror books from the collection. The add-in then creates a new book object for the best-selling book and passes it to the host as a single object.

This walkthrough illustrates the following tasks:

  • Creating a Visual Studio solution.

  • Creating the pipeline directory structure.

  • Creating the contracts and views for objects that must be passed back and forth across the isolation boundary.

  • Creating the add-in-side and host-side adapters required to pass objects across the isolation boundary.

  • Creating the host.

  • Creating the add-in.

  • Deploying the pipeline.

  • Running the host application.

Note

Some of the code shown in this walkthrough contains extraneous namespace references. The walkthrough steps accurately reflect the references required in Visual Studio.

You can find additional sample code, and customer technology previews of tools for building add-in pipelines, at the Managed Extensibility and Add-In Framework site on CodePlex.

Prerequisites

You need the following components to complete this walkthrough:

Creating a Visual Studio Solution

Use a solution in Visual Studio to contain the projects of your pipeline segments.

To create the pipeline solution

  1. In Visual Studio, create a new project named LibraryContracts. Base it on the Class Library template.

  2. Name the solution BooksPipeline.

Creating the Pipeline Directory Structure

The add-in model requires that the pipeline segment assemblies be put in a specified directory structure.

To create the pipeline directory structure

  • Create the following folder structure on your computer. You can locate it anywhere, including within the folders of your Visual Studio solution.

    Pipeline
      AddIns
        BooksAddIn
      AddInSideAdapters
      AddInViews
      Contracts
      HostSideAdapters
    

    All folder names must be specified exactly as shown here, except for the root folder name and the names of individual add-in folders. This example uses Pipeline as the root folder name and BooksAddIn as the name of the add-in folder.

    Note

    For convenience, the walkthrough places the host application in the pipeline root folder. At the appropriate step, the walkthrough explains how to change the code if the application is in a different location.

    For more information about the pipeline folder structure, see Pipeline Development Requirements.

Creating the Contract and Views

The contract segment for this pipeline defines two interfaces:

  • The IBookInfoContract interface.

    This interface contains the methods, such as Author, that contain information about a book.

  • The ILibraryManagerContract interface.

    This interface contains the ProcessBooks method that the add-in uses to process a collection of books. Each book represents an IBookInfoContract contract. The interface also contains the GetBestSeller method that the add-in uses to provide a book object, which represents the best-selling book, to the host.

To create the contract

  1. In the Visual Studio solution named BooksPipeline, open the LibraryContracts project.

  2. In Visual Basic, open Properties for the LibraryContracts project and use the Application tab to delete the default value supplied for Root namespace. By default, Root namespace is set to the project name.

  3. In Solution Explorer, add references to the following assemblies to the project:

    Sytem.AddIn.Contract.dll

    System.AddIn.dll

  4. In the class file, add references to the System.AddIn.Contract and System.AddIn.Pipeline namespaces.

  5. In the class file, replace the default class declaration with two interfaces:

    • The ILibraryManagerContract interface is used to activate the add-in, so it must have the AddInContractAttribute attribute.

    • The IBookInfoContract interface represents an object that is passed between the host and the add-in, so it does not require the attribute.

    Both interfaces must inherit the IContract interface.

  6. Use the following code to add the IBookInfoContract and ILibraryManagerContract interfaces.

    
    Imports Microsoft.VisualBasic
    Imports System
    Imports System.Collections.Generic
    Imports System.Text
    Imports System.AddIn.Pipeline
    Imports System.AddIn.Contract
    Namespace Library
    <AddInContract> _
    Public Interface ILibraryManagerContract
    Inherits IContract
        ' Pass a collection of books,
        ' of type IBookInfoContract
        ' to the add-in for processing.
        Sub ProcessBooks(ByVal books As IListContract(Of IBookInfoContract))
    
        ' Get a IBookInfoContract object
        ' from the add-in of the
        ' the best selling book.
        Function GetBestSeller() As IBookInfoContract
    
        ' This method has has arbitrary
        ' uses and shows how you can
        ' mix serializable and custom types.
        Function Data(ByVal txt As String) As String
    End Interface
    
    ' Contains infomration about a book.
    Public Interface IBookInfoContract
    Inherits IContract
        Function ID() As String
        Function Author() As String
        Function Title() As String
        Function Genre() As String
        Function Price() As String
        Function Publish_Date() As String
        Function Description() As String
    End Interface
    End Namespace
    
    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.AddIn.Pipeline;
    using System.AddIn.Contract;
    namespace Library
    {
        [AddInContract]
        public interface ILibraryManagerContract : IContract
        {
            // Pass a collection of books,
            // of type IBookInfoContract
            // to the add-in for processing.
            void ProcessBooks(IListContract<IBookInfoContract> books);
    
            // Get a IBookInfoContract object
            // from the add-in of the
            // the best selling book.
            IBookInfoContract GetBestSeller();
    
            // This method has has arbitrary
            // uses and shows how you can
            // mix serializable and custom types.
            string Data(string txt);
        }
    
        // Contains infomration about a book.
        public interface IBookInfoContract : IContract
        {
            string ID();
            string Author();
            string Title();
            string Genre();
            string Price();
            string Publish_Date();
            string Description();
        }
    }
    

Because the add-in view and the host view have the same code, you can easily create the views at the same time. They differ by only one factor: the add-in view that is used to activate this segment of the pipeline requires the AddInBaseAttribute attribute; the host view does not require any attributes.

The add-in view for this pipeline contains two abstract classes. The BookInfo class provides the view for the IBookInfoContract interface, and the LibraryManager class provides the view for the ILibraryManagerContract interface.

To create the add-in view

  1. Add a new project named AddInViews to the BooksPipeline solution. Base it on the Class Library template.

  2. In Visual Basic, open Properties for the project and use the Application tab to delete the default value supplied for Root namespace.

  3. In Solution Explorer, add a reference to System.AddIn.dll to the AddInViews project.

  4. Rename the project's default class LibraryManager, and make the class abstract (MustInherit in Visual Basic).

  5. In the class file, add a reference to the System.AddIn.Pipeline namespace.

  6. The LibraryManager class is used to activate the pipeline, so you must apply the AddInBaseAttribute attribute.

  7. Use the following code to complete the abstract LibraryManager class.

    
    Imports Microsoft.VisualBasic
    Imports System
    Imports System.Collections.Generic
    Imports System.AddIn.Pipeline
    Namespace LibraryContractsBase
    ' The AddInBaseAttribute
    ' identifes this pipeline
    ' segment as an add-in view.
    <AddInBase> _
    Public MustInherit Class LibraryManager
        Public MustOverride Sub ProcessBooks(ByVal books As IList(Of BookInfo))
        Public MustOverride Function GetBestSeller() As BookInfo
    
        Public MustOverride Function Data(ByVal txt As String) As String
    End Class
    End Namespace
    
    using System;
    using System.Collections.Generic;
    using System.AddIn.Pipeline;
    namespace LibraryContractsBase
    {
    // The AddInBaseAttribute
    // identifes this pipeline
    // segment as an add-in view.
    [AddInBase]
        public abstract class LibraryManager
        {
            public abstract void ProcessBooks(IList<BookInfo> books);
            public abstract BookInfo GetBestSeller();
    
            public abstract string Data(string txt);
        }
    }
    
  8. Add an abstract class (MustInherit class in Visual Basic) to the project, and name it BookInfo. The BookInfo class represents an object that is passed between the host and the add-in. This class is not used to activate the pipeline, so it does not require any attributes.

  9. Use the following code to complete the abstract BookInfo class.

    
    Imports Microsoft.VisualBasic
    Imports System
    Namespace LibraryContractsBase
    
    Public MustInherit Class BookInfo
    
        Public MustOverride Function ID() As String
        Public MustOverride Function Author() As String
        Public MustOverride Function Title() As String
        Public MustOverride Function Genre() As String
        Public MustOverride Function Price() As String
        Public MustOverride Function Publish_Date() As String
        Public MustOverride Function Description() As String
    End Class
    End Namespace
    
    using System;
    namespace LibraryContractsBase {
    
        public abstract class BookInfo {
    
            public abstract string ID();
            public abstract string Author();
            public abstract string Title();
            public abstract string Genre();
            public abstract string Price();
            public abstract string Publish_Date();
            public abstract string Description();
        }
    }
    

To create the host view

  1. Add a new project named HostViews to the BooksPipeline solution. Base it on the Class Library template.

  2. In Visual Basic, open Properties for the project and use the Application tab to delete the default value supplied for Root namespace.

  3. Rename the project's default class LibraryManager, and make the class abstract (MustInherit in Visual Basic).

  4. Use the following code to complete the abstract LibraryManager class.

    
    Imports Microsoft.VisualBasic
    Imports System.Collections.Generic
    Namespace LibraryContractsHAV
    
    Public MustInherit Class LibraryManager
    
    Public MustOverride Sub ProcessBooks(ByVal books As System.Collections.Generic.IList(Of BookInfo))
    Public MustOverride Function GetBestSeller() As BookInfo
    
    Public MustOverride Function Data(ByVal txt As String) As String
    End Class
    End Namespace
    
    using System.Collections.Generic;
    namespace LibraryContractsHAV {
    
    public abstract class LibraryManager
    {
    
        public abstract void ProcessBooks(System.Collections.Generic.IList<BookInfo> books);
        public abstract BookInfo GetBestSeller();
    
        public abstract string Data(string txt);
    }
    }
    
  5. Add an abstract class (MustInherit class in Visual Basic) to the project, and name it BookInfo.

  6. Use the following code to complete the abstract BookInfo class.

    
    Imports Microsoft.VisualBasic
    Imports System
    Namespace LibraryContractsHAV
        Public MustInherit Class BookInfo
    
            Public MustOverride Function ID() As String
            Public MustOverride Function Author() As String
            Public MustOverride Function Title() As String
            Public MustOverride Function Genre() As String
            Public MustOverride Function Price() As String
            Public MustOverride Function Publish_Date() As String
            Public MustOverride Function Description() As String
        End Class
    End Namespace
    
    namespace LibraryContractsHAV
    {
        public abstract class BookInfo
        {
    
            public abstract string ID();
            public abstract string Author();
            public abstract string Title();
            public abstract string Genre();
            public abstract string Price();
            public abstract string Publish_Date();
            public abstract string Description();
        }
    }
    

Creating the Add-in-Side Adapter

The add-in-side adapter assembly for this pipeline contains four adapter classes:

  • BookInfoContractToViewAddInAdapter

    This adapter is called when the host passes a BookInfo object to the add-in, either by itself or as part of a collection. This class converts the contract of the BookInfo object to a view. The class inherits from the add-in view and implements the view's abstract methods by calling into the contract that is passed to the class's constructor.

    The constructor for this adapter takes a contract, so that a ContractHandle object can be applied to the contract to implement lifetime management.

    Important

    The ContractHandle is critical to lifetime management. If you fail to keep a reference to the ContractHandle object, garbage collection will reclaim it, and the pipeline will shut down when your program does not expect it. This can lead to errors that are difficult to diagnose, such as AppDomainUnloadedException. Shutdown is a normal stage in the life of a pipeline, so there is no way for the lifetime management code to detect that this condition is an error.

  • BookInfoViewToContractAddInAdapter

    This adapter is called when the add-in passes a BookInfo object to the host. This class converts the add-in view of the BookInfo object to a contract. The class inherits from the contract and implements the contract by calling into the add-in view that is passed to the class's constructor. This adapter is marshaled to the host as a contract.

  • LibraryManagerViewToContractAddInAdapter

    This is the type that is returned to the host from its call to activate the add-in. This type is called when the host calls into the add-in, including the call that passes a collection of host objects (IList<BookInfo>) to the add-in. This class converts the contract ILibraryManagerContract to the host view LibraryManager. This class inherits from the host view and implements the contract by calling into the view that is passed to its constructor.

    Because a collection of custom types, the BookInfo objects, must be marshaled across the isolation boundary, this adapter uses the CollectionAdapters class. This class provides methods to convert a IList<T> collection to an IListContract<T> collection, which enables the collection to be passed across the isolation boundary to the other side of the pipeline.

  • BookInfoAddInAdapter

    The static methods (Shared methods in Visual Basic) of this adapter are called by the LibraryManagerViewToContractAddInAdapter class to adapt a contract or view, or to return an existing contract or view. This prevents the creation of an additional adapter when an object makes a round trip between the host and the add-in.

To create the add-in-side adapter

  1. Add a new project named AddInSideAdapters to the BooksPipeline solution. Base it on the Class Library template.

  2. In Visual Basic, open Properties for the project and use the Application tab to delete the default value supplied for Root namespace.

  3. In Solution Explorer, add references to the following assemblies to the AddInSideAdapters project:

    System.AddIn.dll

    System.AddIn.Contract.dll

  4. In Solution Explorer, add references to the following projects to the AddInSideAdapters project:

    AddInViews

    LibraryContracts

    In the reference Properties, set Copy Local to False for these references, to prevent the referenced assemblies from being copied to the local build folder. The assemblies will be located in the pipeline directory structure, as described in the "Deploying the Pipeline" procedure later in this walkthrough.

  5. Name the class file BookInfoContractToViewAddInAdapter.

  6. In the class file, add a reference to the System.AddIn.Pipeline namespace.

  7. Use the following code to add the BookInfoContractToViewAddInAdapter class. The class does not require an attribute, because it is not used to activate the pipeline. The internal (Friend in Visual Basic) GetSourceContract method is used by the BookInfoAddInAdapter class to avoid creating an additional adapter when an object makes a round trip between the host and the add-in.

    
    Imports Microsoft.VisualBasic
    Imports System
    Imports System.AddIn.Pipeline
    Namespace LibraryContractsAddInAdapters
    
    Public Class BookInfoContractToViewAddInAdapter
        Inherits LibraryContractsBase.BookInfo
        Private _contract As Library.IBookInfoContract
        Private _handle As System.AddIn.Pipeline.ContractHandle
        Public Sub New(ByVal contract As Library.IBookInfoContract)
            _contract = contract
            _handle = New ContractHandle(contract)
        End Sub
    
        Public Overrides Function ID() As String
            Return _contract.ID()
        End Function
        Public Overrides Function Author() As String
            Return _contract.Author()
        End Function
        Public Overrides Function Title() As String
            Return _contract.Title()
        End Function
        Public Overrides Function Genre() As String
            Return _contract.Genre()
        End Function
        Public Overrides Function Price() As String
            Return _contract.Price()
        End Function
        Public Overrides Function Publish_Date() As String
            Return _contract.Publish_Date()
        End Function
        Public Overrides Function Description() As String
            Return _contract.Description()
        End Function
    
        Friend Function GetSourceContract() As Library.IBookInfoContract
            Return _contract
        End Function
    End Class
    End Namespace
    
    using System;
    using System.AddIn.Pipeline;
    namespace LibraryContractsAddInAdapters 
    {
    
    public class BookInfoContractToViewAddInAdapter : LibraryContractsBase.BookInfo 
    {
        private Library.IBookInfoContract _contract;
        private System.AddIn.Pipeline.ContractHandle _handle;
        public BookInfoContractToViewAddInAdapter(Library.IBookInfoContract contract) 
        {
            _contract = contract;
            _handle = new ContractHandle(contract);
        }
    
        public override string ID()
        {
            return _contract.ID();
        }
        public override string Author()
        {
            return _contract.Author();
        }
        public override string Title()
        {
            return _contract.Title();
        }
        public override string Genre()
        {
            return _contract.Genre();
        }
        public override string Price()
        {
            return _contract.Price();
        }
        public override string Publish_Date()
        {
            return _contract.Publish_Date();
        }
        public override string Description()
        {
            return _contract.Description();
        }
    
        internal Library.IBookInfoContract GetSourceContract() {
            return _contract;
        }
    }
    }
    
  8. Use the following code to add the BookInfoViewToContractAddInAdapter class to the AddInSideAdapters project. The class does not require an attribute, because it is not used to activate the pipeline. The internal (Friend in Visual Basic) GetSourceView method is used by the BookInfoAddInAdapter class to avoid creating an additional adapter when an object makes a round trip between the host and the add-in.

    
    Imports Microsoft.VisualBasic
    Imports System
    
    Namespace LibraryContractsAddInAdapters
    Public Class BookInfoViewToContractAddInAdapter
        Inherits System.AddIn.Pipeline.ContractBase
        Implements Library.IBookInfoContract
        Private _view As LibraryContractsBase.BookInfo
        Public Sub New(ByVal view As LibraryContractsBase.BookInfo)
            _view = view
        End Sub
        Public Overridable Function ID() As String Implements Library.IBookInfoContract.ID
            Return _view.ID()
        End Function
        Public Overridable Function Author() As String Implements Library.IBookInfoContract.Author
            Return _view.Author()
        End Function
        Public Overridable Function Title() As String Implements Library.IBookInfoContract.Title
            Return _view.Title()
        End Function
        Public Overridable Function Genre() As String Implements Library.IBookInfoContract.Genre
            Return _view.Genre()
        End Function
        Public Overridable Function Price() As String Implements Library.IBookInfoContract.Price
            Return _view.Price()
        End Function
        Public Overridable Function Publish_Date() As String Implements Library.IBookInfoContract.Publish_Date
            Return _view.Publish_Date()
        End Function
        Public Overridable Function Description() As String Implements Library.IBookInfoContract.Description
            Return _view.Description()
        End Function
    
        Friend Function GetSourceView() As LibraryContractsBase.BookInfo
            Return _view
        End Function
    End Class
    End Namespace
    
    using System;
    
    namespace LibraryContractsAddInAdapters 
    {
    public class BookInfoViewToContractAddInAdapter : System.AddIn.Pipeline.ContractBase, Library.IBookInfoContract 
    {
        private LibraryContractsBase.BookInfo _view;
        public BookInfoViewToContractAddInAdapter(LibraryContractsBase.BookInfo view) 
        {
            _view = view;
        }
        public virtual string ID()
        {
            return _view.ID();
        }
        public virtual string Author()
        {
            return _view.Author();
        }
        public virtual string Title()
        {
            return _view.Title();
        }
        public virtual string Genre()
        {
            return _view.Genre();
        }
        public virtual string Price()
        {
            return _view.Price();
        }
        public virtual string Publish_Date()
        {
            return _view.Publish_Date();
        }
        public virtual string Description()
        {
            return _view.Description();
        }
    
        internal LibraryContractsBase.BookInfo GetSourceView() {
            return _view;
        }
    }
    }
    
  9. Use the following code to add the LibraryManagerViewToContractAddInAdapter class to the AddInSideAdapters project. This class requires the AddInAdapterAttribute attribute, because it is used to activate the pipeline.

    The ProcessBooks method shows how to pass a list of books across the isolation boundary. It uses the CollectionAdapters.ToIList method to convert the list. To convert the objects in the list, it passes delegates for the adapter methods provided by the BookInfoAddInAdapter class.

    The GetBestSeller method shows how to pass a single BookInfo object across the isolation boundary.

    
    Imports Microsoft.VisualBasic
    Imports System.AddIn.Pipeline
    Imports System.AddIn.Contract
    Imports System.Collections.Generic
    Namespace LibraryContractsAddInAdapters
    ' The AddInAdapterAttribute
    ' identifes this pipeline
    ' segment as an add-in-side adapter.
    <AddInAdapter> _
    Public Class LibraryManagerViewToContractAddInAdapter
        Inherits System.AddIn.Pipeline.ContractBase
        Implements Library.ILibraryManagerContract
        Private _view As LibraryContractsBase.LibraryManager
        Public Sub New(ByVal view As LibraryContractsBase.LibraryManager)
            _view = view
        End Sub
        Public Overridable Sub ProcessBooks(ByVal books As IListContract(Of Library.IBookInfoContract)) Implements Library.ILibraryManagerContract.ProcessBooks
            _view.ProcessBooks(CollectionAdapters.ToIList(Of Library.IBookInfoContract, _
            LibraryContractsBase.BookInfo)(books, _
            AddressOf LibraryContractsAddInAdapters.BookInfoAddInAdapter.ContractToViewAdapter, _
            AddressOf LibraryContractsAddInAdapters.BookInfoAddInAdapter.ViewToContractAdapter))
        End Sub
        Public Overridable Function GetBestSeller() As Library.IBookInfoContract Implements Library.ILibraryManagerContract.GetBestSeller
            Return BookInfoAddInAdapter.ViewToContractAdapter(_view.GetBestSeller())
        End Function
    
        Public Overridable Function Data(ByVal txt As String) As String Implements Library.ILibraryManagerContract.Data
            Dim rtxt As String = _view.Data(txt)
            Return rtxt
        End Function
    
        Friend Function GetSourceView() As LibraryContractsBase.LibraryManager
            Return _view
        End Function
    End Class
    End Namespace
    
    using System.IO;
    using System.AddIn.Pipeline;
    using System.AddIn.Contract;
    using System.Collections.Generic;
    namespace LibraryContractsAddInAdapters
    {
    // The AddInAdapterAttribute
    // identifes this pipeline
    // segment as an add-in-side adapter.
    [AddInAdapter]
    public class LibraryManagerViewToContractAddInAdapter :
    System.AddIn.Pipeline.ContractBase, Library.ILibraryManagerContract
    {
        private LibraryContractsBase.LibraryManager _view;
        public LibraryManagerViewToContractAddInAdapter(LibraryContractsBase.LibraryManager view)
        {
            _view = view;
        }
        public virtual void ProcessBooks(IListContract<Library.IBookInfoContract> books)
        {
            _view.ProcessBooks(CollectionAdapters.ToIList<Library.IBookInfoContract,
                LibraryContractsBase.BookInfo>(books,
                LibraryContractsAddInAdapters.BookInfoAddInAdapter.ContractToViewAdapter,
                LibraryContractsAddInAdapters.BookInfoAddInAdapter.ViewToContractAdapter));
        }
        public virtual Library.IBookInfoContract GetBestSeller()
        {
            return BookInfoAddInAdapter.ViewToContractAdapter(_view.GetBestSeller());
        }
    
        public virtual string Data(string txt)
        {
            string rtxt = _view.Data(txt);
            return rtxt;
        }
    
        internal LibraryContractsBase.LibraryManager GetSourceView()
        {
            return _view;
        }
    }
    }
    
  10. Use the following code to add the BookInfoAddInAdapter class to the AddInSideAdapters project. The class contains two static methods (Shared methods in Visual Basic): ContractToViewAdapter and ViewToContractAdapter. The methods are internal (Friend in Visual Basic) because they are used only by the other adapter classes. The purpose of these methods is to avoid creating an extra adapter when an object makes a round trip in either direction between the host and the add-in. These methods should be provided for adapters that pass objects across the isolation boundary.

    
    Imports Microsoft.VisualBasic
    Imports System
    Namespace LibraryContractsAddInAdapters
    
    Public Class BookInfoAddInAdapter
      Friend Shared Function ContractToViewAdapter(ByVal contract As Library.IBookInfoContract) As LibraryContractsBase.BookInfo
        If (Not System.Runtime.Remoting.RemotingServices.IsObjectOutOfAppDomain(contract)) AndAlso _
            CType(contract, Object).GetType().Equals(GetType(BookInfoViewToContractAddInAdapter)) Then
            Return (CType(contract, BookInfoViewToContractAddInAdapter)).GetSourceView()
        Else
            Return New BookInfoContractToViewAddInAdapter(contract)
        End If
    
      End Function
    
    Friend Shared Function ViewToContractAdapter(ByVal view As LibraryContractsBase.BookInfo) As Library.IBookInfoContract
        If (Not System.Runtime.Remoting.RemotingServices.IsObjectOutOfAppDomain(view)) AndAlso _
            view.GetType().Equals(GetType(BookInfoContractToViewAddInAdapter)) Then
            Return (CType(view, BookInfoContractToViewAddInAdapter)).GetSourceContract()
        Else
            Return New BookInfoViewToContractAddInAdapter(view)
        End If
    End Function
    End Class
    End Namespace
    
    using System;
    namespace LibraryContractsAddInAdapters {
    
    public class BookInfoAddInAdapter
    {
        internal static LibraryContractsBase.BookInfo ContractToViewAdapter(Library.IBookInfoContract contract)
        {
            if (!System.Runtime.Remoting.RemotingServices.IsObjectOutOfAppDomain(contract) &&
                (contract.GetType().Equals(typeof(BookInfoViewToContractAddInAdapter))))
            {
                return ((BookInfoViewToContractAddInAdapter)(contract)).GetSourceView();
            }
            else {
                return new BookInfoContractToViewAddInAdapter(contract);
            }
    
        }
    
        internal static Library.IBookInfoContract ViewToContractAdapter(LibraryContractsBase.BookInfo view)
        {
            if (!System.Runtime.Remoting.RemotingServices.IsObjectOutOfAppDomain(view) &&
                (view.GetType().Equals(typeof(BookInfoContractToViewAddInAdapter))))
            {
                return ((BookInfoContractToViewAddInAdapter)(view)).GetSourceContract();
            }
            else {
                return new BookInfoViewToContractAddInAdapter(view);
            }
        }
    }
    }
    

Creating the Host-Side Adapter

This host-side adapter assembly for this pipeline contains four adapter classes:

  • BookInfoContractToViewHostAdapter

    This adapter is called when the add-in passes a BookInfo object to the host, either by itself or as part of a collection. This class converts the contract of the BookInfo object to a view. The class inherits from the host view and implements the view's abstract methods by calling into the contract that is passed to the class's constructor.

    The constructor for this adapter takes a contract for its constructor, so that a ContractHandle object can be applied to the contract to implement lifetime management.

    Important

    The ContractHandle is critical to lifetime management. If you fail to keep a reference to the ContractHandle object, garbage collection will reclaim it, and the pipeline will shut down when your program does not expect it. This can lead to errors that are difficult to diagnose, such as AppDomainUnloadedException. Shutdown is a normal stage in the life of a pipeline, so there is no way for the lifetime management code to detect that this condition is an error.

  • BookInfoViewToContractHostAdapter

    This adapter is called when the host passes a BookInfo object to the add-in. This class converts the host view of the BookInfo object to a contract. The class inherits from the contract and implements the contract by calling into the add-in view that is passed to the class's constructor. This adapter is marshaled to the add-in as a contract.

  • LibraryManagerContractToViewHostAdapter

    This adapter is called when the host passes a collection of BookInfo objects to the add-in. The add-in performs its implementation of the ProcessBooks method on this collection.

    This class converts the host view of the LibraryManager object to a contract. It inherits from the contract and implements the contract by calling into the host view that is passed to the class's constructor.

    Because a collection of custom types, the BookInfo objects, must be marshaled across the isolation boundary, this adapter uses the CollectionAdapters class. This class provides methods to convert a IList<T> collection to an IListContract<T> collection, which enables the collection to be passed across the isolation boundary to the other side of the pipeline.

  • BookInfoHostAdapter

    This adapter is called by the LibraryManagerViewToContractHostAdapter class to return any existing contracts or views for the adaptation instead of creating new instances for the call. This prevents the creation of an extra adapter when an object makes a round trip in either direction between the host and the add-in.

To create the host-side adapter

  1. Add a new project named HostSideAdapters to the BooksPipeline solution. Base it on the Class Library template.

  2. In Visual Basic, open Properties for the project and use the Application tab to delete the default value supplied for Root namespace.

  3. In Solution Explorer, add references to the following assemblies to the HostSideAdapters project:

    System.AddIn.dll

    System.AddIn.Contract.dll

  4. In Solution Explorer, add references to the following projects to the HostSideAdapters project:

    HostViews

    LibraryContracts

    In the reference Properties, set Copy Local to False for these references, to prevent the referenced assemblies from being copied to the local build folder.

  5. In the class file, add a reference to the System.AddIn.Pipeline namespace.

  6. Use the following code to add the BookInfoContractToViewHostAdapter class. The class does not require an attribute, because it is not used to activate the pipeline. The internal (Friend in Visual Basic) GetSourceContract method is used by the BookInfoAddInAdapter class to avoid creating an extra adapter when an object makes a round trip between the host and the add-in.

    
    Imports Microsoft.VisualBasic
    Imports System.AddIn.Pipeline
    Namespace LibraryContractsHostAdapters
    Public Class BookInfoContractToViewHostAdapter
        Inherits LibraryContractsHAV.BookInfo
        Private _contract As Library.IBookInfoContract
    
        Private _handle As ContractHandle
    
        Public Sub New(ByVal contract As Library.IBookInfoContract)
            _contract = contract
            _handle = New ContractHandle(contract)
        End Sub
    
        Public Overrides Function ID() As String
            Return _contract.ID()
        End Function
        Public Overrides Function Author() As String
            Return _contract.Author()
        End Function
        Public Overrides Function Title() As String
            Return _contract.Title()
        End Function
        Public Overrides Function Genre() As String
            Return _contract.Genre()
        End Function
        Public Overrides Function Price() As String
            Return _contract.Price()
        End Function
        Public Overrides Function Publish_Date() As String
            Return _contract.Publish_Date()
        End Function
        Public Overrides Function Description() As String
            Return _contract.Description()
        End Function
    
    
        Friend Function GetSourceContract() As Library.IBookInfoContract
            Return _contract
        End Function
    End Class
    End Namespace
    
    using System.AddIn.Pipeline;
    namespace LibraryContractsHostAdapters
    {
        public class BookInfoContractToViewHostAdapter : LibraryContractsHAV.BookInfo
        {
            private Library.IBookInfoContract _contract;
    
            private ContractHandle _handle;
    
            public BookInfoContractToViewHostAdapter(Library.IBookInfoContract contract)
            {
                _contract = contract;
                _handle = new ContractHandle(contract);
            }
    
            public override string ID()
            {
                return _contract.ID();
            }
            public override string Author()
            {
                return _contract.Author();
            }
            public override string Title()
            {
                return _contract.Title();
            }
            public override string Genre()
            {
                return _contract.Genre();
            }
            public override string Price()
            {
                return _contract.Price();
            }
            public override string Publish_Date()
            {
                return _contract.Publish_Date();
            }
            public override string Description()
            {
                return _contract.Description();
            }
    
    
            internal Library.IBookInfoContract GetSourceContract() {
                return _contract;
            }
        }
    }
    
  7. Use the following code to add the BookInfoViewToContractHostAdapter to the HostSideAdapters project. The class does not require an attribute, because it is not used to activate the pipeline. The internal (Friend in Visual Basic) GetSourceView method is used by the BookInfoAddInAdapter class to avoid creating an extra adapter when an object makes a round trip between the host and the add-in.

    
    Imports Microsoft.VisualBasic
    Imports System.AddIn.Pipeline
    Namespace LibraryContractsHostAdapters
    Public Class BookInfoViewToContractHostAdapter
        Inherits ContractBase
        Implements Library.IBookInfoContract
        Private _view As LibraryContractsHAV.BookInfo
    
        Public Sub New(ByVal view As LibraryContractsHAV.BookInfo)
            _view = view
        End Sub
    
        Public Overridable Function ID() As String Implements Library.IBookInfoContract.ID
            Return _view.ID()
        End Function
        Public Overridable Function Author() As String Implements Library.IBookInfoContract.Author
            Return _view.Author()
        End Function
        Public Overridable Function Title() As String Implements Library.IBookInfoContract.Title
            Return _view.Title()
        End Function
        Public Overridable Function Genre() As String Implements Library.IBookInfoContract.Genre
            Return _view.Genre()
        End Function
        Public Overridable Function Price() As String Implements Library.IBookInfoContract.Price
            Return _view.Price()
        End Function
        Public Overridable Function Publish_Date() As String Implements Library.IBookInfoContract.Publish_Date
            Return _view.Publish_Date()
        End Function
        Public Overridable Function Description() As String Implements Library.IBookInfoContract.Description
            Return _view.Description()
        End Function
        Friend Function GetSourceView() As LibraryContractsHAV.BookInfo
            Return _view
        End Function
    End Class
    End Namespace
    
    using System.AddIn.Pipeline;
    namespace LibraryContractsHostAdapters
    {
    public class BookInfoViewToContractHostAdapter : ContractBase, Library.IBookInfoContract
    {
        private LibraryContractsHAV.BookInfo _view;
    
        public BookInfoViewToContractHostAdapter(LibraryContractsHAV.BookInfo view)
        {
            _view = view;
        }
    
        public virtual string ID()
        {
            return _view.ID();
        }
        public virtual string Author()
        {
            return _view.Author();
        }
        public virtual string Title()
        {
            return _view.Title();
        }
        public virtual string Genre()
        {
            return _view.Genre();
        }
        public virtual string Price()
        {
            return _view.Price();
        }
        public virtual string Publish_Date()
        {
            return _view.Publish_Date();
        }
        public virtual string Description()
        {
            return _view.Description();
        }
        internal LibraryContractsHAV.BookInfo GetSourceView()
        {
            return _view;
        }
    }
    }
    
  8. Use the following code to add the LibraryManagerContractToViewHostAdapter to the HostSideAdapters project. This class requires the HostAdapterAttribute attribute, because it is used to activate the pipeline.

    The ProcessBooks method shows how to pass a list of books across the isolation boundary. It uses the CollectionAdapters.ToIListContract method to convert the list. To convert the objects in the list, it passes delegates for the adapter methods provided by the BookInfoHostAdapter class.

    The GetBestSeller method shows how to pass a single BookInfo object across the isolation boundary.

    
    Imports Microsoft.VisualBasic
    Imports System.Collections.Generic
    Imports System.AddIn.Pipeline
    Namespace LibraryContractsHostAdapters
        <HostAdapterAttribute()> _
        Public Class LibraryManagerContractToViewHostAdapter
            Inherits LibraryContractsHAV.LibraryManager
    
            Private _contract As Library.ILibraryManagerContract
            Private _handle As System.AddIn.Pipeline.ContractHandle
    
            Public Sub New(ByVal contract As Library.ILibraryManagerContract)
                _contract = contract
                _handle = New System.AddIn.Pipeline.ContractHandle(contract)
            End Sub
    
            Public Overrides Sub ProcessBooks(ByVal books As IList(Of LibraryContractsHAV.BookInfo))
                _contract.ProcessBooks(CollectionAdapters.ToIListContract(Of LibraryContractsHAV.BookInfo, _
                Library.IBookInfoContract)(books, _
                AddressOf LibraryContractsHostAdapters.BookInfoHostAdapter.ViewToContractAdapter, _
                AddressOf LibraryContractsHostAdapters.BookInfoHostAdapter.ContractToViewAdapter))
            End Sub
    
            Public Overrides Function GetBestSeller() As LibraryContractsHAV.BookInfo
                Return BookInfoHostAdapter.ContractToViewAdapter(_contract.GetBestSeller())
            End Function
    
            Friend Function GetSourceContract() As Library.ILibraryManagerContract
                Return _contract
            End Function
            Public Overrides Function Data(ByVal txt As String) As String
                Dim rtxt As String = _contract.Data(txt)
                Return rtxt
            End Function
        End Class
    End Namespace
    
    using System.Collections.Generic;
    using System.AddIn.Pipeline;
    namespace LibraryContractsHostAdapters
    {
    [HostAdapterAttribute()]
    public class LibraryManagerContractToViewHostAdapter : LibraryContractsHAV.LibraryManager
    {
    
        private Library.ILibraryManagerContract _contract;
        private System.AddIn.Pipeline.ContractHandle _handle;
    
        public LibraryManagerContractToViewHostAdapter(Library.ILibraryManagerContract contract)
        {
            _contract = contract;
            _handle = new System.AddIn.Pipeline.ContractHandle(contract);
        }
    
        public override void ProcessBooks(IList<LibraryContractsHAV.BookInfo> books) {
            _contract.ProcessBooks(CollectionAdapters.ToIListContract<LibraryContractsHAV.BookInfo,
                Library.IBookInfoContract>(books,
                LibraryContractsHostAdapters.BookInfoHostAdapter.ViewToContractAdapter,
                LibraryContractsHostAdapters.BookInfoHostAdapter.ContractToViewAdapter));
        }
    
        public override LibraryContractsHAV.BookInfo GetBestSeller()
        {
            return BookInfoHostAdapter.ContractToViewAdapter(_contract.GetBestSeller());
        }
    
        internal Library.ILibraryManagerContract GetSourceContract()
        {
            return _contract;
        }
        public override string Data(string txt)
        {
            string rtxt = _contract.Data(txt);
            return rtxt;
        }
    }
    }
    
  9. Use the following code to add the BookInfoHostAdapter class to the HostSideAdapters project. The class contains two static methods (Shared methods in Visual Basic): ContractToViewAdapter and ViewToContractAdapter. The methods are internal (Friend in Visual Basic) because they are used only by the other adapter classes. The purpose of these methods is to avoid creating an extra adapter when an object makes a round trip in either direction between the host and the add-in. These methods should be provided for adapters that pass objects across the isolation boundary.

    
    Imports Microsoft.VisualBasic
    Imports System
    Namespace LibraryContractsHostAdapters
    Public Class BookInfoHostAdapter
    
    Friend Shared Function ContractToViewAdapter(ByVal contract As Library.IBookInfoContract) As LibraryContractsHAV.BookInfo
        If Not System.Runtime.Remoting.RemotingServices.IsObjectOutOfAppDomain(contract) AndAlso _
            CType(contract, Object).GetType().Equals(GetType(BookInfoViewToContractHostAdapter)) Then
            Return (CType(contract, BookInfoViewToContractHostAdapter)).GetSourceView()
        Else
            Return New BookInfoContractToViewHostAdapter(contract)
        End If
    End Function
    
    Friend Shared Function ViewToContractAdapter(ByVal view As LibraryContractsHAV.BookInfo) As Library.IBookInfoContract
        If Not System.Runtime.Remoting.RemotingServices.IsObjectOutOfAppDomain(view) AndAlso _
            view.GetType().Equals(GetType(BookInfoContractToViewHostAdapter)) Then
            Return (CType(view, BookInfoContractToViewHostAdapter)).GetSourceContract()
        Else
            Return New BookInfoViewToContractHostAdapter(view)
        End If
    End Function
    End Class
    End Namespace
    
    using System;
    namespace LibraryContractsHostAdapters
    {
    public class BookInfoHostAdapter
    {
    
        internal static LibraryContractsHAV.BookInfo ContractToViewAdapter(Library.IBookInfoContract contract)
        {
            if (!System.Runtime.Remoting.RemotingServices.IsObjectOutOfAppDomain(contract) &&
                (contract.GetType().Equals(typeof(BookInfoViewToContractHostAdapter))))
            {
                return ((BookInfoViewToContractHostAdapter)(contract)).GetSourceView();
    
            }
            else {
                return new BookInfoContractToViewHostAdapter(contract);
            }
        }
    
        internal static Library.IBookInfoContract ViewToContractAdapter(LibraryContractsHAV.BookInfo view)
        {
            if (!System.Runtime.Remoting.RemotingServices.IsObjectOutOfAppDomain(view) &&
                (view.GetType().Equals(typeof(BookInfoContractToViewHostAdapter))))
            {
                return ((BookInfoContractToViewHostAdapter)(view)).GetSourceContract();
            }
            else {
                return new BookInfoViewToContractHostAdapter(view);
            }
        }
    }
    }
    

Creating the Host

A host application interacts with the add-in through the host view. It uses add-in discovery and activation methods provided by the AddInStore and AddInToken classes to do the following:

  • Rebuild the cache of pipeline and add-in information.

  • Find add-ins of type LibraryManager under the specified pipeline root directory.

  • Prompt the user to select the add-in to use. In this example, only one add-in is available.

  • Activate the selected add-in in a new application domain with a specified security trust level.

  • Call the ProcessBooks method to pass a collection of BookInfo objects to the add-in. The add-in calls its implementation of the ProcessBooks method and performs functions such as discounting computer books by 20 percent.

  • Calls the GetBestSeller method that the add-in uses to return a BookInfo object with information about the best-selling book.

  • Calls the Data method to get the current sales tax rate from the add-in. This method takes and returns a string that is a sealed serializable reference type. As a result, the method can be passed over the isolation boundary to the other side of the pipeline without using view-to-contract or contract-to-view adapters.

The host has a CreateBooks method that creates a collection of BookInfo objects. This method creates the collection by using the sample books.xml file that is available from Sample XML File (books.xml).

To create the host

  1. Add a new project named BookStore to the BooksPipeline solution. Base it on the Console Application template.

  2. In Visual Basic, open Properties for the project and use the Application tab to delete the default value supplied for Root namespace.

  3. In Solution Explorer, add a reference to the System.AddIn.dll assembly to the BookStore project.

  4. Add a project reference to the HostViews project. In the reference Properties, set Copy Local to False for this reference, to prevent the referenced assembly from being copied to the local build folder.

  5. In Visual Basic, change the module to a class:

    • Exclude the default module from the project, and then add a class named Program.

    • Replace the Public keyword with the Friend keyword.

    • Add a Shared Sub Main() procedure to the class.

    • Use the Application tab of the Project Properties dialog box to set Startup object to Sub Main.

  6. In the class file, add references to the System.AddIn.Pipeline and the host view segment namespaces.

  7. In Solution Explorer, select the solution, and from the Project menu choose Properties. In the Solution Property Pages dialog box, set the Single Startup Project to be this host application project.

  8. Use the following code for the host application.

    Note

    In the code, change the location from which the books.xml file is loaded to "books.xml", so that the file loads from the application folder. If you want to place the application in a location other than the Pipeline folder, change the line of code that sets the addInRoot variable, so that the variable contains the path to your pipeline directory structure.

    
    Imports Microsoft.VisualBasic
    Imports System
    Imports System.Collections.Generic
    Imports System.Collections.ObjectModel
    Imports System.Text
    Imports LibraryContractsHAV
    Imports System.AddIn.Hosting
    Imports System.Xml
    
    
    Namespace ListAdaptersHost
    Friend Class Program
    Shared Sub Main(ByVal args As String())
    
        ' In this example, the pipeline root is the current directory.
        Dim pipeRoot As String = Environment.CurrentDirectory
    
        ' Rebuild the cache of pipeline and add-in information.
        Dim warnings As String() = AddInStore.Update(pipeRoot)
        If warnings.Length > 0 Then
            For Each one As String In warnings
                Console.WriteLine(one)
            Next one
        End If
    
        ' Find add-ins of type LibraryManager under the specified pipeline root directory.
        Dim tokens As Collection(Of AddInToken) = AddInStore.FindAddIns(GetType(LibraryManager), pipeRoot)
        ' Determine which add-in to use.
        Dim selectedToken As AddInToken = ChooseAddIn(tokens)
    
        ' Activate the selected AddInToken in a new
        ' application domain with a specified security trust level.
        Dim manager As LibraryManager = selectedToken.Activate(Of LibraryManager)(AddInSecurityLevel.FullTrust)
    
        ' Create a collection of books.
        Dim books As IList(Of BookInfo) = CreateBooks()
    
        ' Show the collection count.
        Console.WriteLine("Number of books:  {0}",books.Count.ToString())
    
        ' Have the add-in process the books.
        ' The add-in will discount computer books by $20
        ' and list their before and after prices. It
        ' will also remove all horror books.
        manager.ProcessBooks(books)
    
        ' List the genre of each book. There
        ' should be no horror books.
        For Each bk As BookInfo In books
            Console.WriteLine(bk.Genre())
        Next bk
    
        Console.WriteLine("Number of books: {0}", books.Count.ToString())
    
        Console.WriteLine()
        ' Have the add-in pass a BookInfo object
        ' of the best selling book.
        Dim bestBook As BookInfo = manager.GetBestSeller()
        Console.WriteLine("Best seller is {0} by {1}", bestBook.Title(), bestBook.Author())
    
        ' Have the add-in show the sales tax rate.
        manager.Data("sales tax")
    
        Dim ctrl As AddInController = AddInController.GetAddInController(manager)
        ctrl.Shutdown()
        Console.WriteLine("Press any key to exit.")
        Console.ReadLine()
    End Sub
    
    
    
    Private Shared Function ChooseAddIn(ByVal tokens As Collection(Of AddInToken)) As AddInToken
        If tokens.Count = 0 Then
            Console.WriteLine("No add-ins of this type are available")
            Return Nothing
        End If
        Console.WriteLine("{0} Available add-in(s):",tokens.Count.ToString())
        For i As Integer = 0 To tokens.Count - 1
            ' Show AddInToken properties.
            Console.WriteLine("[{0}] - {1}, Publisher: {2}, Version: {3}, Description: {4}", (i + 1).ToString(), tokens(i).Name, tokens(i).Publisher, tokens(i).Version, tokens(i).Description)
        Next i
        Console.WriteLine("Select add-in by number:")
        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 ChooseAddIn(tokens)
    End Function
    
    
    Friend Shared Function CreateBooks() As IList(Of BookInfo)
        Dim books As List(Of BookInfo) = New List(Of BookInfo)()
    
        Dim ParamId As String = ""
        Dim ParamAuthor As String = ""
        Dim ParamTitle As String = ""
        Dim ParamGenre As String = ""
        Dim ParamPrice As String = ""
        Dim ParamPublish_Date As String = ""
        Dim ParamDescription As String = ""
    
        Dim xDoc As XmlDocument = New XmlDocument()
        xDoc.Load("c:\Books.xml")
    
         Dim xRoot As XmlNode = xDoc.DocumentElement
         If xRoot.Name = "catalog" Then
            Dim bklist As XmlNodeList = xRoot.ChildNodes
            For Each bk As XmlNode In bklist
                ParamId = bk.Attributes(0).Value
                Dim dataItems As XmlNodeList = bk.ChildNodes
                Dim items As Integer = dataItems.Count
                For Each di As XmlNode In dataItems
                    Select Case di.Name
                        Case "author"
                            ParamAuthor = di.InnerText
                        Case "title"
                            ParamTitle = di.InnerText
                        Case "genre"
                            ParamGenre = di.InnerText
                         Case "price"
                            ParamPrice = di.InnerText
                         Case "publish_date"
                            ParamAuthor = di.InnerText
                         Case "description"
                            ParamDescription = di.InnerText
                          Case Else
                    End Select
    
                Next di
                books.Add(New MyBookInfo(ParamId, ParamAuthor, ParamTitle, ParamGenre, ParamPrice, ParamPublish_Date, ParamDescription))
            Next bk
    
         End If
        Return books
    End Function
    
    
    End Class
    
    Friend Class MyBookInfo
        Inherits BookInfo
        Private _id As String
        Private _author As String
        Private _title As String
        Private _genre As String
        Private _price As String
        Private _publish_date As String
        Private _description As String
    
        Public Sub New(ByVal id As String, ByVal author As String, ByVal title As String, ByVal genre As String, ByVal price As String, ByVal publish_date As String, ByVal description As String)
            _id = id
            _author = author
            _title = title
            _genre = genre
            _price = price
            _publish_date = publish_date
            _description = description
        End Sub
    
        Public Overrides Function ID() As String
            Return _id
        End Function
    
        Public Overrides Function Title() As String
            Return _title
        End Function
    
        Public Overrides Function Author() As String
            Return _author
        End Function
    
         Public Overrides Function Genre() As String
            Return _genre
         End Function
        Public Overrides Function Price() As String
            Return _price
        End Function
        Public Overrides Function Publish_Date() As String
            Return _publish_date
        End Function
        Public Overrides Function Description() As String
            Return _description
        End Function
    End Class
    End Namespace
    
    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.Text;
    using LibraryContractsHAV;
    using System.AddIn.Hosting;
    using System.Xml;
    
    
    namespace ListAdaptersHost
    {
    class Program
    {
    static void Main(string[] args)
    {
    
        // In this example, the pipeline root is the current directory.
        String pipeRoot = Environment.CurrentDirectory;
    
        // Rebuild the cache of pipeline and add-in information.
        string[] warnings = AddInStore.Update(pipeRoot);
        if (warnings.Length > 0)
        {
            foreach (string one in warnings)
            {
                Console.WriteLine(one);
            }
        }
    
        // Find add-ins of type LibraryManager under the specified pipeline root directory.
        Collection<AddInToken> tokens = AddInStore.FindAddIns(typeof(LibraryManager), pipeRoot);
        // Determine which add-in to use.
        AddInToken selectedToken = ChooseAddIn(tokens);
    
        // Activate the selected AddInToken in a new
        // application domain with a specified security trust level.
        LibraryManager manager = selectedToken.Activate<LibraryManager>(AddInSecurityLevel.FullTrust);
    
        // Create a collection of books.
        IList<BookInfo> books = CreateBooks();
    
        // Show the collection count.
        Console.WriteLine("Number of books:  {0}",books.Count.ToString());
    
        // Have the add-in process the books.
        // The add-in will discount computer books by $20
        // and list their before and after prices. It
        // will also remove all horror books.
        manager.ProcessBooks(books);
    
        // List the genre of each book. There
        // should be no horror books.
        foreach (BookInfo bk in books)
        {
            Console.WriteLine(bk.Genre());
        }
    
        Console.WriteLine("Number of books: {0}", books.Count.ToString());
    
        Console.WriteLine();
        // Have the add-in pass a BookInfo object
        // of the best selling book.
        BookInfo bestBook = manager.GetBestSeller();
        Console.WriteLine("Best seller is {0} by {1}", bestBook.Title(), bestBook.Author());
    
        // Have the add-in show the sales tax rate.
        manager.Data("sales tax");
    
        AddInController ctrl = AddInController.GetAddInController(manager);
        ctrl.Shutdown();
        Console.WriteLine("Press any key to exit.");
        Console.ReadLine();
    }
    
    
    
    private static AddInToken ChooseAddIn(Collection<AddInToken> tokens)
    {
        if (tokens.Count == 0)
        {
            Console.WriteLine("No add-ins of this type are available");
            return null;
        }
        Console.WriteLine("{0} Available add-in(s):",tokens.Count.ToString());
        for (int i = 0; i < tokens.Count; i++)
        {
            // Show AddInToken properties.
            Console.WriteLine("[{0}] - {1}, Publisher: {2}, Version: {3}, Description: {4}",
                (i + 1).ToString(), tokens[i].Name, tokens[i].Publisher,
                tokens[i].Version, tokens[i].Description);
        }
        Console.WriteLine("Select add-in by number:");
        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 ChooseAddIn(tokens);
    }
    
    
    internal static IList<BookInfo> CreateBooks()
    {
        List<BookInfo> books = new List<BookInfo>();
    
        string ParamId = "";
        string ParamAuthor = "";
        string ParamTitle = "";
        string ParamGenre = "";
        string ParamPrice = "";
        string ParamPublish_Date = "";
        string ParamDescription = "";
    
        XmlDocument xDoc = new XmlDocument();
        xDoc.Load(@"c:\Books.xml");
    
         XmlNode xRoot = xDoc.DocumentElement;
         if (xRoot.Name == "catalog")
        {
            XmlNodeList bklist = xRoot.ChildNodes;
            foreach (XmlNode bk in bklist)
            {
                ParamId = bk.Attributes[0].Value;
                XmlNodeList dataItems = bk.ChildNodes;
                int items = dataItems.Count;
                foreach (XmlNode di in dataItems)
                {
                    switch (di.Name)
                    {
                        case "author":
                            ParamAuthor = di.InnerText;
                            break;
                        case "title":
                            ParamTitle = di.InnerText;
                            break;
                        case "genre":
                            ParamGenre = di.InnerText;
                            break;
                         case "price":
                            ParamPrice = di.InnerText;
                            break;
                         case "publish_date":
                            ParamAuthor = di.InnerText;
                            break;
                         case "description":
                            ParamDescription = di.InnerText;
                            break;
                          default:
                            break;
                    }
    
                }
                books.Add(new MyBookInfo(ParamId, ParamAuthor, ParamTitle, ParamGenre,
                                ParamPrice, ParamPublish_Date, ParamDescription));
            }
    
        }
        return books;
    }
    
    
    }
    
    class MyBookInfo : BookInfo
    {
        private string _id;
        private string _author;
        private string _title;
        private string _genre;
        private string _price;
        private string _publish_date;
        private string _description;
    
        public MyBookInfo(string id, string author, string title,
                            string genre, string price,
                            string publish_date, string description)
        {
            _id = id;
            _author = author;
            _title = title;
            _genre = genre;
            _price = price;
            _publish_date = publish_date;
            _description = description;
        }
    
        public override string ID()
        {
            return _id;
        }
    
        public override string Title()
        {
            return _title;
        }
    
        public override string Author()
        {
            return _author;
        }
    
         public override string Genre()
        {
            return _genre;
        }
        public override string Price()
        {
            return _price;
        }
        public override string Publish_Date()
        {
            return _publish_date;
        }
        public override string Description()
        {
            return _description;
        }
    }
    }
    

To create the books.xml data file

  1. Add a new XML file to the BookStore project. In the Add New Item dialog box, name the file books.xml.

  2. Replace the default contents of books.xml with the XML from Sample XML File (books.xml).

  3. In Solution Explorer, select books.xml, and in Properties set Copy to Output Directory to Copy always.

Creating the Add-In

An add-in implements the methods specified by the add-in view. This add-in implements the ProcessBooks method. The method performs the following operations on a collection of BookInfo objects that the host passes to it:

  • Discounts the price of all computer books by 20 percent.

  • Removes all horror books from the collection.

This add-in also implements the GetBestSeller method by passing a BookInfo object that describes the best-selling book to the host.

To create the add-in

  1. Add a new project named BooksAddin to the BooksPipeline solution. Base it on the Class Library template.

  2. In Visual Basic, open Properties for the project and use the Application tab to delete the default value supplied for Root namespace.

  3. In Solution Explorer, add a reference to the System.AddIn.dll assembly to the BooksAddin project.

  4. Add a project reference to the AddInViews project. In the reference Properties, set Copy Local to False for this reference, to prevent the referenced assembly from being copied to the local build folder.

  5. In the class file, add references to the System.AddIn and the add-in view segment namespaces.

  6. Use the following code for the add-in application.

    
    Imports Microsoft.VisualBasic
    Imports System
    Imports System.Collections.Generic
    Imports System.Text
    Imports LibraryContractsBase
    Imports System.AddIn
    Imports System.IO
    
    Namespace SampleAddIn
    <AddIn("Books AddIn",Version:="1.0.0.0")> _
    Public Class BooksAddIn
        Inherits LibraryManager
        ' Calls methods that updates book data
        ' and removes books by genre.
        Public Overrides Sub ProcessBooks(ByVal books As IList(Of BookInfo))
            For i As Integer = 0 To books.Count - 1
                books(i) = UpdateBook(books(i))
            Next i
            RemoveGenre("horror", books)
        End Sub
    
        Public Overrides Function Data(ByVal txt As String) As String
            ' assumes txt = "sales tax"
            Dim rtxt As String = txt & "= 8.5%"
            Return rtxt
        End Function
    
        Friend Shared Function RemoveGenre(ByVal genre As String, ByVal books As IList(Of BookInfo)) As IList(Of BookInfo)
            ' Remove all horror books from the collection.
            Dim i As Integer = 0
            Do While i < books.Count
                If books(i).Genre().ToLower() = "horror" Then
                    books.RemoveAt(i)
                End If
                i += 1
            Loop
            Return books
        End Function
    
        ' Populate a BookInfo object with data
        ' about the best selling book.
        Public Overrides Function GetBestSeller() As BookInfo
            Dim ParamId As String = "bk999"
            Dim ParamAuthor As String = "Corets, Eva"
            Dim ParamTitle As String = "Cooking with Oberon"
            Dim ParamGenre As String = "Cooking"
            Dim ParamPrice As String = "7.95"
            Dim ParamPublish_Date As String = "2006-12-01"
            Dim ParamDescription As String = "Recipes for a post-apocalyptic society."
    
            Dim bestBook As MyBookInfo = New MyBookInfo(ParamId, ParamAuthor, ParamTitle, ParamGenre, ParamPrice, ParamPublish_Date, ParamDescription)
            Return bestBook
        End Function
    
        Friend Shared Function UpdateBook(ByVal bk As BookInfo) As BookInfo
            ' Discounts the price of all
            ' computer books by 20 percent.
            Dim ParamId As String = bk.ID()
            Dim ParamAuthor As String = bk.Author()
            Dim ParamTitle As String = bk.Title()
            Dim ParamGenre As String = bk.Genre()
            Dim ParamPrice As String = bk.Price()
            If ParamGenre.ToLower() = "computer" Then
                Dim oldprice As Double = Convert.ToDouble(ParamPrice)
                Dim newprice As Double = oldprice - (oldprice *.20)
                ParamPrice = newprice.ToString()
                If ParamPrice.IndexOf(".") = ParamPrice.Length - 4 Then
                    ParamPrice = ParamPrice.Substring(1, ParamPrice.Length - 1)
                End If
                Console.WriteLine("{0} - Old Price: {1}, New Price: {2}",ParamTitle,oldprice.ToString(),ParamPrice)
            End If
            Dim ParamPublish_Date As String = bk.Publish_Date()
            Dim ParamDescription As String = bk.Description()
    
            Dim bookUpdated As BookInfo = New MyBookInfo(ParamId, ParamAuthor, ParamTitle, ParamGenre, ParamPrice, ParamPublish_Date, ParamDescription)
    
            Return bookUpdated
    
        End Function
    
    End Class
    
    ' Creates a BookInfo object.
    Friend Class MyBookInfo
        Inherits BookInfo
        Private _id As String
        Private _author As String
        Private _title As String
        Private _genre As String
        Private _price As String
        Private _publish_date As String
        Private _description As String
    
        Public Sub New(ByVal id As String, ByVal author As String, ByVal title As String, ByVal genre As String, ByVal price As String, ByVal publish_date As String, ByVal description As String)
            _id = id
            _author = author
            _title = title
            _genre = genre
            _price = price
            _publish_date = publish_date
            _description = description
        End Sub
    
        Public Overrides Function ID() As String
            Return _id
        End Function
    
        Public Overrides Function Title() As String
            Return _title
        End Function
    
        Public Overrides Function Author() As String
            Return _author
        End Function
    
        Public Overrides Function Genre() As String
            Return _genre
        End Function
        Public Overrides Function Price() As String
            Return _price
        End Function
        Public Overrides Function Publish_Date() As String
            Return _publish_date
        End Function
        Public Overrides Function Description() As String
            Return _description
        End Function
    End Class
    
    End Namespace
    
    using System;
    using System.Collections.Generic;
    using System.Text;
    using LibraryContractsBase;
    using System.AddIn;
    using System.IO;
    
    namespace BooksAddIn
    {
    [AddIn("Books AddIn",Description="Book Store Data",
           Publisher="Microsoft",Version="1.0.0.0")]
    
    public class BooksAddIn : LibraryManager
    {
        // Calls methods that updates book data
        // and removes books by their genre.
        public override void ProcessBooks(IList<BookInfo> books)
        {
            for (int i = 0; i < books.Count; i++)
            {
                books[i] = UpdateBook(books[i]);
            }
            RemoveGenre("horror", books);
        }
    
        public override string Data(string txt)
        {
            // assumes txt = "sales tax"
            string rtxt = txt + "= 8.5%";
            return rtxt;
        }
    
        internal static IList<BookInfo> RemoveGenre(string genre, IList<BookInfo> books)
        {
            // Remove all horror books from the collection.
            for (int i = 0; i < books.Count; i++)
            {
                if (books[i].Genre().ToLower() == "horror")
                    books.RemoveAt(i);
            }
            return books;
        }
    
        // Populate a BookInfo object with data
        // about the best selling book.
        public override BookInfo GetBestSeller()
        {
            string ParamId = "bk999";
            string ParamAuthor = "Corets, Eva";
            string ParamTitle = "Cooking with Oberon";
            string ParamGenre = "Cooking";
            string ParamPrice = "7.95";
            string ParamPublish_Date = "2006-12-01";
            string ParamDescription = "Recipes for a post-apocalyptic society.";
    
            MyBookInfo bestBook = new MyBookInfo(ParamId, ParamAuthor, ParamTitle, ParamGenre,
                                    ParamPrice, ParamPublish_Date, ParamDescription);
            return bestBook;
        }
    
        internal static BookInfo UpdateBook(BookInfo bk)
        {
            // Discounts the price of all
            // computer books by 20 percent.
            string ParamId = bk.ID();
            string ParamAuthor = bk.Author();
            string ParamTitle = bk.Title();
            string ParamGenre = bk.Genre();
            string ParamPrice = bk.Price();
            if (ParamGenre.ToLower() == "computer")
            {
                double oldprice = Convert.ToDouble(ParamPrice);
                double newprice = oldprice - (oldprice * .20);
                ParamPrice = newprice.ToString();
                if (ParamPrice.IndexOf(".") == ParamPrice.Length - 4)
                    ParamPrice = ParamPrice.Substring(1, ParamPrice.Length - 1);
                Console.WriteLine("{0} - Old Price: {1}, New Price: {2}",ParamTitle,oldprice.ToString(),ParamPrice);
            }
            string ParamPublish_Date = bk.Publish_Date();
            string ParamDescription = bk.Description();
    
            BookInfo bookUpdated = new MyBookInfo(ParamId, ParamAuthor, ParamTitle, ParamGenre,
                            ParamPrice, ParamPublish_Date, ParamDescription);
    
            return bookUpdated;
    
        }
    
    }
    
    // Creates a BookInfo object.
    class MyBookInfo : BookInfo
    {
        private string _id;
        private string _author;
        private string _title;
        private string _genre;
        private string _price;
        private string _publish_date;
        private string _description;
    
        public MyBookInfo(string id, string author, string title,
                            string genre, string price,
                            string publish_date, string description)
        {
            _id = id;
            _author = author;
            _title = title;
            _genre = genre;
            _price = price;
            _publish_date = publish_date;
            _description = description;
        }
    
        public override string ID()
        {
            return _id;
        }
    
        public override string Title()
        {
            return _title;
        }
    
        public override string Author()
        {
            return _author;
        }
    
        public override string Genre()
        {
            return _genre;
        }
        public override string Price()
        {
            return _price;
        }
        public override string Publish_Date()
        {
            return _publish_date;
        }
        public override string Description()
        {
            return _description;
        }
    }
    
    }
    

Deploying the Pipeline

You are now ready to build and deploy the add-in segments to the required pipeline directory structure.

To deploy the segments to the pipeline

  1. For each project in the solution, use the Build tab of Project Properties (the Compile tab in Visual Basic) to set the value of the Output path (the Build output path in Visual Basic) as shown in the following table.

    Project

    Path

    BooksAddIn

    Pipeline\AddIns\CalcV1

    AddInSideAdapters

    Pipeline\AddInSideAdapters

    AddInViews

    Pipeline\AddInViews

    LibraryContracts

    Pipeline\Contracts

    BookStore

    Pipeline (or your own application directory)

    HostSideAdapters

    Pipeline\HostSideAdapters

    HostViews

    Pipeline (or your own application directory)

    Note

    If you decided to put your application in a location other than the Pipeline folder, make sure to change the host code that specifies the location of the pipeline root directory.

  2. Build the Visual Studio solution.

    For information about deploying to the pipeline, see Pipeline Development Requirements.

Running the Host Application

You are now ready to run the host and interact with the add-in.

To run the host application

  1. At the command prompt, go to the pipeline root directory and run the host application. In this example, the host application is BookStore.exe.

  2. The host finds all available add-ins of its type and prompts you to select an add-in. Enter 1 for the only available add-in.

    The host activates the add-in and uses it to perform several operations on the list of books.

  3. Press any key to close the application.

See Also

Tasks

Walkthrough: Creating an Extensible Application

Walkthrough: Enabling Backward Compatibility as Your Host Changes

Concepts

Pipeline Development Requirements

Contracts, Views, and Adapters

Pipeline Development