A "scope" is a collection of MEF parts that are tied to the lifecycle of a specific object, or "context". When that object or context is torn down, so are the MEF parts that live in that scope. Scopes themselves can be nested so that parts that live in child scopes can "inherit" or import exports from parent scopes, but not vice versa.
In Visual Studio, there is one global default "scope" of context and three related to project systems:
- VS Default Container. This is the default global scope, where parts in Visual Studio live by default. There is exactly one of these in the process.
IVsSolution, or the solution context. There is exactly one of these in the process.IVsProject, or the project context. There is exactly one of these per project in the solution.IVsProjectCfg, or the project configuration context. There is exactly one of these for each build configuration, for each project, in the solution.
These contexts can be represented in a hierarchy:
- Visual Studio process
- VS Default Container
IVsSolutionIVsProject(a.csproj)IVsProjectCfg(Debug|AnyCPU)IVsProjectCfg(Release|AnyCPU)IVsProjectCfg(Debug|x86)IVsProjectCfg(Release|x86)
IVsProject(b.vcxproj)IVsProjectCfg(Debug|Win32)IVsProjectCfg(Release|Win32)
- VS Default Container
Within CPS, the three project system scopes are known by the CPS concept names rather than their VS-specific equivalents. Here they are with their equivalents:
| VS term | CPS term | MSBuild term |
|---|---|---|
| IVsSolution | ProjectService | ProjectCollection |
| IVsProject | UnconfiguredProject | ProjectRootElement (construction model) |
| IVsProjectCfg | ConfiguredProject | Project (evaluation model) |
Any code in VS may obtain the IProjectService or IVsSolution because there
is just one in the process.
Code that wants an UnconfiguredProject (or IVsProject) must either already
be operating at that context to obtain it, or must ask its parent context
for the project-specific context by naming the project to be obtained.
Code that wants a ConfiguredProject (or IVsProjectCfg) must similarly
belong to that context or first retrieve an UnconfiguredProject and then
request the ConfiguredProject by configuration name.
Note that ProjectService scope is not equivalent to the VS default MEF
container. ProjectService is a scope beneath the default container and must
be obtained from the IProjectServiceAccessor, which itself is defined in
the VS default container.
Because CPS projects are MSBuild based which has a project file format
in which nearly everything can be an expression and therefore may vary by
project configuration, most of CPS services for querying or manipulating
project data are implemented in the ConfiguredProject scope. This is
at odds with VS which expects most project data to be exposed at the
IVsProject context. For example, the IVsProject object is expected to
be able to enumerate items in the project, but items in MSBuild are the
result of MSBuild evaluation (since they may include MSBuild expressions or
conditions) so items can only be obtained from the IVsProjectCfg context.
To rectify this disparity, CPS can 'lift' services that are defined in the
ConfiguredProject scope into the UnconfiguredProject scope. As there are
many ConfiguredProject scopes that are children of an UnconfiguredProject
scope, CPS picks the ConfiguredProject associated with the active project
configuration. The active project configuration is a VS concept that dictates
that for a given project, at most one configuration can be 'active'. CPS
synchronizes with the VS concept of active project configuration so that
CPS always uses the active ConfiguredProject as its source for data when
it is queried for it at the UnconfiguredProject scope.
The short answer is that if your code operates at the project level (that
is, you want one instance of your service per project) you should export
it to the UnconfiguredProject scope. Otherwise if you want your service
to have an instance for each individual configuration you should export
it to the ConfiguredProject scope.
MEF parts belong to the scope necessary to satisfy all of its imports. A MEF
part that imports nothing belongs to the 'default' or global scope. A MEF part
that imports anything from the ConfiguredProject scope belongs to the
ConfiguredProject scope as well (unless it does so via the
ActiveConfiguredProject<T> wrapper).
| A part belongs to, the scope below, when it imports MEF parts in the columns to the right | VS default container | ProjectService | UnconfiguredProject | ConfiguredProject |
|---|---|---|---|---|
| VS default container | Y/N | No | No | No |
| CPS ProjectService | Y/N | Yes | No | No |
| CPS UnconfiguredProject | Y/N | Y/N | Yes | No |
| CPS ConfiguredProject | Y/N | Y/N | Y/N | Yes |
This ability to 'lift' data upward from ConfiguredProject to UnconfiguredProject
scope is exposed to you in at least two ways. If you simply want to access
ConfiguredProject data that CPS exposes while you're at the UnconfiguredProject
context the easiest way is often to query for the service stub that CPS often
exposes at the UnconfiguredProject scope. For example, if you want to get
the IProjectSubscriptionService, which is exported to the ConfiguredProject
scope, you can instead get the IActiveConfiguredProjectSubscriptionService,
which is available at the UnconfiguredProject scope.
When you are defining a MEF part that is exported to the UnconfiguredProject
scope, you may import ConfiguredProject-scoped services using the
ActiveConfiguredProject<T> wrapper. This is a MEF open-generic export
that you can import, providing your own T, where T is an export from the
ConfiguredProject scope. Note this technique only works when T is exported
with the default MEF contract name for T. So for example, if you want to
import the IBuildProject service from the active configuration from your
UnconfiguredProject MEF part, you can use this syntax:
[Import]
ActiveConfiguredProject<IBuildProject> BuildProject { get; set; }Note that the generic type argument can even be ConfiguredProject itself:
[Import]
ActiveConfiguredProject<ConfiguredProject> ActiveConfiguredProject { get; set; }Most commonly, it's useful to define your own private nested class that
imports everything you need from the ConfiguredProject scope, and then
import that:
[Export]
class MyUnconfiguredProjectPart
{
[Import]
UnconfiguredProject Project { get; set; }
[Import]
ActiveConfiguredProject<ConfiguredProjectHelper> ActiveConfigurationExports { get; set; }
[Export]
class ConfiguredProjectHelper
{
[Import]
internal ConfiguredProject ConfiguredProject { get; set; }
[Import]
internal IBuildProject BuildProject { get; set; }
}
}Q: Is there a way to know which services belong to which scope (e.g., naming convention)?
A: No. Just documentation, but we hope we have a better answer in vNext.