ShapeAppBasicCSharpGenericCollectionIndexAdapter sample
We’ll assume that you’ve looked over the previous sample from part 2
This sample will demonstrate how to pass a System.Collections.Generic.IList<ShapeAppCSharp. Document > instance, as a ‘by reference’ parameter, from an AddIn to ShapeAppBasicCSharp host application. This is accomplished by extending the VSTA pipeline with generic list adapters and converters that provide Host application with a (cross-AppDomain) reference to a generic collection passed from a VSTA AddIn. The host application will, in-turn, populate the AddIn collection parameter with host-side Document objects (as RemoteObject instances).
To illustrate, here's an add-in example calling this[typeof(Document), doctypedocs];:
private void AppAddIn_Startup(object sender, EventArgs e)
{
IList<Document> doctypedocs = new List<Document>();
int count = this[typeof(Document), doctypedocs];
testdoc = doctypedocs[0];
string dwgName2 = doctypedocs[0].Drawings[0].Name;
}
Our design goal is for the Add-in author to pass a generic collection parameter to the host application and receive it back populated with host elements. In this case, we’re using the generic collection ( System.Collections.Generic.IList<Document>) as the parameter passed to the host.
To accomplish this we’ll add a new property to the host class ‘Application’ in the sample’s Application.cs (which contains the main object model class’ source code):
public partial class Application : System.Windows.Forms.IWin32Window
{
public int this[System.Type type, IList<Document> documents]
{
get
{
if (type.FullName.Contains("Document"))
{
// Create a test collection of documents.
IList<Document> tempdocs = new List<Document>();
tempdocs.Add(this.document);
// add test collection of documents to (byref) document collection parameter.
foreach (Document doc in tempdocs)
{
documents.Add(doc);
}
}
return documents.Count;
}
}. . .
Then we add the host method’s complement into the proxy object model assembly source in the sample’s proxy.cs class, ApplicationEntryPoint:
[global::System.AddIn.Pipeline.AddInBaseAttribute(ActivatableAs = new global::System.Type[] { typeof(ShapeAppCSharp.IShapeAppEntryPoint) })]
public partial class ApplicationEntryPoint : global::ShapeAppCSharp.IShapeAppEntryPoint
{
/*uses Custom Adapter for System.Collections.Generic.IList*/
public int this[System.Type type, System.Collections.Generic.IList<Document> documents]
{
get
{
//IList<Document> adapter returns a RemoteObjectContract-to-Document converter, so we'll get list
System.Collections.Generic.IList<Document> tempdocs = new System.Collections.Generic.List<Document>();
int count = this.RemoteObject[type, tempdocs];
foreach (Document doc in tempdocs)
{
ShapeAppCSharp.AddInSideAdapters.IRemoteObjectContractToViewAddInAdapter adapter = tempdocs[0] as ShapeAppCSharp.AddInSideAdapters.IRemoteObjectContractToViewAddInAdapter;
documents.Add(adapter.GetView());
}
return documents.Count;
//return this.RemoteObject[type, documents];
}
}. . .
Notice that fix-up code replaces the expected return this.RemoteObject[type, documents];
The meta OM method description is included in the abstract proxy class Application:
[global::Microsoft.VisualStudio.Tools.Applications.Runtime.HostTypeAttribute("ShapeAppCSharp, Microsoft.VisualStudio.Tools.Applications.Samples.ShapeApp.Applic" +
"ation")]
public abstract partial class Application : global::System.MarshalByRefObject, global::System.Windows.Forms.IWin32Window
{
/*uses Custom Adapter for System.Collections.Generic.IList*/
public virtual int this[System.Type type, System.Collections.Generic.IList<Document> documents]
{
[global::Microsoft.VisualStudio.Tools.Applications.Runtime.HostMemberAttribute("Item", BindingFlags = global::System.Reflection.BindingFlags.Instance | global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.GetProperty)]
get { throw new global::System.NotImplementedException(); }
}
. . .
Adding IList<Document> to the VSTA Pipeline – bi-directional support
We must use custom adapters to pass the System.Collections.Generic.IList<Document> type through the VSTA pipeline from addIn to host as a reference parameter. The host must be able to alter the collection’s contents. This is accomplished by overriding, or hooking into the standard VSTA pipeline’s view, host adapter, and addin adapter assemblies.
To do this, I revised the pipeline segment assemblies created previously in my part 2 sample.
Let’s watch it all go…
We’ll look at these interesting snippets in the sequence called by from the AddIn code:
int count = this[typeof(Document), doctypedocs];
HostTypeMapProvider’s GetTypeForCanonicalName:
In the HostTypeMapProvider’s GetTypeForCanonicalName(), we add the VSTA generated -canonical name mapping for IList<Document> , the type our addIn passes as parameter. This causes, ProxyResolve, and AdapterResolve to be fired when passing parameter types with a canonical name that does not exist in the host type map.
public System.Type GetTypeForCanonicalName(string canonicalName)
{
if (canonicalName == "Microsoft.VisualStudio.Tools.Applications.Adapter.v9.0, Microsoft.VisualStudio.Tools.Applications.DynamicProxy`1[[System.Collections.Generic.IList`1[[Microsoft.VisualStudio.Tools.Applications.Samples.ShapeApp.Document, ShapeAppCSharpProxy, Version=1.0.0.0, Culture=neutral, PublicKeyToken=3c3c0c46dd27dbcf]], mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]")
{
return typeof(System.Collections.Generic.IList<Document>);
}
//default fetch from the dictionary
if (canonicalToTypeName.ContainsKey(canonicalName))
{
return canonicalToTypeName[canonicalName];
}
return null;
}
AddIn-side AdapterResolve:
When the AddIn passes a generic collection parameter to the host for its use, the VSTA pipeline calls TypeInfrastructureManager_AdapterResolve to explicitly resolve the IList<Document> type, and pass a collection contract adapter to the host AppDomain.
void TypeInfrastructureManager_AdapterResolve(object sender, AdapterResolveEventArgs e)
{
if (typeof(System.Collections.Generic.IList<Document>).IsAssignableFrom(e.ExpectedType))
{
e.AdapterToReturn = System.AddIn.Pipeline.CollectionAdapters.ToIListContract<Document, RemoteObjectAdapter>(e.ObjectToPack as System.Collections.Generic.IList<Document>, DocumentAddInAdapter.ViewToContractAdapter, DocumentAddInAdapter.ContractToViewAdapter);
}
}
Host-side ProxyResolve:
When the host receives the IListContract from the AddIn-side adapter, the VSTA pipeline calls TypeInfrastructureManager_ProxyResolve to resolve AddIn’s IListContract to a Document List passed to the host application.
[Find in ShapeAppCSharp.HostSideAdapters.IShapeAppEntryPointHostAdapter. TypeInfrastructureManager_AdapterResolve()]
void TypeInfrastructureManager_ProxyResolve(object sender, ProxyResolveEventArgs e)
{
if (e.Adapter is System.AddIn.Contract.IListContract<RemoteObjectAdapter>)
{
e.ProxyToReturn = System.AddIn.Pipeline.CollectionAdapters.ToIList<RemoteObjectAdapter, Document>(e.Adapter as System.AddIn.Contract.IListContract<RemoteObjectAdapter>, DocumentHostAdapter.ContractToViewAdapter, DocumentHostAdapter.ViewToContractAdapter);
}
}
DocumentHostAdapter:
The host application populates the generic document list, via ContractListAdapter. When populated, System.AddIn.Pipeline.ListContractAdapter, in turn, hooks up Document pipeline segments to make host documents added into the document list available to the AddIn via a DocumentViewToContractHostAdapter.
[Find in ShapeAppCSharp.HostSideAdapters]
public class DocumentHostAdapter
{
internal static RemoteObjectAdapter ViewToContractAdapter(Document view)
{
if (view.GetType().Equals(typeof(IRemoteObjectContractToViewHostAdapter)))
{
return ((IRemoteObjectContractToViewHostAdapter)(view)).GetSourceContract();
}
else
{
return new DocumentViewToContractHostAdapter(view);
}
}
DocumentAddInAdapter:
Next, the System.AddIn.Pipeline.ListContractAdapter passes the RemoteObjectAdapter contract to the AddIn-side Document adapter which holds the contract until the Addin uses one of the Document elements in the generic collection.
public class DocumentAddInAdapter
{
internal static Document ContractToViewAdapter(RemoteObjectAdapter contract)
{
if (((System.Runtime.Remoting.RemotingServices.IsObjectOutOfAppDomain(contract) != true)
&& contract.GetType().Equals(typeof(DocumentViewToContractAddInAdapter))))
{
return ((DocumentViewToContractAddInAdapter)(contract)).GetSourceView();
}
else
{
return new IRemoteObjectContractToViewAddInAdapter(contract);
}
}
IRemoteObjectContractToViewAddInAdapter
To use the Documents in its generic Document collection, the Addin calls the contract to view converter: IRemoteObjectContractToViewAddInAdapter. This code converts the RemoteObjectAdapter contract to a Document proxy object. It is called from the addIn’s proxy class ApplicationEntryPoint.
public class IRemoteObjectContractToViewAddInAdapter : Document
{
private RemoteObjectAdapter _contract;
private System.AddIn.Pipeline.ContractHandle _handle;
static IRemoteObjectContractToViewAddInAdapter()
{
}
public IRemoteObjectContractToViewAddInAdapter(RemoteObjectAdapter contract)
{
_contract = contract;
_handle = new System.AddIn.Pipeline.ContractHandle(contract);
}
// convert RemoteObjectAdapter contract list element to a Document instance
public Document GetView()
{
System.AddIn.Contract.IContract roc = _contract.QueryContract(typeof(System.AddIn.Contract.Automation.IRemoteObjectContract).AssemblyQualifiedName);
return TypeServices.ObjectFromContract(roc, typeof(Document), IShapeAppEntryPointAddInAdapter.tim) as Document;
}
In conclusion
ShapeAppBasicCSharpGenericCollectionIndexAdapter shows the use of custom adapters, tightly integrated with the ShapeAppBasicCSharp object model types and the VSTA pipeline, to pass a custom type (generic Document list) as a parameter from the addIn to the host. By hooking System.AddIn.Pipeline.CollectionAdapters and converters into the VSTA pipeline, and using RemoteObjectAdapters to marshal Document instances across the AppDomain as list elements, the VSTA pipeline is extended, making it possible for the AddIn to create a generic Document list, pass the Document list to the host application and receive it back, populated with host documents. By testing, we demonstrated that bi-directional support for IList<Document>, is available to the VSTA Add-In author.
Posted
Jan 26 2009, 04:16 PM
by
Gary
Filed under: Addin.Contract, Contract, Adapter, VSTA 2.0, ProxyResolve, AdapterResolve, System.AddIn, pipeline, System.AddIn.Pipeline.CollectionAdapters, ByRef, converters, RemoteObjectAdapters