We have seen how we can use the VSTA provided pipeline to load add-ins and enable various scenarios.  There may be some cases when we want to extend the existing VSTA pipeline to enable new kind of contracts, to allow any of our custom types to cross the contract boundary (if we don’t care about any of the type isolation requirements!) or just for reasons like performance where a custom adapter could give better performance than the generic VSTA mechanism. Let’s see how the existing VSTA pipeline could be extended to hook up custom adapters. By default the Pipeline entry point views for VSTA pipeline are IEntryPoint and IExtendedEntryPoint

namespace Microsoft.VisualStudio.Tools.Applications.Runtime

{

    [AddInBase]

    public interface IEntryPoint

    {

        void FinishInitialization();

        //

        //

        // Parameters:

        //   serviceProvider:

        void Initialize(IServiceProvider serviceProvider);

        //

        void InitializeDataBindings();

        //

        void OnShutdown();

    }

}

namespace Microsoft.VisualStudio.Tools.Applications.Runtime

{

    public interface IExtendedEntryPoint : IEntryPoint

    {

        object GetEntryPointObject();

    }

}

For us to differentiate our custom pipeline from the standard VSTA pipeline we will go ahead and define a new view. But as we want to reuse most of VSTA pipeline we would derive our view from the VSTA view as below. We define a method in the view that enables the integration code to hook custom adapters when needed. Define the view in an assembly that can be shared between hosts and add-ins.

    [AddInBase]

    public interface INewEntryPoint : IExtendedEntryPoint

    {

        void HookCustomAdapters();

    }

 

In my case I just defined the interface in the assembly which has the common interface IService as mentioned in the earlier posts. You could also create a new assembly that holds the definition for these interfaces. Now after we have the view definition in place we would have to define pipeline adapters to make sure we have a valid System.AddIn pipeline that could be loaded. Again this pipeline would be an extension of VSTA pipeline as we want VSTA pipeline to handle all requests unless they are specialized for certain cases (the cases that introduce our new custom adapters).

So to define a pipeline we have to define following four components

1.     Contract

2.     Add-In Side Adapter

3.     Host Side Adapter

4.     View

Let’s define a contract that would work with our already defined view. In this case I defined the following contract which again defines from the IEntryPointContract2 to use the existing VSTA infrastructure.

    [AddInContract]

    public interface INewEntryPointContract:IEntryPointContract2

    {

        void HookCustomAdapter();

    }

That would work with our new view. The Contract is defined in a new assembly contracts.dll (this would just ensure our pipeline components are deployed as per System.AddIn pipeline protocol). Let’s create Host Side Adapter that uses our new view. Our host side adapter for this new pipeline will derive from VSTA host side adapter this would make us reuse VSTA pipeline semantics for activation. (We would need reference to Microsoft.VisualStudio.Tools.Applications.HostAdapter.v9.0.dll)

    [HostAdapter]

    class HostSideAdapter:EntryPointHostAdapter

The Host Adapter would implement the view so that it could be act as a handle to the integration code in the host object model.  The adapter would define the constructor that accepts the contract which would help us communicate across contract boundary.

public HostSideAdapter(INewEntryPointContract entryPointContract)

            : base(entryPointContract)

        {

            if (entryPointContract == null)

                throw new ArgumentNullException("contract");

 

            this.handle = new ContractLock<INewEntryPointContract>(entryPointContract);

           

      

        }

As our Contract is derived from IEntryPointContract2 we can initialize the state for the VSTA adapter by passing the contract to the base adapter as above.

Let’s implement the view methods now in this case

  public void HookCustomAdapters()

VSTA pipeline provides TypeInfrastructureManager, it could be considered as a type that provides all type services in VSTA pipeline. It’s exposed as a protected property in VSTA host side adapter.  Now before we start looking at the implementation of HookCustomAdapters let’s create a scenario in our simple object model as in previous posts that would use custom adapters.

We define a new interface that will be used by add-ins and host through custom adapters rather than VSTA pipeline

   public interface INameCollection

    {

        string GetName(int index);

    }

This interface should be again in an assembly that can be shared between add-ins and host. We could have different interfaces for host and add-ins let’s keep that discussion for another post. In this case added the interface to the same assembly that holds our  INewEntryPoint  interface. We need a method to be added in the host object model. I modified the host object model class as below.

