Using Dependency Injection with CAB

I was working through a problem tonight regarding dependency injection and CAB. CAB provides a facility to inject services and whatnot into other class using ObjectBuilder, Microsoft's DI framework. ObjectBuilder isn't the same as a DI/IOC container like Windsor Container or Spring.NET (or Jeremy Millers excellent StructureMap) but more like a framework for building containers. However in CAB it serves the purpose we need.

Let's say I have a service that performs lookups and returns me lists of items from some backend system. I would like to use this LookupService in various modules but I don't want the modules responsible for creating the service (especially since I only want one of them and don't want to deal with singletons) and I want an easy way to ensure the service is loaded and ready to go when I need it. Here's where CAB will help you with this.

First let's look at our service implementation:

public class LookupService : ILookupService

{

    public List<KeyValuePair<int, string>> Items

    {

        get

        {

            List<KeyValuePair<int, string>> items = new List<KeyValuePair<int, string>>();

            items.Add(new KeyValuePair<int, string>(1, "Item 1"));

            items.Add(new KeyValuePair<int, string>(1, "Item 2"));

            return items;

        }

    }

}

This is a straight forward service that returns a generic List<> of KeyValuePairs<>. I might use this in my UI in a combo box or whatever, but it's just a lookup of items. The implementation here is hard coded, but you could just as easily have this call out to a database, do an asynchronous web service call, whatever you need.

To share the service, I'll use the Infrastructure.Module project in my SCSF generated solution. This module gets loaded first and using SCSF I have it set to be a dependency so whenever the system loads any module I'll load this one first, ensuring my service is there. Here's my ProfileCatalog.xml that shows the dependency.

<SolutionProfile xmlns="http://schemas.microsoft.com/pag/cab-profile/2.0">

    <Section Name="Services">

        <Modules>

            <ModuleInfo AssemblyFile="Infrastructure.Module.dll" />

        </Modules>

    </Section>

    <Section Name="Apps">

        <Dependencies>

            <Dependency Name="Services" />

        </Dependencies>

        <Modules>

            <ModuleInfo AssemblyFile="Project.dll" />

        </Modules>

    </Section>

</SolutionProfile>

The module dependency is part of SCSF so it won't exist if you're just using CAB. In my profile catalog, the moment the Project.dll module loads, it will first load it's dependency module(s) from the Services section of the XML file. You can have as many services as you want here and they'll load in reverse order that they're listed in the file.

To instantiate the service and make it available, I have to load it up and add it to the RootWorkItem and it's list of services. This is done in the ModuleController.cs in the Infrastructure.Module project:

public class ModuleController : WorkItemController

{

    public override void Run()

    {

        AddServices();

        ExtendMenu();

        ExtendToolStrip();

        AddViews();

    }

 

    private void AddServices()

    {

        WorkItem.RootWorkItem.Services.AddNew<LookupService, ILookupService>();

    }

 

    private void ExtendMenu()

    {

    }

 

    private void ExtendToolStrip()

    {

    }

 

    private void AddViews()

    {

    }

}

If I were to load it up like a regular WorkItem and only use this code:

private void AddServices()

{

    WorkItem.Services.AddNew<LookupService, ILookupService>();

}

Then I would be loading it into the services for this module only, which is great, but I want this for all modules to use so I add it to my RootWorkItem. RootWorkItem is a property of any WorkItem that refers to the one and only root item created by the Shell. This way I know there's only one and I can access it from any module anywhere.

Once it's been added to the WorkItems list of Services, I can inject it into any module I need. I'll inject it into my presenter class as that's where I'll use it. The presenter will call the service to get it's values, and set the View with those values to update some GUI element (implementation of the View isn't shown but it just takes the values and binds them to a listbox or whatever you would use them for). I can inject it into the Presenter class two different ways. First, I can use the [ServiceDependency] tag in a parameter passed to the constructor of the Presenter:

public class ProjectListViewPresenter : Presenter<IProjectListView>

{

    private ILookupService _lookupService;

 

    public ProjectListViewPresenter([ServiceDependency] ILookupService lookupService)

    {

        _lookupService = lookupService;

    }

}

Not that nowhere do I have to call the constructor, this is done with the AddViews method in the ModuleController and it knows that it needs a type of ILookupService to inject during construction. The constructor sets a private member variable of type ILookupService to the value passed in. ObjectBuilder knows it needs to get an object of that type and will find it using the ServiceLocator service, which is constructed by the Shell. The second way is I can set a property and decorate it using the [ServiceDependency] tag like so:

public class ProjectListViewPresenter : Presenter<IProjectListView>

{

    private ILookupService _lookupService;

 

    [ServiceDependency]

    public ILookupService LookupService

    {

        get { return _lookupService; }

        set { _lookupService = value; }

    }

}

This is the same effect and is done whenever the object is created. Use one technique, not both as they'll both be called. Even though it's the same service object, it's just a waste to do it twice. Finally I just use the service in a method in my presenter when it's ready to update the view:

public class ProjectListViewPresenter : Presenter<IProjectListView>

{

    private ILookupService _lookupService;

 

    [ServiceDependency]

    public ILookupService LookupService

    {

        get { return _lookupService; }

        set { _lookupService = value; }

    }

 

    public override void OnViewReady()

    {

        View.Items = LookupService.Items;

        base.OnViewReady();

    }

}

The end result is that I have a loosely coupled service that's injected into my presenter and provides my view with the services it needs. You can use either technique to set the service in the presenter and the great thing is that using something like Rhino mocks, you don't need to create the implementation of the service so writing presenter tests is a breeze with this technique, as you can setup whatever conditions you want for your tests.

No Comments