1038 .
chaPter 53 mAnAged exTenSibiliTy FrAmeWork (meF)
this contract by supplying either a string or a type to the constructor of either the ImportAttribute
or the ExportAttribute. The following code snippet shows three exports that all have the same
contract:
c#
class Settings
{
[Export]
public string Username;
[Export(typeof(string))]
public string Password;
[Export("System.String")]
public string Server;
}
Code snippet GettingStarted\Settings.cs
Vb
Public Class Settings
< Export() >
Dim Username As String
< Export(GetType(String)) >
Dim Password As String
< Export("System.String") >
Dim Server As String
End Class
Code snippet GettingStarted\Settings.vb
It is recommended to use a type for the contract, because a fully qualified type
name is more likely to be unique. If you need to use string contracts, you should
come up with a way of ensuring they are all unique.
You can specify a contract that is different than the type of the export, if required. The best reason
to do this is if the type implements an interface or inherits from an abstract base class. In the
following sample, the SaveOperation class is not aware of the concrete message sender it will use
and instead imports an abstraction: IMessageService. The CommandLineMessageService exports
itself under the contract of the IMessageService interface. In this way, the SaveOperation class
is able to take advantage of message sending without worrying about the details of how these
messages are being sent. If you wanted to change the way the application worked later, you could
implement a new IMessageService and then change which concrete type exported the contract.
Getting started with Mef .
1039
c#
public interface IMessageService
{
void SendMessage(string message);
}
[Export(typeof(IMessageService))]
public class CommandLineMessageService : IMessageService
{
public void SendMessage(string message)
{
Console.WriteLine(message);
}
}
public class SaveOperation
{
[Import]
public IMessageService MessageService { get; set; }
public void DoSave()
{
MessageService.SendMessage("Saving...");
// Perform the save operation
MessageService.SendMessage("Saved");
}
}
Code snippet GettingStarted\SaveOperation.cs
Vb
Public Interface IMessageService
Sub SendMessage(ByVal message As String)
End Interface
<Export(GetType(IMessageService))>
Public Class CommandLineMessageService
Implements IMessageService
Public Sub SendMessage(ByVal message As String) _
Implements IMessageService.SendMessage
Console.WriteLine(message)
End Sub
End Class
Public Class SaveOperation
<Import()>
Public Property MessageService As IMessageService
Public Sub DoSave()
1040 .
chaPter 53 mAnAged exTenSibiliTy FrAmeWork (meF)
MessageService.SendMessage("Saving...")
' Perform the save operation
MessageService.SendMessage("Saved")
End Sub
End Class
Code snippet GettingStarted\SaveOperation.vb
Exporting abstractions and strings raises a potential issue. If there are many
exports with the same contract, MEF will not know which one to use to satisfy
any given import. If this is the case, you can import an enumerable collection for
a contract instead of a single instance using the ImportMany attribute. It is also
possible to attach more metadata to an export, which you can use to refine the
imports. See http://mef.codeplex.com for more information on this technique.
catalogs
In the sample code so far, the only way that the CompositionContainer is made aware of parts is
by passing instances into the ComposeParts method. This means that your application will need to
know about each part added to the container, which will not work for extensions that need to be
deployed after release. It also gets a little tedious after a while.
Locating parts is the job of a catalog, which can be provided to the CompositionContainer
constructor. If a composition container is constructed with a catalog, it will consult the catalog
whenever it needs to locate an export. MEF ships with four catalogs:
.
A TypeCatalog is created with a list of part types. The parts will be instantiated as
required by the composition container to fulfill the import requirements during part
composition.
.
An AssemblyCatalog is similar to the TypeCatalog except that it scans an entire assembly
looking for part types.
.
A DirectoryCatalog scans a folder structure looking for assemblies, which can be examined
for part types.
.
An AggregateCatalog collects the parts from a number of other catalogs. This is useful
because the composition container constructor is only able to accept a single catalog.
The following code sample demonstrates creating a composition container that will look for parts in
the currently executing assembly and in all of the assemblies in the /Extensions folder:
c#
var assemblyCatalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
var directoryCatalog = new DirectoryCatalog(@".\Extensions\");
The Visual studio 2010 editor .
1041
var aggregateCatalog = new AggregateCatalog(assemblyCatalog, directoryCatalog);
var compositionContainer = new CompositionContainer(aggregateCatalog);
Vb
Dim assemblyCatalog As New AssemblyCatalog(Assembly.GetExecutingAssembly())
Dim directoryCatalog As New DirectoryCatalog(".\Extensions\")
Dim aggregateCatalog As New AggregateCatalog(assemblyCatalog, directoryCatalog)
Dim compositionContainer As New CompositionContainer(AggregateCatalog)
You can create your own catalog by creating a new class that inherits from
ComposablePartCatalog and overriding the Parts property.
advanced Mef
MEF supports a number of advanced scenarios that can be useful to you when you are creating
host applications, or when you are creating add-ons or extensions for another host application.
These include:
.
Exporting properties, fields, and methods
.
Importing fields, methods, and constructor arguments
.
Importing collections
.
Composition batches and recomposition
.
Lazy imports
.Catalog filtering
.
Part lifetimes
.
Importing and exporting custom metadata
See the MEF Programming Guide on http://mef.codeplex.com for more information about
these topics.
the Visual studio 2010 editor
One of the most significant changes in Visual Studio 2010 is the new code and text editor control,
which is written in managed code. This new editor uses MEF to manage its structure, which means
that it imports many predefined contracts. In addition to this, it exports a number of services under
1042 .
chaPter 53 mAnAged exTenSibiliTy FrAmeWork (meF)
predefined contracts that provide access to the presentation layer and the underlying model of the
editor. The new editor is made up of four main subsystems.
the text Model subsystem
The Text Model subsystem is used to represent text and enable its modification. It is a logical model
only, which doesn’t have any responsibility for displaying pixels on the screen.
The chief component of this subsystem is the ITextBuffer, which represents a sequence of characters
that should be displayed by the editor. The ITextBuffer can be persisted to the file system as an
ITextDocument, but it doesn’t need to be. It can be an entirely in-memory representation. To create
new ITextBuffer instances, you can use an ITextBufferFactoryService. Any number of threads
can make changes to an ITextBuffer until one of them calls the TakeThreadOwnership
method.
Whenever an ITextBuffer is changed, a new version is created. Each version is represented as an
immutable ITextSnapshot. Because these snapshots cannot change, any number of threads can
refer to them safely, even if the ITextBuffer that they refer to is still changing.
To make a change to an ITextBuffer, you can use the CreateEdit method to create an instance of
the ITextEdit interface. ITextEdit allows you to replace a span of text in the buffer with a new
set of characters. The ITextEdit instance can be applied to the ITextBuffer by calling its Apply
method. It can be abandoned by calling either the Cancel or Dispose method. Only one ITextEdit
can be instantiated for an ITextBuffer at any given time, and if the buffer is owned by a particular
thread, only that thread can create the edits.
The ITextBuffer interface contains Insert, Replace, and Deleteconvenience
methods, which just wrap up the creation and application of an ITextEditinstance.
All operations within a single ITextEdit occur relative to the initial state of the ITextBuffer at
the time when the edit was created. Because of this you cannot insert some text and then remove it
again within a single edit.
When an ITextEdit is applied, new instances of ITextVersion and ITextSnapshot are created
and a Changed event is raised. The ITextVersion represents the changes between the current state
of the ITextBuffer and the previous state, whereas the ITextSnapshot is a read-only view of the
ITextBuffer after the edit has been applied. The changes in an ITextVersion are represented
as a list of ITextChange instances which, if they are applied to a snapshot, would produce the
subsequent snapshot. This collection is always null
(Nothing) for the most recent version.
the text View subsystem
The Text View subsystem is responsible for managing the display of text on the screen. This
includes which lines should be displayed and how text should be formatted. It is also responsible
for enhancing the text with visual adornments such as the squiggly line, which notifies you of
The Visual studio 2010 editor .
1043
compilation errors. Finally, this subsystem manages the borders around the edges of the editor,
which can be enhanced with additional information.
The main part of this subsystem is the ITextView interface. Instances of this interface are used to
represent text visually on the screen. This is used for the main editor window but also for things like
tooltip text. The ITextView keeps track of three different text buffers through its TextViewModel
property. These are:
.
The data buffer, which is the actual text
.
The edit buffer in which text edits occur
.
The visual buffer, which is actually displayed
Text is formatted based on classifiers (see “The Classification Subsystem”) and decorated with
adornments, which come from adornment providers attached to the text view.
The part of the text that is displayed on the screen is the view port. The view port relies on a
logical coordinate system that has (0,0) as the top left of the text. If the editor is not zoomed or
transformed in any way, each unit of distance in the view is the equivalent of a single pixel. Each
line of text that is displayed on the screen is an instance of the ITextViewLine interface. This
interface can be used to map from pixel points to characters.
Finally, the entire editor and all adornments and margins are contained within an
IWpfTextViewHost.
The Text View subsystem comes in two parts. One part is technology agnostic
and is found in the Microsoft.VisualStudio.Text.UI assembly. The other
part is the WPF implementation and is found in the Microsoft.VisualStudio.Text.UI.WPF assembly. In most cases, the WPF-specific items contain the text
“Wpf” in the name.
the classification subsystem
The Classification subsystem manages the recognition and formatting of different types of text. It
is also responsible for tagging text with additional metadata, which will be used by the Text View
subsystem for attaching glyphs and adornments as well as text highlighting and text outlining (such
as collapsed regions of code).
the operations subsystem
The Operations subsystem defines editor behavior and commands. It also provides the Undo
capability.
1044 .
chaPter 53 mAnAged exTenSibiliTy FrAmeWork (meF)
extendinG the editor
Editor extensions are .vsix packages, which export contracts that Visual Studio components will
import. When Visual Studio loads these packages, it adds their contents to a MEF catalog, which
is then used to compose parts of the editor control. The Visual Studio Integration SDK comes
with a number of templates to get you started creating editor controls. These appear under the
Extensibility page of the New Project dialog shown in Figure 53-1.
fiGure 53-1
The Visual Studio 2010 SDK is not installed with Visual Studio 2010. You can
download a copy from http://msdn.microsoft.com/en-us/vsx/default.aspx.
If you want to start with a clean slate, you need to use the VSIX Project template. To expose editor
extensions via this package, edit the source.extension.vsixmanifest file, and use the Add
Content button to add the current project as an MEF Component as in Figure 53-2.
extending the editor .
1045
fiGure 53-2
Once your project is set up to contain MEF content, all you need to do is to create classes that
export known extension contracts and Visual Studio will pick them up. In addition to this, you
can import service contracts from Visual Studio that will provide you with access to the full
capabilities of the editor.
During development, editor extensions can be run and debugged in the Experimental Instance of
Visual Studio. The Experimental Instance behaves like a separate installation of Visual Studio with
its own settings and registry. It also manages a separate set of extensions. When you are ready to
deploy your extension to the normal instance of Visual Studio, you can double-click the .vsix
package, which is created as a part of the build process. This package is entirely self-contained, so
you can use it to deploy your extension to other machines as well.
editor extension Points