public class ServiceProvider

    {

        *old methods / variables omitted for clarity*

 

        public INameCollection GetNames()

        {

            if (collection == null)

            {

                List<string> listStrings = new List<string>();

                listStrings.Add("TemperatureService");

                listStrings.Add("HumidityService");

                collection = new NameCollection(listStrings);

            }

 

            return collection;

        }

 

    }

 

The added method that uses the INameCollection is as above just return a list of strings. Again there is no significance of what the method does. We also add a implementation NameCollection in the host object model that is only exposed as  INameCollection.

  internal class NameCollection : INameCollection

    {

        List<string> _collection = null;

        internal NameCollection(List<string> collection)

        {

            _collection = collection;

        }

 

        #region INameCollection Members

 

        public string GetName(int index)

        {

            if (_collection.Count > index)

            {

                return _collection[index];

            }

 

            return null;

        }

 

        #endregion

    }

 

Now you need to generate proxy for changed object model using method defined in earlier posts. If you don’t want to do the whole process again you could manually change the proxy file as below.

Add the following definition in ServiceProvider class.

        [global::Microsoft.VisualStudio.Tools.Applications.Runtime.HostMemberAttribute("GetNames", BindingFlags = global::System.Reflection.BindingFlags.Instance | global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.InvokeMethod)]

public virtual INameCollection GetNames() { throw new global::System.NotImplementedException(); }

And following definition in ServiceProviderEntryPoint class

        public INameCollection GetNames()

        {

            return this.RemoteObject.GetNames();

        }

You don’t need to do anything in proxy file for NameCollection type defined in Host Object Model as that’s internal. The interface defined here is only a view for add-ins and host. To cross the contract boundary while working with this INameCollection interface we still need a contract. Define a contract as below in your contracts assembly.

    public interface INameCollectionContract:IContract

    {

        string GetName(int index);

    }

Note that this interface is not marked as [AddInContract] as our INewEntryPointContract because this is not a contract used for activation of add-in pipeline. Now we have our contracts, views and host object model scenarios cooked. Let’s go back to public void HookCustomAdapter() implementation.  In this implementation we will hook our adapter for our INameCollection.

The implementation looks like

  #region INewEntryPoint Members

 

        public void HookCustomAdapters()

        {

            if (this.TypeInfrastructureManager != null)

            {

this.TypeInfrastructureManager.AdapterResolve += new EventHandler<AdapterResolveEventArgs>(TypeInfrastructureManager_AdapterResolve);

            }

 

            handle.Contract.HookCustomAdapter();

        }

 

 #endregion

 

     void TypeInfrastructureManager_AdapterResolve(object sender, AdapterResolveEventArgs e)

        {

            if (typeof(INameCollection).IsAssignableFrom(e.ExpectedType))

            {

e.AdapterToReturn = (INameCollectionContract)(new NameCollectionAdapter((INameCollection)e.ObjectToPack,this.TypeInfrastructureManager));

            }

        }

 

Here we define an event handler that hooks to Adapter Resolve event for TypeInfrastructureManager. The method also called HookCustomAdapters on the contract that will enable add-in side adapter to do any processing if needed for the custom adapter hook up. Look at TypeInfrastructureManager_AdapterResolve you would see we check if the ExpectedType is of type INameCollection we create a specific adapter instance (adapter meant for INameCollection specifically). The adapter just would do impedance match between view and the contract. The adapter definition is as below. Define the following interface in the Host Side Adapter assembly.

public class NameCollectionAdapter:ContractAdapterBase,INameCollectionContract

    {

        INameCollection _nameCollection;

 

        public NameCollectionAdapter(INameCollection nameCollection, TypeInfrastructureManager  infrastructureManager):base(infrastructureManager)

        {

            _nameCollection = nameCollection;

        }

 

 

        #region INameCollectionContract Members

 

        public string GetName(int index)

        {

           return  _nameCollection.GetName(index);

        }

 

        #endregion

 

    }

It just exposes the INameCollectionContract to the add-in side of the pipeline.

Now let’s define Add-In Side Adapter assembly. Here again the adapter would derive from the VSTA add-in side adapter and this time we will just hook up with ProxyResolve event (as we are on add-in side that is consuming the interface).

