Creating and Using Services in the CAB (Introduction to the CAB/SCSF Part 8)

Introduction

Part 7 of this series of articles gave us a general introduction to services in the CAB. This article will go into more detail on the various ways we can create and use such services.

Ways of Creating a Service

We start with the various ways services can be created. This can be done with the various ‘Add’ methods, with XML configuration files or by using the ‘Service’ attribute.

Ways of Creating a Service (1) – Add Methods

In the basic example in part 7 we used the AddNew method to create a service:

            RootWorkItem.Services.AddNew<MyService>();

We have seen this before: it both instantiates the object and adds it to the collection. As before, we can also add objects that already exist to the Services collection with the Add method.

The Services collection also has an ‘AddOnDemand’ method. If we use this in place of AddNew in the example in part 7 the service does not immediately get created (the MyService class is not instantiated). Instead a placeholder is added to the Services collection until such time as some client code retrieves the service (using the same syntax as before). When this happens the service object will get instantiated so that it can be used. This example shows this:

            // Use AddOnDemand to set up the service: the MyService constructor
            // is not called
            RootWorkItem.Services.AddOnDemand<MyService>();
            // When we dislay the Services collection we can see there's a placeholder
            // for MyService in there
            DisplayWorkItemCollections(RootWorkItem);
            // Only when we use .Get to retrieve the service is MyService actually
            // instantiated (note we have code in MyService to show when the constructor
            // is called by writing to the Output window)
            UseMyService();
            // Now our Services collection has a fully fledged MyService service available
            DisplayWorkItemCollections(RootWorkItem);

There are also Contains and Remove methods on the Services collection. Remember we can only have one service of a given type: if a service already exists and we want to replace it these methods can be useful.

Ways of Creating a Service (2) – XML Configuration File

It is also possible to create services using the app.config file. To do this in our simple example we just take out the line:

            RootWorkItem.Services.AddNew<MyService>();

Then in an App.Config file we add a ‘services’ section with an ‘add’ element to a CompositeUI config section as below:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <configSections>
      <section name="CompositeUI" type="Microsoft.Practices.CompositeUI.Configuration.SettingsSection, Microsoft.Practices.CompositeUI"  allowExeDefinition="MachineToLocalUser" />
    </configSections>
  <CompositeUI>
    <services>
      <add serviceType ="Shell.MyService, Shell" instanceType ="Shell.MyService, Shell" />    
    </services>
  </CompositeUI>
</configuration>

The code for this is available.

In general I’m not a fan of writing code in XML if there are proper C# alternatives. Here the XML is certainly less transparent than the one-line C# equivalent, and as usual debugging becomes more difficult with XML. However, one potential advantage of using the configuration file is that we could in theory change our service at runtime without having to recompile the code.

Ways of Creating a Service (3) – the Service Attribute

As mentioned previously, we can create a service simply by decorating our concrete class with the ‘Service’ attribute. We can register the service with a separate interface (as in the section ‘Splitting the Interface from the Implementation’ in part 7) by providing a positional type parameter. We can also make our service one that gets added on demand to the Services collection by adding a named boolean parameter called ‘AddOnDemand’. These attributes are illustrated below:

    [Service(typeof(IMyService), AddOnDemand=true)]
    public class MyService : IMyService
    {
        public string GetHello()
        {
            return "Hello World";
        }
    }

If we declare our service class in this way we have no need to explicitly add it to the Services collection before using it. There’s also no need to explicitly instantiate the class. Just adding the attribute ensures that when the code runs the service will get set up. The code showing this working is available.

Why the Service Attribute is Unusual

The ‘Service’ attribute is in some ways quite different from other attributes we’ve seen used with the CAB. Most CAB attributes only work for objects that are already in a collection associated with a WorkItem. For examples see the discussion about ComponentDependency, ServiceDependency and CreateNew in part 5 of this series of articles. In particular CreateNew will only work on a setter if that setter is in an object that is already in a WorkItem collection. We can’t just put CreateNew in any old class and expect it to work.

In contrast the Service attribute will work with ‘any old class’, provided it’s in a module (see part 1 for a discussion of modules). The Service attribute really couldn’t work any other way. The attribute when applied to a class is telling the CAB to add an object of that type to the Services collection of the WorkItem. It wouldn’t make much sense if it only worked if the object was already in a collection of the WorkItem.

Where the CAB is Looking for the Service Attribute

So how does the CAB find these Service objects and use them? The answer is that when a module loads the CAB uses reflection to find all public classes in the assembly which have the ‘Service’ attribute applied. All of these classes get instantiated and added in to the Services collection of the root WorkItem of the CAB application.

Note that the CAB only scans assemblies that are explicitly listed as modules (in ProfileCatalog.xml usually). An assembly won’t get scanned if it’s just referenced from a module project.

Drawbacks of the Service Attribute

One problem with this is that we don’t have a lot of control over where the service gets created. Our new service always gets added to the root WorkItem, meaning we can’t create services at a lower level in the WorkItem hierarchy. Another problem is that we have no control over when our service is created: in particular we have no way of ensuring that our services are created in a specific order.

My personal opinion is that setting up services using the Service attribute can be a little confusing. The services appear magically as if from nowhere. If we explicitly create the service and add it to the appropriate WorkItem we have more control and what we are doing is more transparent.

Ways of Retrieving a Service

There are two main ways of retrieving a service. We have already seen examples of these, but a recap is given below.

Ways of Retrieving a Service (1) – Get Method

In the basic example in part 7 we used the Get method of the Services collection to retrieve MyService. For example, the code below is taken from the final example (‘Splitting the Interface from the Implementation’):

        private void UseMyService()
        {
            IMyService service = RootWorkItem.Services.Get<IMyService>();
            System.Diagnostics.Debug.WriteLine(service.GetHello());
        }

Ways of Retrieving a Service (2) – Dependency Injection

We can also retrieve a service via dependency injection by using the ServiceDependency attribute. We saw some examples of this in part 5.

To set up a service in a class we can decorate a setter of the appropriate type in a class with the ServiceDependency attribute. The class can then use the service:

    public class ServiceClient
    {
        private IMyService service;
 
        [ServiceDependency]
        public IMyService Service
        {
            set
            {
                service = value;
            }
        }
 
        internal string UseMyService()
        {
            return service.GetHello();
        }
    }

As discussed previously, the CAB looks for the ServiceDependency attribute when an object of type ServiceClient is added to one of the WorkItem collections. When that happens the CAB looks for a service of type IMyService in the Services collection of the WorkItem. When it finds one it retrieves it and sets it on the ServiceClient object by calling the setter.

So to set up this class we need to ensure that an IMyService service has been created, and then we can just create a ServiceClient object in our WorkItem:

            // Create the service
            RootWorkItem.Services.AddNew<MyService, IMyService>();
 
            // Add a ServiceClient object to our Items collection:
            // this causes the CAB to inject our service into the ServiceClient
            // because it has a setter decorated with ServiceDependency
            ServiceClient serviceClient = RootWorkItem.Items.AddNew<ServiceClient>();

Now we can call the service on the ServiceClient object:

            System.Diagnostics.Debug.WriteLine(serviceClient.UseMyService());

The code for this example is available.

We can also use the ServiceDependency attribute with constructor injection as discussed in part 6. This is a simple change to the ServiceClient class in the example above:

    public class ServiceClient
    {
        private IMyService service;
 
        public ServiceClient([ServiceDependency]IMyService service)
        {
            this.service = service;
        }
 
        internal string UseMyService()
        {
            return service.GetHello();
        }
    }

The code for this example is also available.

Finding Services Higher Up the Hierarchy

As already discussed, if the CAB can’t find a service in the Services collection of the current WorkItem it will look in the Services collections of parent WorkItems. We can illustrate this by adding a new WorkItem called ‘testWorkItem’ to our basic example from part 7. We still add our service to the RootWorkItem:

        WorkItem testWorkItem = null;
        protected override void AfterShellCreated()
        {
            testWorkItem = RootWorkItem.WorkItems.AddNew<WorkItem>();
 
            RootWorkItem.Services.AddNew<MyService, IMyService>();
            UseMyService();
            DisplayWorkItemCollections(RootWorkItem);
        }
 
        private void UseMyService()
        {
            IMyService service = testWorkItem.Services.Get<IMyService>();
            System.Diagnostics.Debug.WriteLine(service.GetHello());
        }

