Overview
CIS includes an assembly consisting of core functionality, called Medsphere.OpenVista.Core. In this assembly, our core "service" infrastructure, consists of ServiceManager, IService, and the attributes ServiceReferenceAttribute and LazyServiceAttribute. A service is any persistent object/model you want accessed by any other part of the software. It loosely fills the role of "Singleton" but providing a mechanism for abstracting things out behind interfaces, to allow for platform/application dependant implementation of certain services, and to aid in unit testing. Services are accessed via ServiceManager, and can be registered into ServiceManager one of two ways:
Manually using ServiceManager.RegisterService (IService service)
Dynamically via Mono.Addins by decorating the IService subclass with [Mono.Addins.Extension ("/Core/Services")]
IService
IService is the base interface for all services. It is extremely simple, as of this writing:
public interface IService
{
/// Initialize any resources needed by the service.
/// @return Used to indicate success/failure in intializing any needed resources.
bool Initialize ();
}
Creating an IService Subclass
At a minimum, you need to implement the IService interface in a subclass, and manually register the service with ServiceManager.RegisterService (IService service). After the service is registered, it can be accessed via IService ServiceManager.GetService (Type type). Here's a rough psuedo-C# example:
public class MyService : IService
{
public string Name { get { return "MyService"; } }
public bool Initialize () { return true; }
}
// Create the service instance.
IService my_service = new MyService ();
// Services registered dynamically are automatically initialized,
// but since we're doing it manually, we'll do it by hand here.
my_service.Initialize ();
// Register the service.
ServiceManager.RegisterService (my_service);
// Fetch the service back, and verify it's the right service.
IService my_service_2 = ServiceManager.GetService (typeof (MyService));
Assert.AreEqual (my_service, my_service_2);
Dynamically Registered Service
If you would like CIS to automatically register your service into ServiceManager at start-up, via Mono.Addins, you do the following:
[Mono.Addins.Extension ("/Core/Services")]
public class MyService : IService
{
public string Name { get { return "MyService"; } }
public bool Initialize () { return true; }
}
Lazy Loaded Services
NOTE: The following section only applies to services dynamically registered via Mono.Addins.
You can also make dynamically registered services be "lazy loaded", that is, they will not be created nor initialized until someone attempts to fetch references to them. This improves application start time by avoiding having to load all services when the application is first run. To make you service lazy-loaded, simply decorate it with the LazyServiceAttribute. Here's an example:
[LazyService]
[Mono.Addins.Extension ("/Core/Services")]
public class MyService : IService
{
public string Name { get { return "MyService"; } }
public bool Initialize () { return true; }
}
When the service is needed, because of a ServiceDependencyAttribute, a ServiceReferenceAttribute or when requted via ServiceManager.GetService, the service, and any services it depends on, will be loaded and initialized.
Conventions
There are a few conventions and best practices that can aid you in writing services.
Most services should use an intermediary interface that defines the public API of the service. E.g. MyService -> IMyService -> IService. This will aid in unit testing code that consumes those services.
Service sub-interfaces and subclasses should have names ending in 'Service', e.g. IFooService and FooService.
Service Dependencies
If your service depends on other services being loaded first (either to consume/use them, or due to potential load order issues), you can use ServiceDependencyAttribute on your service class. Here's an example:
[ServiceDependency (typeof (IMyOtherService))]
[Mono.Addins.Extension ("/Core/Services")]
public class MyService : IService
{
public string Name { get { return "MyService"; } }
public bool Initialize () { return true; }
}
This will ensure that IMyOtherService is loaded and initialized before MyService.
Fetching Services, Two Approaches
There are two approaches to fetching references to services from ServiceManager. One, as previously mentioned is to use IService ServiceManager.GetService (Type type). The other way is to use the ServiceReferenceAttribute applied to fields on your class.
IService ServiceManager.GetService (Type type)
The direct way to fetch a service is with IService ServiceManager.GetService (Type type). Here is an example:
IMyService my_svc = ServiceManager.GetService (typeof (IMyService)) as IMyService;
ServiceManager will return null if the service is not found, so often you will want to add a null check to ensure you got the service you wanted from ServiceManager.
ServiceReferenceAttribute and ServiceManager.BindFields (object obj, Type type)
The other way to get references to services registered into ServiceManager is to decorate your fields with the ServiceReferenceAttribute, e.g.:
public class MyServiceConsumer
{
[ServiceReference]
private IMyService my_svc;
public MyServiceConsumer ()
{
ServiceManager.BindFields (this, typeof (MyServiceConsumer));
}
}
You'll notice that in order to have the fields actually set to the references of the service, the class must call ServiceManager.BindFields (object obj, Type type). This call makes ServiceManager iterate over all the fields in the class, and assign values to the ones decorated with the ServiceReferenceAttribute.
NOTE: For IService subclasses that are dynamically registered via Mono.Addins, you do not need to use ServiceManager.BindFields. The fields will automatically be bound by ServiceManager after your service is created, and before the IService.Initialize () method is called on your service. This convenience allows you to write services that consume other services very easily.
Conclusion
The ServiceManager functionality provided in Medsphere.OpenVista.Core provides a powerful way to create persistent, shared models between parts of CIS, CIS plugins, etc. When writing CIS plugins, or enhancing the base CIS code, it is the prefered way to achieve a singleton pattern, with all the benefits that come from interface abstraction, etc. Some example services you can see in CIS are:
Medsphere.OpenVista.Shared.UI.FontService
Medsphere.OpenVista.Shared.UI.UserIdleService
Medsphere.OpenVista.Shared.UI.MessageService