The World’s Leading Microsoft .NET Magazine
   
 
The .NET Addict's Blog

My Top Tags

                                                           

My RSS Feeds








I heart FeedBurner

Latest Diggs - Programming

Computers Blogs - Blog Top Sites

Site Hits

Total: 4,904,749
since: 19 Jan 2005

Extending your Applications with MAF/VSTA - Part III

posted Thu 20 Jul 06

In this article, I'm going to walk through the first steps involved in preparing an otherwise mild-mannered application for that jump into the phonebooth, whereupon it will exit the phonebooth as Superman...er.. no...maybe just an Add-In-Aware application.

The first thing you need to do is open up the solution you've been working on if you've been following along through this series of articles and add the following references to the Windows Forms project:
  • Microsoft.VisualStudio.Tools.Applications.Adapter
  • Microsoft.VisualStudio.Tools.Applications.AddInManager
  • Microsoft.VisualStudio.Tools.Applications.Contract
  • System.AddIn.Contract
Next, you need to create a HostItemProvider. A host item provider is a class that functions like a gateway. An AddIn (think of an AddIn as a client, really) gives the addin host a little poke in the tummy, and after giggling a little bit and taking off its white fluffy hat, the host application responds with remote references to various objects as well as the Contract to which the AddIn host and AddIn must adhere. Create a new folder in the WinForms app called Extensibility, and add a new class to it called HostItemProvider. Copy and paste the following into it, and I'll explain it below:

using System;
using System.Collections.Generic;
using System.Text;
using System.AddIn.Contract;
using System.AddIn.Contract.Automation;
using System.Runtime.Remoting;
using Microsoft.VisualStudio.Tools.Applications.Contract;
using Microsoft.VisualStudio.Tools.Applications; // the SDK sample walkthrough forgets this!!

namespace AddInHostDemo.Extensibility
{
    public class HostItemProvider : ContractAdapterBase,
        IHostItemProviderContract
    {
        private HostedApplicationRoot _application;

        public HostItemProvider(HostedApplicationRoot application,
            TypeInfrastructureManager typeInfrastructureManager)
            : base(typeInfrastructureManager)
        {
            _application = application;
        }

        protected override IContract QueryContract(string contractIdentifier)
        {
            if (string.Compare(contractIdentifier,
                typeof(IHostItemProviderContract).AssemblyQualifiedName,
                StringComparison.Ordinal) == 0)
            {
                return (IContract)this;
            }
            return base.QueryContract(contractIdentifier);
        }

        public IRemoteObjectContract GetHostObject(string objectType,
            string cookie)
        {
            if (string.Compare(objectType,
                typeof(AddInHostDemo.HostedApplicationRoot).FullName,
                StringComparison.Ordinal) == 0)
            {
                RemoteObjectAdapter adapter = new RemoteObjectAdapter(
                    typeof(AddInHostDemo.HostedApplicationRoot),
                    _application,
                    TypeInfrastructureManager);
                return adapter;
            }

            throw new ArgumentOutOfRangeException();
        }

        // plumbing methods, included in ShapeApp sample as-is
        protected override string RemoteToString()
        {
            return this.ToString();
        }

        protected override int GetRemoteHashCode()
        {
            return this.GetHashCode();
        }

        protected override bool RemoteEquals(IContract contract)
        {
            if (contract == null)
                return false;

            if (!System.Runtime.Remoting.RemotingServices.IsObjectOutOfAppDomain(contract))
            {
                HostItemProvider contractAdapter =
                    contract as HostItemProvider;
                return this.Equals(contractAdapter);
            }

            return false;
        }
    }
}

