Rich Newman

September 9, 2007

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.

September 8, 2007

Introduction to Services in the CAB (Introduction to the CAB/SCSF Part 7)

Introduction

Part 6 of this series of articles concluded our discussion of dependency injection in the CAB.

This article and part 8 of the series will discuss services in the CAB in some more detail. This article discusses services in general terms, whilst part 8 will show in detail the various ways of creating and using services.

What is a ‘Service’?

You probably already have an intuitive idea of what a ‘service’ is in computer programming. Wikipedia defines a service in the context of a Service-oriented architecture as ‘a discretely defined set of contiguous and autonomous business or technical functionality’. The CAB documentation defines a service as ‘a supporting class that provides functionality to other components in a loosely coupled way’. CabPedia has a good definition:

‘Services allow easy access to a chunk of functionality that may be used frequently throughout an application. Error handlers, loggers and the event broker are good examples of functionality that can be exposed as a service.’

So think of a service as a chunk of related functionality. In the CAB each service is exposed through one class (and maybe an associated interface) that provides a gateway into the functionality.

We have already seen how to use CAB services in earlier articles in this series. Part 2 described how one of the collection classes on a WorkItem is the Services collection. Part 5 showed how we can inject a service (which is just any item in the Services collection) by using a setter with a ServiceDependency attribute. We saw that we can inject the WorkItem itself in this way. This is because a WorkItem is automatically a ‘service’ in its own Services collection.

The Services Collection versus the Items Collection

Part 5 of this series of articles also touched on the fact that the Services collection on a WorkItem has some important differences with the Items collection. This is separate from the fact that the intent of the two collections is different: services are meant to be objects that expose chunks of related functionality, whereas items are just any objects that we want to be able to access through the WorkItem.

The differences between the collections can be confusing so I’ll start this discussion by comparing the two collections.

Services vs Items (1): Unique Types

The most obvious of these conceptual differences is the fact that the Services collection can only ever contain one object of a given type. If we attempt to add a second service of the same type to a Services collection we get an ArgumentException with message ‘A service of this type already exists’.

In contrast, the Items collection of a WorkItem can contain multiple objects of the same type. To accommodate this all objects added to the Items collection are assigned a unique string ID, either by the user explicitly, or by the CAB implicitly (a random GUID is assigned).

However, this means that if we want to access a specific object in the Items collection we have to know its ID. Hence the ComponentDependency attribute, which injects an object from the Items collection, needs an ID passed as an argument.

We don’t need an ID to access an object in the Services collection. Because we can identify a service uniquely by its type the ServiceDependency attribute can be used to inject a service without the need for any ID.

Services vs Items (2): Searching the Hierarchy

Another difference between the Services collection and the Items collection is what happens if we request a specific object from the collection and the CAB can’t find it in the WorkItem’s collection. When trying to retrieve a service the CAB will then search in any parent WorkItem’s Services collections for the object, and return it if found. With the Items collection the CAB will just return null if the object cannot be found in the specific Items collection for the WorkItem.

Basic Service Example without Dependency Injection

A simple example of how to create and use a service without using dependency injection is available. The ‘service’ here is just one class (MyService) with one method (GetHello) that returns the string “Hello World”:

    public class MyService
    {
        public string GetHello()
        {
            return "Hello World";
        }
    }

We can make this simple class into a CAB service by adding it to the Services collection of a WorkItem. In this case we add it to the Services of the RootWorkItem in AfterShellCreated:

        protected override void AfterShellCreated()
        {
            RootWorkItem.Services.AddNew<MyService>();
            UseMyService();
            ...

We use the service in routine UseMyService. Here we retrieve the service using the Get method of the Services collection, telling it the type of the service we want to retrieve. We then call our method and output the results to the Output window:

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

It’s as simple as that. The full code also outputs all of the services that have been set up in the application, and shows that our MyService service is in there. You can see that there are a surprisingly large number of services set up by default by the CAB even in a simple application of this kind:

SERVICES:
[Microsoft.Practices.CompositeUI.WorkItem, Microsoft.Practices.CompositeUI.WorkItem]
[Microsoft.Practices.CompositeUI.Services.ICryptographyService, Microsoft.Practices.CompositeUI.Collections.ServiceCollection+DemandAddPlaceholder]
[Microsoft.Practices.CompositeUI.ITraceSourceCatalogService, Microsoft.Practices.CompositeUI.TraceSourceCatalogService]
[Microsoft.Practices.CompositeUI.Services.IWorkItemExtensionService, Microsoft.Practices.CompositeUI.Services.WorkItemExtensionService]
[Microsoft.Practices.CompositeUI.Services.IWorkItemTypeCatalogService, Microsoft.Practices.CompositeUI.Services.WorkItemTypeCatalogService]
[Microsoft.Practices.CompositeUI.IWorkItemActivationService, Microsoft.Practices.CompositeUI.SimpleWorkItemActivationService]
[Microsoft.Practices.CompositeUI.Services.IAuthenticationService, Microsoft.Practices.CompositeUI.Services.WindowsPrincipalAuthenticationService]
[Microsoft.Practices.CompositeUI.Services.IModuleLoaderService, Microsoft.Practices.CompositeUI.Services.ModuleLoaderService]
[Microsoft.Practices.CompositeUI.Services.IModuleEnumerator, Microsoft.Practices.CompositeUI.Services.FileCatalogModuleEnumerator]
[Microsoft.Practices.CompositeUI.Commands.ICommandAdapterMapService, Microsoft.Practices.CompositeUI.Commands.CommandAdapterMapService]
[Microsoft.Practices.CompositeUI.UIElements.IUIElementAdapterFactoryCatalog, Microsoft.Practices.CompositeUI.UIElements.UIElementAdapterFactoryCatalog]
[Microsoft.Practices.CompositeUI.WinForms.IControlActivationService, Microsoft.Practices.CompositeUI.WinForms.ControlActivationService]
[Shell.MyService, Shell.MyService]

Splitting the Interface from the Implementation

As we all know, for services it is considered good practice to separate our interface from our implementation. This allows us to vary the way the service is implemented without having necessarily to change the interface and thus affect client code. This can be important in a composite smart client application where different teams may be working on different parts of the application.

This can be very easily done for our basic example. We simply set up an interface with our method signature in it:

    public interface IMyService
    {
        string GetHello();
    }

We then implement this interface on our MyService class:

    public class MyService : IMyService
    {
        public string GetHello()
        {
            return "Hello World";
        }
    }

Another change we need to make is how we create the service. We want the type associated with it in the Services collection to be IMyService, but clearly we need to tell the CAB that it is creating an object of type MyService (it can’t just instantiate the interface). The syntax for this is as below:

            RootWorkItem.Services.AddNew<MyService, IMyService>();

The UseMyService method now retrieves the service using the IMyService interface. It doesn’t need to know about the MyService class:

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

These are the only changes that need to be made. The code for this example is available.

Conclusion

That concludes our initial introduction to services in the CAB. Part 8 will show the various options for creating and using services in more detail.

References

CabPedia page on Services
http://www.cabpedia.com/index.php?title=Services

Wikipedia on Services in an SOA
http://en.wikipedia.org/wiki/Service_%28Systems_Architecture%29

The Shocking Blue Green Theme. Create a free website or blog at WordPress.com.

Follow

Get every new post delivered to your Inbox.

Join 81 other followers