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 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.

You need the following components to complete this walkthrough:

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.

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.

    NoteNote

    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.

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.

    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.

    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.

    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.

    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.

    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();
        }
    }
    

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 note 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.

    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.

    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.

    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.

    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);
            }
        }
    }
    }
    

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 note 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.

    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.

    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.

    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.

    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);
            }
        }
    }
    }
    

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 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.

    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.

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.

    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;
        }
    }
    
    }
    

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 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.

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.

Was this page helpful?
(1500 characters remaining)
Thank you for your feedback
Show:
© 2014 Microsoft