ContractAdapterBase is a class from which all host item providers must inherit. I won't go into too much detail here - for all of the classes that I mention in these articles that are part of the VS 2005 SDK, you can check them out in the SDK documentation. While I'm finding that the walkthrough for AddIns in the SDK basically sucks, the per-class reference sections are actually quite informative. QueryContract returns a host item provider contract. GetHostObject is responsible for returning the appropriate host object. You'll note that in the above case, I return the _application instance whenever the client (AddIn) requests an object with a fully qualified name that matches the fully qualified name of that type. If they didn't ask for that, I defer the request to the parent object. As much as I hate to see it, this entire scenario is very COM-ish. That fact bothers me. It grates on every nerve I own, and a couple that I'm borrowing. It makes the hair on the back of my neck stand up... making me wish I could reach back there to shave it... This is 2006 here people, can we please put all of the COM metaphors, similies, hyperboles, and haikus in the grave where they belong?!?!?!

The next class to create is the ApplicationExtension class. This is kind of a catch-all utility class.  This class is going to contain the wrapper and helper methods that the application will use in order to load AddIns. To load AddIns, this application is going to scan a specific directory for all .dll files, and then use the System.AddIn namespace and Add-In management capabilities from the SDK to load the AddIns. Add the ApplicationExtension class to the Extensibility folder in your WinForms app:

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.VisualStudio.Tools.Applications;
using Microsoft.VisualStudio.Tools.Applications.Contract;
using System.IO;

namespace AddInHostDemo.Extensibility
{
    public class ApplicationException
    {
        private HostedApplicationRoot _application;
        private Context _addInContext;
        private AddInCollection _addInCollection;
        private IHostItemProviderContract _itemProvider;
        private string _addInPath;
        private TypeInfrastructureManager _typeInfrastructureManager;

        internal ApplicationExtension()
        {
            InitializeTypeInfrastructureManager();
        }

        internal void Connect(HostedApplicationRoot application)
        {
            _application = application;
            _itemProvider = new HostItemProvider(
                _application, _typeInfrastructureManager); // differs from the SDK walkthrough..
                                                           // more design guideline violations
            _addInPath = Path.Combine(
                System.Environment.GetFolderPath(
                    Environment.SpecialFolder.MyDocuments),
                    @"AddInHostDemo\AddIns");
            LoadAddIns();
        }

        public void LoadAddIns()
        {
            if (!Directory.Exists(_addInPath))
                return;
            if (_addInContext == null)
            {
                _addInContext = new Context("Application", _itemProvider);
                _addInCollection = new AddInCollection();
                _addInContext.Add(_addInCollection);
            }

            string[] addInDirectoryNames =
                Directory.GetDirectories(_addInPath);
            foreach (string addInDirectoryName in addInDirectoryNames)
            {
                string addInShortName = string.Format("{0}.dll",
                    Path.GetFileName(addInDirectoryName));
                string addInFullName = Path.Combine(
                    addInDirectoryName, addInshortName);
                if (!File.Exists(addInFullName))
                    continue;

                AddIn addIn = new AddIn(addInShortName, addInFullName);
                _addInCollection.Add(addIn);
                addIn.Load(addInDirectoryName);
            }
        }

        internal TypeInfrastructureManager TypeInfrastructureManager
        {
            get { return _typeInfrastructureManager; }
        }

        private void InitializeTypeInfrastructureManager()
        {
            if (_typeInfrastructureManager == null)
                _typeInfrastructureManager = Microsoft.VisualStudio.Tools.Applications.ProxyServices.
                    Helper.CAddInHostDemoInitializer.CreateTypeInfrastructureManager();
        }
    }
}

In a nutshell, this is the code that the main application form is going to call when the application starts up to scan through a directory and load all of the DLLs into the AddIn management system that is now part of the SDK, and will actually be a part of the core framework during the "Orcas" timeframe. Note the bolded, italicized, underlined, red text above. Anybody recognize a naming convention that is not part of the .NET Framework and belongs in the MFC graveyard from whence the necromancer revived it!?!? I do. This one wins the "WTH??!?" award of the day. As you'll see in a second, you run the proxygen.exe tool to create proxies, but you also use it to create what is called a host map, which is a class that contains a little helper class that initializes the type infrastructure manager (a fancy way of referring to a type map dictionary manager). The class that it generates for you is prefixed with a C, MFC-style.

