Archive for the ‘bistro’ Category

Injecting functionality into Visual Studio projects, or if neither of the two options are good, go for the third one

Tuesday, July 28th, 2009

For some time now I was dancing around a question: How do I inject additional functionality into a Visual Studio project.

One option would be to build a VSPackage which will be always loaded and will be watching every move you make and interfere when it thinks the time is right. Another option is to implement a custom project type so that these extra commands are only available for the projects of this type.

I was going back and forth between these 2 options. The first one seems to be too intrusive, the second one is too much work - even if you use project flavoring, you still need to create a flavor for every project you intend to support. It also makes it difficult to convert existing projects to bistro projects.

It looks like the right chice is neither and all this time it was right under my nose. The option I am going to go with is based on the VsProjectStartupServices interface. This interface allows to add to the project file a GUID of a global service to be started when the project is opened.

Here is how this approach works:

First, in your package definition you have to declare the service as a global service:

// This attribute registers a service used to
// bootstrap the package for converted projects
[ProvideService(typeof(SProjectManager))]

The next step is to actually instantiate and register the object implementing the service:

protected override void Initialize()
{
   base.Initialize();
   solutionEvents = new SolutionEventSinks();
   projectManager = new ProjectManager(this, solutionEvents);
   IServiceContainer container = this as IServiceContainer;
   container.AddService(typeof(SProjectManager), projectManager, true);
 
    ...
 
}

Do not miss the last parameter to the AddService method. Setting it to true promotes the service registration. In other words makes it global.

Based on this registartion a call to the Package.GetGlobalService(typeof(SProjectManager)) will always return an instance of the ProjectManager. It means that if the package is not active yet, Visual Studio will load the package first, giving it a chance to instantiate and Register the ProjectManager service. This is what will happen if any project with a reference to the ProjectManager service will be opened in Visual Studio.

Now inserting such a reference into a project is pretty simple too. All what is necessary is a command executing the following:

internal void Convert()
{
   object startupService;
 
   ErrorHandler.ThrowOnFailure(
         hierarchy.GetProperty(
                VSConstants.VSITEMID_ROOT,
                (int)__VSHPROPID.VSHPROPID_StartupServices,
                out startupService));
 
   Guid guid = new Guid(Guids.guidProjectManager);
 
   ErrorHandler.ThrowOnFailure(
        ((IVsProjectStartupServices)startupService)
                .AddStartupService(ref guid));
}

Such command can be placed somewhere in the menu and configured in such a way that the command is visible even if the package is not loaded.