The definition is as below (you would need reference to Microsoft.VisualStudio.Tools.Applications.AddInAdapter.v9.0.dll)

[AddInAdapter]

    public class CustomAddInAdapter : AddInAdapter, INewEntryPointContract

    {

        private INewEntryPoint entryPoint;

        public CustomAddInAdapter(INewEntryPoint initialEntryPoint)

            : base(initialEntryPoint)

        {

            entryPoint = initialEntryPoint;

 

        }

 

        void TypeInfrastructureManager_ProxyResolve(object sender, ProxyResolveEventArgs e)

        {

            if (e.Adapter is INameCollectionContract)

            {

                e.ProxyToReturn = (INameCollection)(new NameCollectionAddInSideAdapter(e.Adapter as INameCollectionContract));

            }

        }

 

        #region INewEntryPointContract Members

 

        public void HookCustomAdapter()

        {

            this.TypeInfrastructureManager.ProxyResolve += new EventHandler<ProxyResolveEventArgs>(TypeInfrastructureManager_ProxyResolve);

        }

 

        #endregion

    }

 

So now you have add-in side adapter, host side, contract and view assemblies available. Compile these assemblies and copy them in the location that holds VSTA pipeline

Again on my machine this is at

C:\Program Files (x86)\Common Files\microsoft shared\VSTA\Pipeline

Copy view assembly that contains INewEntryPoint and INameCollection in

C:\Program Files (x86)\Common Files\microsoft shared\VSTA\Pipeline\AddInViews

Contracts.dll in

C:\Program Files (x86)\Common Files\microsoft shared\VSTA\Pipeline\Contracts

Similarly add-in side adapter assembly and host side adapter assembly in C:\Program Files (x86)\Common Files\microsoft shared\VSTA\Pipeline\AddInSideAdapters and C:\Program Files (x86)\Common Files\microsoft shared\VSTA\Pipeline\HostSideAdapters folders respectively.

To create AddInStore cache and make sure pipeline is valid rebuild the vsta pipeline

AddInUtil.exe   –PipelineRoot:” C:\Program Files (x86)\Common Files\microsoft shared\VSTA\Pipeline” –Rebuild

AddInUtil.exe is available in v3.5 framework folder under windows folder on your machine.

Now we have pipeline defined with proxy & host object model changed.

One additional change which we need to do to make activation work is to modify the EntryPoint class in proxy class to use the new entry point interface.

  [global::System.AddIn.Pipeline.AddInBaseAttribute(ActivatableAs = new global::System.Type[] { typeof(INewEntryPoint) })]

    public partial class ServiceProviderEntryPoint : INewEntryPoint

    {

        #region INewEntryPoint Members

 

        public void HookCustomAdapters()

        {

            // (We never call this in our add-in side adapter so no need to implement it)

            throw new System.NotImplementedException();

        }

 

        #endregion

Notice the change above to the new interface. Now let’s change the integration code to use the new pipeline. This could be done by just switching the view type in the integration code. Here we would load our first add-in with new pipeline. See the change where we call the hookcustomadapters to hook custom adapters for our specific interface.

 

            Type havType = typeof(INewEntryPoint);

 

            *code omitted for clarity*

      

            if (addins.Count > 0)

            {

 INewEntryPoint addin =     addins[0].Activate<INewEntryPoint>(AddInSecurityLevel.FullTrust);

                      addin.Initialize(serviceProvider);

                     addin.HookCustomAdapters();

                     addin.InitializeDataBindings();

                     addin.FinishInitialization();

            }

            else

            {

             Console.WriteLine("Add In could not be discovered");

            }

 

The add-in code could test the usage with the following change in the add-in class

        TemperatureService tempServ = (TemperatureService)this.GetService("TemperatureService");

        Console.WriteLine(tempServ.Temperature.ToString());

        HumidityService humidServ = (HumidityService)this.GetService("HumidityService");

        Console.WriteLine(humidServ.Humidity.ToString());

        AddInService service = new AddInService();

        this.RegisterService("MYADDINSERVICE", service);

        INameCollection collection = this.GetNames();

       Console.WriteLine(collection.GetName(0));

So we were able to use the new interface with custom adapters which we hooked to VSTA pipeline.