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:
Visual Studio.
The books.xml sample file, which can be copied from Sample XML File (books.xml).
Creating a Visual Studio Solution
Use a solution in Visual Studio to contain the projects of your pipeline segments.
To create the pipeline solution
In Visual Studio, create a new project named LibraryContracts. Base it on the Class Library template.
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
In the Visual Studio solution named BooksPipeline, open the LibraryContracts project.
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.
In Solution Explorer, add references to the following assemblies to the project:
Sytem.AddIn.Contract.dll
System.AddIn.dll
In the class file, add references to the System.AddIn.Contract and System.AddIn.Pipeline namespaces.
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.
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
Add a new project named AddInViews to the BooksPipeline solution. Base it on the Class Library template.
In Visual Basic, open Properties for the project and use the Application tab to delete the default value supplied for Root namespace.
In Solution Explorer, add a reference to System.AddIn.dll to the AddInViews project.
Rename the project's default class LibraryManager, and make the class abstract (MustInherit in Visual Basic).
In the class file, add a reference to the System.AddIn.Pipeline namespace.
The LibraryManager class is used to activate the pipeline, so you must apply the AddInBaseAttribute attribute.
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); } }
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.
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
Add a new project named HostViews to the BooksPipeline solution. Base it on the Class Library template.
In Visual Basic, open Properties for the project and use the Application tab to delete the default value supplied for Root namespace.
Rename the project's default class LibraryManager, and make the class abstract (MustInherit in Visual Basic).
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); } }
Add an abstract class (MustInherit class in Visual Basic) to the project, and name it BookInfo.
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 Note:
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
Add a new project named AddInSideAdapters to the BooksPipeline solution. Base it on the Class Library template.
In Visual Basic, open Properties for the project and use the Application tab to delete the default value supplied for Root namespace.
In Solution Explorer, add references to the following assemblies to the AddInSideAdapters project:
System.AddIn.dll
System.AddIn.Contract.dll
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.
Name the class file BookInfoContractToViewAddInAdapter.
In the class file, add a reference to the System.AddIn.Pipeline namespace.
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; } } }
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; } } }
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; } } }
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 Note:
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
Add a new project named HostSideAdapters to the BooksPipeline solution. Base it on the Class Library template.
In Visual Basic, open Properties for the project and use the Application tab to delete the default value supplied for Root namespace.
In Solution Explorer, add references to the following assemblies to the HostSideAdapters project:
System.AddIn.dll
System.AddIn.Contract.dll
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.
In the class file, add a reference to the System.AddIn.Pipeline namespace.
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; } } }
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; } } }
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; } } }
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
Add a new project named BookStore to the BooksPipeline solution. Base it on the Console Application template.
In Visual Basic, open Properties for the project and use the Application tab to delete the default value supplied for Root namespace.
In Solution Explorer, add a reference to the System.AddIn.dll assembly to the BookStore project.
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.
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.
In the class file, add references to the System.AddIn.Pipeline and the host view segment namespaces.
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.
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
Add a new XML file to the BookStore project. In the Add New Item dialog box, name the file books.xml.
Replace the default contents of books.xml with the XML from Sample XML File (books.xml).
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
Add a new project named BooksAddin to the BooksPipeline solution. Base it on the Class Library template.
In Visual Basic, open Properties for the project and use the Application tab to delete the default value supplied for Root namespace.
In Solution Explorer, add a reference to the System.AddIn.dll assembly to the BooksAddin project.
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.
In the class file, add references to the System.AddIn and the add-in view segment namespaces.
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
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.
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
At the command prompt, go to the pipeline root directory and run the host application. In this example, the host application is BookStore.exe.
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.
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
Change History
Date |
History |
Reason |
---|---|---|
July 2008 |
Fixed errors in the text. Added a note about keeping a reference to the contract. |
Customer feedback. |