-
Notifications
You must be signed in to change notification settings - Fork 1k
The Service Model
MonoDevelop 8.1 introduces a new model for implementing and consuming services. In previous versions MonoDevelop had a mix of approaches for implementing services. Some were implemented as instance classes, some as static classes, and there was no standard way of initializing them.
The new service model has three main goals:
- Homogenize how services are implemented, instantiated, initialized and accessed.
- Support asynchronous and on-demand initialization of services, to reduce and parallelize the of work that needs to be done at IDE startup.
- Improve support for unit testing by allowing discrete service initialization and using mock test services.
The model is very simple:
- The new ServiceProvider class provides methods for registering and getting references to services.
- Services implement the interface
IService
, either directly or through the abstract classService
. The interface defines the async methodTask Initialize(ServiceProvider)
which is invoked the first time a service is requested.
In this new model all services are instances (no more static classes, with the exception of some special cases), and it is possible to get any service using the main service provider available in Runtime.ServiceProvider
. The Runtime
class also provides some shortcut methods for getting services. For example:
var fontService = await Runtime.GetService<FontService> ();
...
The GetService()
method can be used to get a service. Services are created on demand, so if the service has not been requested before, it will be created and initialized at this time.
There are several ways of getting a reference to a service:
### The ServiceProvider class
The ServiceProvider
class provides several methods for getting a service:
-
Task<T> GetService<T> ()
: this async method can be used to get a service. Services are created on demand, so if the service has not been requested before, it will be created and initialized at this time. -
T PeekService<T> ()
: it returns the service of the specified type, but only if the service has been initialized. It returnsnull
otherwise. -
IDisposable WhenServiceInitialized<T> (Action<T> action)
: Executes the given action when (and if) the service is initialized. This method is useful when the dependency on a service is optional, and no action has to be taken until that service is initialized.
Getting a service is an asynchronous operation (since it may involve initialization). This may not always be convenient, since services also need to be used from methods that are not asynchronous. To make it easier to use services, MonoDevelop.Ide defines the class IdeServices
, which has references to all services that are initialized at startup. Referencing services from IdeServices
is safe once the Ide has completed the initialization (that is when the IdeApp.Initialized event is raised and IdeApp.IsInitialized is set to true).
In some scenarios (such as in unit tests or when implementing a command line command), IdeServices still can be used, but will only have references to those services that have been initialized.
A benefit of the new service model is that unit tests don't need to initialize the whole IDE to test some functionality. Services can be initialized and obtained independently, and in general they will take care of ensuring that the services they depend on are also initialized.
However, even though a service knows which other services it needs to do its work and can initialize those when needed, it doesn't know what extensions may need. For example, the DocumentManager service doesn't need the Type System service to work, but some document controller implementations may depend on it, and assume that it will be available in IdeServices.
When writing unit tests those cases are easy to detect, because the test will fail with a "Service not initialized" exception, so the solution is to explicitly initialize the service at startup. If the unit test is subclassing the TestBase
class defined by the UnitTests
assembly, service initialization can be easily requested using the [RequireService]
attribute, for example:
[RequireService (typeof(TypeSystemService))]
public class SomeTests : TestBase
{
...
}
Bulding and Running
Writing Add-ins
MonoDevelop API
MonoDevelop Design and Architecure
- The Project Model
- Error and Exception Handling
- The Command System
- The Service Model
- The Document/View Model
MonoDevelop Coding Guides