
The basic re-hosted workflow designer solution described in re-hosting the workflow designer article provides a baseline for a bespoke application that can be used for designing workflows, but without some further enhancements it is quite limited. By default it will only allow you to create and edit workflows that use the Framework’s built-in set of activities.
In order to make the re-hosted workflow designer more useful, we are going to add the ability to load additional assemblies into the designer so we can access a broader range of workflow activities.
This worked example will build upon the basic application discussed in the re-hosting the workflow designer article. If you are going build the solution as you go you should probably start by following the basic re-hosting article and creating the baseline application as a starting point. Once you are ready to continue, you can come back here!
First, let’s add a button to load the additional assembly via a file dialog box. This can be added to the stack panel in the 1st row of the main grid layout in the main window markup file.
<Button Content=" Load Library " Click="LoadLibraryButton_Click" Margin="5,5,5,5" />
Code language: HTML, XML (xml)
Next, we need to detect and prompt the user to find any missing dependencies when loading the new assembly. We also need to be able to traverse a Type’s inheritance chain to determine if it is an activity or not so we can decide if it should be added to the toolbox.
private Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
string assemblyName = args.Name.Contains(",")
? args.Name.Substring(0, args.Name.IndexOf(","))
: args.Name;
OpenFileDialog open = new OpenFileDialog();
open.Title =
"Please locate the missing dependent assembly (" + assemblyName + ")...";
open.Filter = ".NET Assemblies (*.exe,*.dll)|*.exe;*.dll";
open.CheckFileExists = true;
open.Multiselect = false;
bool? result = open.ShowDialog();
if (result != null && (bool)result)
{
return Assembly.LoadFrom(open.FileName);
}
return null;
}
private static IEnumerable<Type> GetBaseTypes(Type type)
{
// Need to get base types and interfaces for entire inheritance chain.
List<Type> types = new List<Type>();
List<Type> mainTypes = new List<Type>(type.GetInterfaces());
if (type.BaseType != null)
{
mainTypes.Add(type.BaseType);
}
types.AddRange(mainTypes);
foreach (Type subType in mainTypes)
{
foreach (Type inherited in GetBaseTypes(subType))
{
if (!types.Contains(inherited))
{
types.Add(inherited);
}
}
}
return types;
}
Code language: C# (cs)
Now, we need to add the event handler for our ‘add library’ button in the code behind file and also add the logic to load the type into the toolbox, if appropriate. We’ll register the assembly-resolving event handler just before loading the assembly, and then unregister it again after we’re done.
private void LoadActivityIntoToolbox(string category, Type tool)
{
ToolboxCategory section = this.GetToolboxCategory(this.MainToolbox, category);
// Check the activity isn't already there.
bool isPresent = false;
foreach (ToolboxItemWrapper wrapper in section.Tools)
{
if (wrapper.Type == tool)
{
isPresent = true;
break;
}
}
if (!isPresent)
{
this.AddToolToCategory(section, tool);
}
}
private void LoadLibraryButton_Click(object sender, RoutedEventArgs e)
{
OpenFileDialog open = new OpenFileDialog();
open.Title = "Select the assembly to load...";
open.Filter = ".NET Assemblies (*.exe,*.dll)|*.exe;*.dll";
open.CheckFileExists = true;
open.Multiselect = false;
bool? result = open.ShowDialog();
if (result != null && (bool)result)
{
try
{
// Register the assembly resolution event handler so we can detect any
// missing dependant assemblies and load them.
AppDomain.CurrentDomain.AssemblyResolve +=
this.CurrentDomain_AssemblyResolve;
// Now load the properly and iterate through its types.
Assembly library = Assembly.LoadFrom(open.FileName);
bool isActivity;
IEnumerable<Type> baseTypes;
List<Attribute> categories;
string categoryName;
foreach (Type entry in library.GetTypes())
{
if (!entry.IsClass) continue;
baseTypes = GetBaseTypes(entry);
isActivity = (baseTypes.Contains(typeof(Activity)) ||
baseTypes.Contains(typeof(System.Activities.Statements.State)));
if (!isActivity) continue;
categories = new List<Attribute>(
entry.GetCustomAttributes(typeof(CategoryAttribute)));
categoryName = categories.Count > 0
? ((CategoryAttribute)categories[0]).Category
: "Misc";
this.LoadActivityIntoToolbox(categoryName, entry);
}
}
finally
{
AppDomain.CurrentDomain.AssemblyResolve -=
this.CurrentDomain_AssemblyResolve;
}
}
}
Code language: C# (cs)
We’ve now got a workflow designer application that we can load additional assemblies into and get any activities they contain added to the toolbox so we can use them in our workflows.