Components are only included into the Component Model if the zones they belong to are "active". Zones are not activated by default, and must be activated by an activator class, which is a component decorated by the
[ZoneActivator] attribute, and implementing
IActivate<TZone> for each zone it activates.
If a zone is not activated, any components that require that zone will not be available in the application. It's worth noting that if none of the components in an assembly are available, then the assembly itself isn't even loaded into memory, as the catalogues that the Component Model use are based on caching and direct IL metadata reading, rather than Reflection.
An activator class implements the
IActivate<TZone> interface, which is defined as:
The class can implement the interface multiple times, once for each zone it activates. During application startup, the ReSharper Platform will query the activator for each implementation of
IActivate<TZone> and call the
ActivatorEnabled method. If the class returns
true, the zone is marked as active.
When activating a zone, the ReSharper Platform activates that zone, and all zones that inherit from it. For example, if
IActivate<IPsiLanguageZone>.ActivatorEnabled() returns true, the
IPsiLanguageZone is now activated, as are all zone definitions that inherit from
IPsiLanguageZone, such as
Typically, a zone activator aligns with a Product or Feature, and unconditionally enables the zone definition that declares the Product or Feature via the
[ZoneDefinitionProduct] attribute. It should also activate the zones that the Product or Feature zone requires. It is not an error to activate the same zone from multiple activators. E.g. if both ReSharper and dotPeek need the
ExternalSourcesZone, they should both activate it - if they don't, and they end up being the only product installed into the ReSharper Platform, the required zone won't be active and the Product or Feature's requiring zones won't be loaded.
There is usually little or no logic in the implementation of
ActivatorEnabled. Generally speaking, if a product is installed, it should be activated. Activation is separate to licensing, trial versions and user disabling of a product or feature - this is handled separately by the ReSharper Platform. Unlicensed, expired, or disabled zones are removed from the list of activated zones before components are filtered into the container.
However, it is perfectly reasonable to implement some logic in this method, for example, the
InternalModeProductZoneActivator class will activate the
IInternalVisibilityZone (for Internal Mode) only if the
/Internal command line argument is supplied.
The activator can implement the
IActivate<TZone> interfaces explicitly, to allow for different implementations for each zone:
Activator class as component
Similarly, zone activator classes are components (
ZoneActivatorAttribute derives from
EnvironmentComponentAttribute, so they are components that get created very early in the application lifecycle), which means they can accept dependencies in the constructor. For example, a Feature that is intended for use only in Internal Mode can do something like:
This is perhaps a contrived example - an internal only Feature would be better requiring
IInternalVisibilityZone in its zone, rather than handling it in this manner. However, it is an example that serves as a reminder that a zone activator is a component, and follows normal Component Model rules.
Component Model considerations
Since the activator class is a standard component, it naturally follows normal Component Model creation rules. This means that an activator class must itself belong to a zone that is already active!
In other words, the zone activator is only available if it has a zone marker. The zone marker can be an empty zone marker, in which case the activator is always active, and always created. Or it can require one or more of the default active zones (see below), in which case the activator component will only be active if all of the required zones are active.
For example, the
ReSharperZonesActivator class, which lives in the
JetBrains.ReSharper.Product.Application.Product namespace, has the following zone marker:
Note the namespace of the marker is above the namespace of the activator. This means that the activator class belongs to the zone definitions declared in the marker's dependencies, namely
IVisualStudioZone. So, the ReSharper product zone activator is only active when the
IVisualStudioZone zone is itself active. This zone is an environmental zone, and is created and activated before the environment container is composed.
A zone activator can take a dependency on a zone, using
IRequire<TZone> in the same way zone markers do. This allows for a zone activator to be disabled if any of these required zones are disabled, for whatever reason - unlicensed, expired or explicitly disabled by the user.
It is not immediately obvious why a zone activator would wish to be disabled.
This happens naturally if an activator's zone marker's dependencies aren't satisfied. However, zone markers for zone activators are limited in that they can only require the bootstrap zones that are used when creating the environment component container (see the section on default active zones). It also happens if any of the zones being activated are no longer licensed.
The main reason for wanting to disable a zone activator is so that supporting zones are not activated unnecessarily. A common pattern is for a zone activator to require a dependency on the main zone it is trying to activate. If that zone has been disabled, for whatever reason, the activator is also disabled, and supporting zones are not activated.
For example, the ReSharper C++ activator requires the C++ product zone, but also activates code editing, daemon and navigation zones. If the C++ product zone feature is disabled, the C++ activator would still activate code editing, daemon and navigation. If no other Features or Products used those zones, they are unnecessarily activated (and probably unlicensed). By requiring the C++ product zone, the ReSharper Platform will disabled the C++ activator if the C++ product zone is disabled. And now, the code editing, daemon and navigation zones are only activated if another activator needs them.
Default active zones
The ReSharper Platform provides several default zones that are automatically activated based on the current host's environment. These zones are used to bootstrap the zone activators. An activator's zone marker can require one or more of these zones to allow for conditionally including and excluding zone activators based on the host environment.
For example, if a zone needs to be run inside a particular version of Visual Studio, the zone activator's zone marker can require the Visual Studio zone. If the current host is an appropriate version of Visual Studio, the Visual Studio zone is activated, the zone marker's dependencies are satisfied, and the zone activator is created and finally activates the target zone.
The default zones derive from
IHostSpecificZone and are:
An environment zone, representing the current running host environment. These zones derive from
IConsoleZonewhen the host is a command line utility. Derived interfaces represent the different supported console applications, such as InspectCode and DuplicatesFinder.
IStandaloneUIZonefor when the host is a standalone GUI application, such as dotPeek. Again, derived interfaces represent standalone applications.
ITeamCityZonefor when the host is running inside TeamCity.
IVisualStudioZonefor when the host is Visual Studio.
CLR version zone -
ISinceClr4Zone. Components that are built for .net 4 should require
ISinceClr4Zone. Note that
ISinceClr4Zonehas a dependency on
ISinceClr2Zone, so all CLR 2 components are also available for .net 4.
CPU architecture zone, representing 32 and 64 bit -
ITestsZoneto indicate that the code is running under test.
The Visual Studio zones make extensive use of zone inheritance to model different versions. Each version has two interfaces, a "since" zone and a "just" zone, e.g.
The "since" zones inherit from the previous version's "since" zone, so
ISinceVs12Zone derives from
ISinceVs11Zone, which derives from
ISinceVs10Zone, etc. When running in VS12 (Visual Studio 2013), the
ISinceVs12Zone is active, and so is
ISinceVs10Zone. If a component uses a Visual Studio feature that works across versions, it should require the zone at which the feature was introduced. For example, if the feature was introduced in Visual Studio 2012 (VS11), the component should require
ISinceVs11Zone. When running in Visual Studio 2012 (VS11) or 2013 (VS12), the
ISinceVs11Zone is active, and the component runs. If running in Visual Studio 2010 (VS10), the
ISinceVs11Zone isn't active, and the component isn't loaded.
The "just" zones inherit from the current version's "since" zone, so
IJustVs12Zone derives from
ISinceVs12Zone. If code is written that targets just a single version of Visual Studio, it should require the "just" zone. For example, if code targets just Visual Studio 2010 (VS10), it should require
IJustVs10Zone. When run in Visual Studio 2012 (VS11), the
IJustVs10Zone isn't active, and the component isn't loaded.
Inheritance and activation
When declaring a zone definition using the
[ZoneDefinition] attribute, dependencies can be declared either by implementing the
IRequire<TZone> interface, or by inheriting directly from an existing zone definition. There is no difference between the two with regard to dependencies - when applying a zone to a class or namespace, that zone and all of its dependencies must be active before the component is allowed.
The difference comes with activation. When a zone inherits from another zone definition, it is automatically activated when the base zone is activated. For example, the
ReSharperZonesActivator class implements
IActivate<IPsiLanguageZone>.ActivatorEnabled() and returns true. This activates the
IPsiLanguageZone and all zone definitions that inherit from it, including
IClrPsiLanguageZone and therefore
ILanguageCSharpZone. Any new code that is added that implements a zone inheriting from
IClrPsiLanguageZone will also get automatically enabled.
Dependencies registered via
IRequire<TZone> are not automatically enabled. These zones must be enabled via another activator, or via activation of their base zone definitions.
The Visual Studio zones are very hierarchical (see above). The "just" zones are the zones that are activated. Because the "just" zone derives from a chain of "since" zones, when a "just" zone is activated, the derived "since" zones are also enabled. So when
IJustVs12Zone is activated, it's inherited
ISinceVs12Zone is also activated, as are
A zone definition can be declared as auto-enabled, if it makes sense that the zone is always available, regardless of configuration, installed products, etc. This is handled with the
ZoneFlags parameter to the
This has the same effect as a simple implementation of
IActivate<TZone>.ActivatorEnabled() that always returns true. However, it doesn't allow for finer control, such as activating supporting zones - it will only activate
IMyZone requires other zones to be active, an activator class should be used.