So, to create the CAddInHostDemoInitializer class via proxygen, issue the following command-line from the \Program Files\Visual Studio 2005 SDK\2006.04\VisualStudioToolsForApplications\Tools\Proxygen\x86 directory (the 2006.04 refers to the CTP I'm using):

proxygen /l:"c:\blah\blah\blah-to-your-project\bin\debug\AddinHostDemo.exe" /h:HostMap.cs

Note that you need a compiled executable. Hopefully you built the app before this article and have an EXE handy. Note that you do not need a host item provider in your application to generate proxies or a host map, host item providers are an entirely runtime necessity. Now add that class as an "existing item" to the Extensibility folder of your main app. Rebuild, and you should have a compiled app that pretty much does nothing useful at this point.

At this point, you should have an Extensibility folder with the HostItemProvider.cs and ApplicationExtension.cs classes contained within it. You have not yet generated any client proxies, or a visual studio project template, or registered the application as an AddIn host. I took up more space on this blog post than I expected, so I will defer the host registration and proxy generation to the next post.

Take-aways from this step in the process:
  • The COM metaphor has got to GO GO GO... Maybe its only me, but QueryContract and GetHostObject bring back so many COM memories that I start having nightmarish flashbacks. If its just me, I'll let it slide. This time.
  • The break in workflow to go out and run proxygen just so I can create the host map class to add to my project (this isn't how the walk-through did it, the walk-through used CUT AND PASTE... GAH!) feels detached and doesn't "fit" with the rest of the process.
    • This is screaming to be refactored. You have a process that must be done every time you create an AddIn host application, that is done by an external application that looks the same every time you do it... This, to me, is an ideal candidate for a way to make the process smoother in the future: get rid of the build-time requirement of using proxygen to build a host map, do the host map automatically for me at runtime!
  • It appears as though the power and flexibility of this setup is going to be quite useful. I am, however, weary of one thing: What happens when I add objects, classes, and methods to the addin application? What's the versioning story here? Will I break existing addins, and will I have to create new proxies and new project templates for the new stuff to be exposed? I think I will.. I think I'm having more COM flashbacks...
Stay tuned, same .NET-time, same .NET-channel, and we'll see if our dynamic duo can thwart the evil penguin and create a VSTA AddIn model that is worthy of commercial adoption by application developers everywhere!

tags:            

links: digg this    del.icio.us    technorati    reddit

AddThis Social Bookmark Button




1. Leandro left...
Mon 27 Nov 06 4:58 pm

Your article can be more valuable for all worldwide community if you don't use slangs and non technical words. It's so hard to us understand this flowery text...


2. Melody left...

LOL- it's nice to see a little humor in the technobabble. Very nice.


Tag Related Posts

WPF Bumper Stickers

Tue 12 Dec 06 7:32 P GMT-05

First Impressions of Windows Vista RC1

Thu 07 Sep 06 1:30 P GMT-05
tags:                      

Localizing a WPF Application

Tue 22 Aug 06 11:39 A GMT-05
tags:            

WPF Slide Show and Photo Album

Fri 18 Aug 06 6:48 P GMT-05

Is Windows Workflow Foundation Too Complex?

Fri 18 Aug 06 12:15 P GMT-05

ADO.NET Entity Framework Announced Today!

Wed 16 Aug 06 11:08 A GMT-05

Extending your Applications with MAF/VSTA - Part III

Fri 21 Jul 06 12:30 A GMT-05
tags:            

Extending your Application with MAF/VSTA AddIns

Thu 20 Jul 06 1:51 A GMT-05
tags:            

July CTP is out... yay.

Tue 18 Jul 06 6:34 P GMT-05
tags:    

.NET Framework 3.0 June CTP is out!

Fri 23 Jun 06 6:23 P GMT-05
tags: