Possible bug in CabApplication

Topics: CAB & Smart Client Software Factory
Mar 21, 2006 at 7:12 PM
originally posted by: kentcb

Hi there,

We have the beginnings of a CAB application and ran into what I believe may be a bug in the CabApplication class.

Here goes . . . Our root form has a dependency on a service declared like this:

public partial class RootForm : Form
{
private ConnectivityService _connectivityService;
ServiceDependency
public ConnectivityService ConnectivityService
{ set { _connectivityService = value; } }

...
}

We have our shell application declared as:

public class ShellApplication : FormShellApplication<WorkItem, RootForm>

and we start it with:

new ShellApplication().Run();

We had our services assembly declared in ProfileCatalog.xml like this (names changed to protect the innocent):

&ltSolutionProfile xmlns="http://schemas.microsoft.com/pag/cab-profile"&gt
&ltModules&gt
&ltModuleInfo AssemblyFile="Company.Product.Services.dll" /&gt
&lt/Modules&gt
&lt/SolutionProfile&gt

When we ran our application, we were getting the following exception:

Microsoft.Practices.CompositeUI.Services.ServiceMissingException was unhandled
Message="Service Company.Product.Services.Connectivity.ConnectivityService is not available in the current context."
Source="Microsoft.Practices.CompositeUI"
StackTrace:
at Microsoft.Practices.CompositeUI.Collections.ServiceCollection.Get(Type serviceType, Boolean ensureExists) in C:\Data\CAB\Src\CompositeUI\Collections\ServiceCollection.cs:line 300
at Microsoft.Practices.CompositeUI.ServiceDependencyAttribute.ServiceDependencyParameter.GetValue(IBuilderContext context) in C:\Data\CAB\Src\CompositeUI\ServiceDependencyAttribute.cs:line 69
at Microsoft.Practices.ObjectBuilder.PropertySetterInfo.GetValue(IBuilderContext context, Type type, String id, PropertyInfo propInfo) in ...
<snip (stack trace was very deep!)>

When we got this exception we tried explicitly declaring our services module with the Module attribute, and then declaring a dependency on that module from our UI assembly. However, this resulted in another exception, again telling us that the service was not loaded.

I stepped through the CAB source and found the following lines in CabApplication:

ProcessShellAssembly();
rootWorkItem.BuildUp();
LoadModules();
rootWorkItem.FinishInitialization();

The rootWorkItem.BuildUp() line was failing because of the service dependency. However, the line after that - LoadModules() - is what eventually loads our service. Swapping those two lines fixed the problem for us:

ProcessShellAssembly();
LoadModules();
rootWorkItem.BuildUp();
rootWorkItem.FinishInitialization();

That way, all services are loaded before the root work item is built up.

Could someone please confirm whether this is a bug (maybe I'm just doing something wrong elsewhere)?

Thanks,
Kent
Mar 21, 2006 at 7:18 PM
originally posted by: matiaswoloski

Hi Kent,

Did you added the service?

You can either (1) add the Service attribute above your service declaration inside your module or (2) you can use the WorkItem.Services.AddNew<> to add the service for the module

1.
Service(typeof(IConnectivityService)
public class ConnectivityService : IConnectivityService { .. }

2.
protected override void AddServices() {
WorkItem.Services.AddNew<ConnectivityService, IConnectivityService>
}

Notice that if you add the service in your module, its scope will be the root workitem of the module and the descendand workitems, views or whatever class you have in the WorkItem container.
If you want the service to be available across the whole app you should add it to the RootWorkItem

RootWorkItem.Services.AddNew<ConnectivityService, IConnectivityService>

Matias,
http://staff.southworks.net/blogs/matiaswoloski
Mar 21, 2006 at 8:34 PM
originally posted by: kentcb

Hi Matias, and thanks for the prompt reply.

We were using option number 1 in your list.

Let me confirm when you're saying: if I use option 1 then the scope of the service will be limited to its own module. If I use option 2 the scope of the service will be limited to its work item and all child work items.

Is that right? That seems counter-intuitive to me. What is the reasoning behind this behaviour?

Thanks,
Kent
Mar 21, 2006 at 10:43 PM
originally posted by: matiaswoloski

I haven't read your post carefully.

After rereading....
You won't be able to use the ServiceDependency in the Shell form and I think you found why: the rootworkitem is initialized, all the builder strategies run, including the one that looks for ServiceDependency's and try to inject a Service, and finally the modules are loaded. Because the module is not loaded until the root workitem is initialized you get the exception

If you want to use a service in the Shell you can inject the root workitem in the Shell

ServiceDependency
public WorkItem WorkItem
{
get { return _rootWorkItem; }
set { _rootWorkItem = value; }
}

And then use this:

IMyService svc = _rootWorkItem.Services.Get<IMyService>();

That makes sense?

Matias
http://staff.southworks.net/blogs/matiaswoloski
Mar 22, 2006 at 8:27 AM
originally posted by: BradWilsonMSFT

This is not a bug. This is the intended initialization order. The reason is that modules will most likely need to depend on things that are available in the shell in order to function properly.

To work around this, instead of taking a service dependency on the service directly, you could write something like:

public partial class RootForm : Form
{
private WorkItem _workItem;
private ConnectivityService _connectivityService;

ServiceDependency
public WorkItem WorkItem
{
set { _workItem = value; }
}

public ConnectivityService ConnectivityService
{
get
{
if (_connectivityService == null && _workItem != null)
_connectivityService = _workItem.Services.Get<ConnectivityService>();
return _connectivityService;
}
}
}

That will "just in time" retrieve the service instance for you. You will need to make sure you don't attempt to use the service before it's available.

Alternatively, you could move the service into the Shell project rather than loading it through the use of a module, and manually register it before the shell is created so that the shell can use ServiceDependency on it.