2.2 Component Model (R7)

Skip to end of metadata
Go to start of metadata

ReSharper has two kinds of components – shell and solution components. Knowledge and understanding of how these differ is critical to being able to write plug-ins that successfully use ReSharper services.

Shell Components

A Shell Component is a component that gets created when the Visual Studio shell is created. Shell components are typically marked with the ShellComponent attribute.

Shell components are useful if you want to use some service in your plug-in that you want to be created virtually as soon as you plug-in is loaded. To do that, simply write:

In certain times, you might need to access other shell components (e.g., ReSharper’s shell components) from within your code. There are, essentially, two ways of doing this.

First, you can use constructor injection, i.e., simply define the component in the constructor of your class and, if the shell component is available, it will be injected automatically:

The other, more straightforward solution, is simply calling Shell.Instance.GetComponent() as follows:

Solution Components

Solution components are components that are tied to the solution currently opened. Consequently, they cannot be accessed when a solution is not present (e.g., in shell components). Solution components use the SolutionComponent attribute.

Solution components are typically acquired through different means compared to shell components. In most cases, you are likely to need a solution component in some method that has been provided, e.g., an IDataContext or even an ISolution. That being the case, you can simply get the solution from this context, and subsequently call GetComponent() on the solution. For example:

Lazy acquisition

In addition to getting the components directly, it's also possible to get them to only be acquired at the time of use. To do this, simply use the type Lazy<ComponentType> instead of ComponentType when querying or injecting a component.

Acquiring Visual Studio interfaces

ReSharper also allows access to interfaces implemented by Visual Studio. However, it is recommended that you try and avoid the use of Visual Studio interfaces, as this usually ties your plugin to a particular version of Visual Studio. You are strongly encouraged to use ReSharper interfaces and services whenever possible.

If you still need to get an interface implemented by Visual Studio, or by a Visual Studio extension, you can do this in several ways. The easiest is to simply inject the interface as a constructor parameter to your plugin component, or retrieve it from the Shell’s container. For example, to get to VS's Output Window interface IVsOutputWindow, you can acquire it with the following:

Note the use of Lazy<T>. Various Visual Studio interfaces exposed by ReSharper are available as Lazy-only, meaning you can only get them if you ask for Lazy<T>. This allows for delay loading resources, such as VS packages and assemblies, until they are used.

ReSharper does not expose all of Visual Studio’s interfaces in this manner. Instead, it exposes only those needed to support ReSharper's own integration with Visual Studio. In addition, some interfaces are wrapped, to ensure correct usage, e.g. wrapping parameters for COM interop, releasing returned COM intptr's, checking returned HRESULTs, etc. You should consult the whitelist below to see what interfaces are available. If it's there, you can consume it via a component's construcutor.

If you need to access interfaces that aren’t exposed, you have two options:

  1. Get access to the Microsoft.VisualStudio.OLE.Interop.IServiceProvider interface, and request it yourself
  2. Set up a class to expose the required interfaces from the container

Retrieving interfaces from IServiceProvider

First, it’s important to note that there are several IServiceProvider types in the CLR. We are interested in Microsoft.VisualStudio.OLE.Interop.IServiceProvider, which is a COM interface.

ReSharper does not expose this interface directly. If you try and consume IServiceProvider in your constructor, ReSharper will throw an exception, and tell you to use the JetBrains.VsIntegration.Application.RawVsServiceProvider wrapper class. The purpose of this class is to ensure that you know you are deliberately bypassing ReSharper and talking directly to Visual Studio. This isn't a recommended solution, but is sometimes necessary. Make sure you know what you are doing. You should always try and use either a ReSharper interface, or request the Visual Studio interface from ReSharper first - remember that ReSharper wraps some Visual Studio interfaces.

Once you have the RawVsServiceProvider class, you can simply use the Value property to get access to the interface, and query Visual Studio for a specific service. It is now your responsibility to ensure the interface is called correctly, and any COM requirements are met.

Expose interfaces to the container

You can implement a class that will be called by ReSharper during initialisation that can register additional Visual Studio interfaces. It needs to be marked with the WrapVsInterfaces attribute, and implement the IExposeVsServices interface:

