Dynamically loading modules in CAB.

Although this little bit of code and technique is specific to the WPF variant of CAB, I’m sure you can use it in the Windows Forms variant as well.

In the Cobalt.IDE I had developed a very similar structure to what you find in CAB but it was from the beginning clear to me that modules should be loaded on demand and not automtically at start-up (as it is in CAB now). The ideas was that you can specify in the configuration file some kind of AutoLoad attribute and that module information (ModuleInfo class in CAB) would be loaded but not necessarily the assembly. As it stands now CAB is rather poor in certain areas and dynamically loading assemblies is not configurable, however you can use the mechanisms burried in the architecture to load modules and workitems at runtime. Hope this will be useful to somebody since it seems only few people are interested in the WPF-CAB things (too difficult to handle?). Another area where I will shortly post about is the possibility to make the CAB shell interactive with some kind of macro’s. If you look into Cobalt and Netron you will see what I mean. Cobalt has a scripting plugin with which you can dive into the runtime hierarchy and execute C# stuff. More about this later.

What I did is the following:

  • attaching a menu item in the shell which loads a precompiled assembly/module
  • the way the menu item is attached is a technique I used extensively in the free Unfold code. It’s based on the idea of coupling UI elements to ‘tools’. The tools are one-one with a specific Command as well as an Execute method and a Query method which is being called regularly by the WPF loop to see if menu item are still enabled.
  • the key to loading a module at runtime is hidden in the ModuleLoaderService of the CompositeUI assembly. This service is automatically loaded by the CABAppplication and available through the Services collection in diverse places. The IModuleLoaderService has two overloaded Load methods of which the easiest one is accepting an assembly to load and a workitem that acts as the host for the module. All you need to do is, hence, picking up and assembly and passing it to the service. Of course, you should not put a <moduleinfo> section in your ProfileCatalog for the dynamic module!

The following snippet adds dynamically a new menu item and attaches a handler via a tool.

1
2
3
4
5
6
7
8
9
10
11
protected override void AfterShellCreated()
{
...
menuItem = new MenuItem();
menuItem.Header = "Test";
TestTool testTool = new TestTool(RootWorkItem);
menuItem.CommandBindings.Add(new CommandBinding(testTool.Command, testTool.OnExecute, testTool.OnQueryEnabled));
menuItem.Command = testTool.Command;
RootWorkItem.UIExtensionSites[UIExtensionConstants.FILE].Add(menuItem);
...
}

The tool is based on a ToolBase and is very similar to what you find in the Unfold code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
public abstract class ToolBase
{
///
/// the Name field
///
private string mName;
///
/// the command attached to this tool
///
protected RoutedUICommand mCommand;
///
/// Gets the routed command.
///
/// The command.
public RoutedUICommand Command
{
get { return mCommand; }
}
public string Name
{
get { return mName; }
set { mName = value; }
}
#region Constructor
///
///Default constructor
///
public ToolBase(string name)
{
this.mName = name;
mCommand = new RoutedUICommand();
 
}
#endregion
#region Methods
///
/// Determines if a command is enabled. Override to provide custom behavior. Do not call the
/// base version when overriding.
///
public virtual void OnQueryEnabled(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = true;
e.Handled = true;
}
 
///
/// Function to execute the command.
///
public abstract void OnExecute(object sender, ExecutedRoutedEventArgs e);
#endregion
}

Finally, this is where the real work is done. Make sure you put the code where the RootWorkItem is accessible and not null…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
public class TestTool : ToolBase
{
 
WorkItem worker;
public TestTool(WorkItem worker)
: base("Test tool")
{
this.worker = worker;
}
public override void OnExecute(object sender, ExecutedRoutedEventArgs e)
{
Microsoft.Practices.CompositeUI.Services.IModuleLoaderService loader = worker.Services.Get();
if (loader == null) return;
string testAssembly = "Unfold.StartModule.dll";
System.Reflection.Assembly[] assemblies = new System.Reflection.Assembly[] { LoadAssembly(testAssembly) };
 
loader.Load(worker, assemblies);
}
private string GetModulePath(string assemblyFile)
{
if (System.IO.Path.IsPathRooted(assemblyFile) == false)
assemblyFile = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, assemblyFile);
 
return assemblyFile;
}
private System.Reflection.Assembly LoadAssembly(string assemblyFile)
{
 
assemblyFile = GetModulePath(assemblyFile);
 
System.IO.FileInfo file = new System.IO.FileInfo(assemblyFile);
System.Reflection.Assembly assembly = null;
 
try
{
assembly = System.Reflection.Assembly.LoadFrom(file.FullName);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
 
return assembly;
}
}

Note that there are other ways to do this, you could go much deeper and plug your own module loader service which could handle configuration attributes. Or you could go even further and extend the CompositeUI on top of the ObjectBuilder mechanisms. CAB is a wonderful playground for ideas.

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

top