When we come to use the service in UseMyService (immediately above) we try to retrieve it from the testWorkItem. The code still works even though the service isn’t in testWorkItem’s Services collection: the CAB retrieves it from the parent RootWorkItem. Once again the code for this example is available.

Services Not Found

If the CAB attempts to retrieve a service and can’t find it at all it usually does not throw an exception. It simply returns null. Consider the changes to our basic example from part 7 below:

        protected override void AfterShellCreated()
        {
            //RootWorkItem.Services.AddNew<MyService, IMyService>();
            UseMyService();
            DisplayWorkItemCollections(RootWorkItem);
        }
 
        private void UseMyService()
        {
            // There's no IMyService available, so the CAB sets service = null below
            IMyService service = RootWorkItem.Services.Get<IMyService>();
            // We get a NullReferenceException when we try to use the service
            System.Diagnostics.Debug.WriteLine(service.GetHello());
        }

Here we have commented out the line that creates the service so it never gets created. As a result the call to ‘Get’ the service returns null, and we get a NullReferenceException when we try to call GetHello.

This may not be the behaviour we want. It may be better to throw an exception as soon as we know the service does not exist before we attempt to use it. Fortunately the Get method is overloaded to allow us to do this. It can take a boolean argument, EnsureExists, which if set to true throws a ServiceMissingException immediately the service cannot be retrieved:

IMyService service = RootWorkItem.Services.Get<IMyService>(true);

The code for this example is available.

Conclusion

This article has shown us how to use services in the CAB in some details. The next two articles will examine commands in the CAB: part 9 will recap the Command design pattern, and part 10 will explain how this is implemented for menus using commands in the CAB.

13 thoughts on “Creating and Using Services in the CAB (Introduction to the CAB/SCSF Part 8)

  1. Great articals Rich, well done. It would be great if you could also write something about how events are dealt with in CAB.
    Thanks

  2. You did a great job. But I did not find anything about building GUI. Maybe you will write about it in next part. I am trying to build some application and I found very difficult to create child modal form with own UIExtensionSites etc.
    I give you a little brief about it: I have list with records and I want to edit record in modal window. This window has its own tool strip and status strip and tab workspace. I want to able to add views to tabs and those views to able to add own tool strip items to tool strip via the CompositeUI framework – i.e. UIExtensionSites.RegisterSite.
    Can you help me or point me to some example (Google is not helping).
    Thanks

  3. Karel

    I will be writing about this, but not immediately (there’s a lot to talk about on the CAB/SCSF). I’ve deliberately left the UI part until last. Most documentation starts with this and it’s not really at the heart of what we’re doing.

    I’m pretty unconvinced of the value of UIExtensionSites (although I can see why the P&P group thought they were a good idea). You can’t even hide a toolstrip without writing your own adapter.

    In our project currently we’re just adding ToolStripPanels to our shell form, and then adding those to the root WorkItem Items collection (with a sensible name). This means any project can get their hands on them and put a toolstrip or menustrip in the appropriate place. This does make them effectively global singletons, which is probably a scoping crime, but it makes life much easier. We’ve written quite a powerful menuing system around this. We don’t use UIExtensionSites at all.

  4. In the section of “Why the Service Attribute is Unusual”, I am not quite sure what “old class” means. I appreciate if you or some one could give further explanation on this.

  5. Thank you for these articles that spped up my learning process.
    [Q]How does client module know the service type at compile time?

  6. I have been struggling with using custom services in the CAB framework so it was with some relief that I have found your article.

    While your example works, I am trying to load services that have been written in a class library project.
    No matter what I do I get configuration errors.

    Do you have a more complex example of loading services from a dll with a different name space other than the shell itself?

  7. It quite similar to Spring.Net concept…. IOC and dependency Injection…. If once know how the Sprint.net work, it very easy to understand this great article. 🙂

Leave a comment