This class allows you to expose one or more interfaces that comes from a service (in the example above, it exposes MEF’s IComponentModel interface by first querying for the SComponentModel service), or write a custom function that will be called to return an instance of a specific interface.

One important thing to note is that you can only register an interface once. If there are two plugins both registering the same interface, only the first registration (arbitrarily chosen) will be used. The second is ignored. So it is important not to do anything “fancy” in a custom function - it might never get called! Also, while the second registration is ignored and the registration method completes successfully, ReSharper handles and logs an exception. Since these exceptions are displayed to the user in the exception reporter, it is a good practice to check if the interface is already registered before trying to register yourself. You can use the following extension method to do this (and doesn't interfere with lazy instantiation):

These interfaces are now exposed to ReSharper’s container, and can be injected into the constructor, or retrieved via the Shell’s GetComponent method.

You should use Lazy<TInterface> when consuming a Visual Studio interface, so that Visual Studio packages are not loaded until they are used. For example, accepting a parameter of IEditorOperationsFactoryService will cause the VS editor to be loaded into memory as soon as your plugin is loaded, which is at application startup, rather than when the editor is first needed, which might be after a solution has loaded.

If you expect the interface to not be available, you can mark the parameter with the OptionalAttribute. Note that this only works with interfaces, not Lazy<T>, which means you will lose the benefit of using Lazy<T> (i.e. delay loading of resources) if you use Optional. It is recommended to prefer Lazy<T> over Optional.

To expose a MEF advertised service, copy and paste this extension method into your code, and call with map.Mef<IVSInterface>() from your IExposeVsServices.Register method:

The interface is being registered as LazyOnly, which means you can only get it if you request Lazy<TMefInterface. If there is a chance that the MEF interface is not available, you should request IComponentModel (which isn't exposed by default, see example above) and calling IComponentModel.GetService<> yourself, wrapped in a try/catch block.

Default list of exposed interfaces

For ReSharper 7, the default whitelist of supported interfaces is as follows:

  • IVsSolutionBuildManager, IVsSolutionBuildManager2, IVsSolutionBuildManager3
  • IVsTrackProjectDocuments2
  • IVsTextManager, IVsTextManager2, IVsHiddenTextManager
  • ILocalRegistry, ILocalRegistry2
  • IOleComponentManager
  • IVsUIShell, IVsUIShell2
  • Help, Help2
  • IVsProfferCommands, IVsProfferCommands2, IVsProfferCommands3
  • IVsFontAndColorCacheManager
  • IVsQueryEditQuerySave2
  • IVsCodeDefView
  • IVsDebugger, IVsDebugger2
  • DTE, DTE2
  • IVsCmdNameMapping
  • IProfferService
  • IUIHostLocale
  • IVsAppCommandLine
  • IVsClassView
  • IVsExternalFilesManager
  • IVsFileChangeEx
  • IVsFontAndColorStorage
  • IVsLaunchPad, IVsLaunchPad2
  • IVsLinkedUndoTransactionManager
  • IVsObjBrowser
  • IVsObjectManager2
  • IVsOutputWindow
  • IVsRegisterEditors
  • IVsRegisterPriorityCommandTarget
  • IVsSolution
  • IVsUIShellOpenDocument
  • IVsWebBrowsingService
  • IVsActivityLog
  • IVsExtensibility3

Also, the following Visual Studio 2010 are exposed:

  • IVsEditorAdaptersFactoryService
  • IOutliningManagerService
  • IEditorOperationsFactoryService
  • IClassificationTypeRegistryService
  • IClassificationFormatMapService
  • IViewTagAggregatorFactoryService
  • IVsSolution4
  • IVsUIShell4

And Visual Studio 2012:

  • IVsTaskSchedulerService
  • IVsUIShell5
  • IVsFileChangeExPrivate

Furthermore, the following classes are used instead of accessing the VS interfaces directly:

  • RawVsServiceProvider (IServiceProvider - use advisedly!)
  • JetBrains.VsIntegration.Interop.Shim.Shell.IVsMonitorSelection
  • Interop.Shim.Shell.IVsRunningDocumentTable
  • Interop.Shim.Shell.IVsShell
  • JetBrains.VsIntegration.Application.VsUIHostCommandDispatcher (SUIHostCommandDispatcher’s IOleCommandTarget)