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();
}
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.
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
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
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
if (this.TypeInfrastructureManager != null)
this.TypeInfrastructureManager.AdapterResolve += new EventHandler<AdapterResolveEventArgs>(TypeInfrastructureManager_AdapterResolve);
handle.Contract.HookCustomAdapter();
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
return _nameCollection.GetName(index);
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);
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
// (We never call this in our add-in side adapter so no need to implement it)
throw new System.NotImplementedException();
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.