Workflow Designer Re-Hosting

This article starts by explaining what is designer re-hosting, what is workflow designer re-hosting and how it can be of benefit in dynamically editing workflows and rules. Then later it explains how you can re-host the workflow designer and then how it can be used to edit a rule. 

I assume that the reader knows what Windows Workflow Foundation (WF) is, what an Activity is, how to create Rules etc. If you are not familiar with it, I recommend you to first please go to my blog on this topic.

What is Designer Re-hosting?

Designer re-hosting refers to the ability to reuse the various controls offered within Visual Studio’s workflow design experience in custom applications. Such a tailored graphical user interface enables non-developers such as business users to easily create and edit workflows. These users are not familiar with the Visual Studio environment in most cases and they don’t need all the features it offers. Re-hosting the workflow designer provides a way to provide a developer experience for workflow, outside of Visual Studio.

Why Designer Re-hosting?

Let us consider Visual Studio – a great IDE from Microsoft for development of various technologies/tools like Workflow Foundation, ASP.NET, Entity Framework etc. Each such technology may use a different kind of design components for development. For example, WF has its own designer for creating workflows, activities etc. and provides XAML editing. ASP.NET provides a designer for adding controls and changing their HTML.

Some applications may require workflows and rules to be edited dynamically. Well, that is a bit challenging thing if you think about the designer environment which is used to create workflows and rules. Of course, you can create rules using the code without using graphical interface. For writing code, you should be a developer. I want the end users to be able to edit rules in my application. So, the option of writing code is ruled out; so, we are ended up with an option of providing the same kind of designer interface which was used to design the rules.

If we provide the same designer experience to the user to create rules, a user should be able to create rules which might help to reduce a lot of time and effort. That sounds very promising, right? Workflow Designer Re-hosting is the solution for this. It is useful in creating rules at runtime as well as editing the rules at runtime.

Designer Re-hosting

Figure 1

What is the scope?

You might still be wondering in what real scenario where designer re-hosting can be applied! Will the user be able to add any number of rules? Does the application need to be compiled again for the rules to be reflected?

I already mentioned that we can use re-hosting for creating rules dynamically as well as editing rules dynamically.

However, one major thing we need to understand about designer re-hosting is, rule engine will have to be compiled again to get the changes reflected in the application.

Then, you might ask what is the purpose of all this, if re-hosting is not an easy task.

Let us consider a business, where the rules are very complex. If a developer wants to create rules in this case, first, the business user will have to convey the logic of the rules to the developer. While produced by the user, it is found that some of the logic is not as the user expected. So, the developer will have to go back and modify the rule and bring it back to the user of another round of review. This process may continue until the developer is able to produce the rules correctly. We can see that a lot of time and energy is wasted perceiving the logic and implementing it.

If the user themselves can create or edit the rule, it will significantly reduce the time and effort. They will create or edit rules; all that the developer needs to do is just compile the rule engine. That’s it, the new/modified rules are reflected in the system!

Re-Hosting Architecture 

Figure 2 below, demonstrates the Workflow design components that can be re-hosted outside of the Visual Studio environment. It will provide the user a compelling experience for designing and editing workflows.

Designer Re-hosting

Figure 2

Workflow View

This is the designer area for the workflow. This is where we add controls and add connections and flows to create a workflow. This control provides a tree view summary of a workflow design thus helps in fast navigation to specific activities within the design. 

Toolbox

This contains activities required for creating or modifying a workflow. 

Property Grid

This is another useful component while creating/editing rules. It is a standard Windows Forms control that can be used here for setting properties of activities and rules. 

Workflow Outline

This is mainly used for easy navigation through the workflow view, especially when the design is very big. 

With designer re-hosting, the user will be able to drag and drop components from the Toolbox to the workflow view and set their properties using Property Grid and navigate through the workflow view using workflow outline. All these happen with a well-defined service interface handles the numerous service requests and responses that take place for example, when you drag and drop an activity to the workflow view.

As we have got a basic understanding about re-hosting, we can go ahead and do a re-hosting to find how it works.

How to re-host the workflow designer?

This sample demonstrates how you can re-host the workflow designer. It contains only one project.

I am using Visual Studio 2017 for creating the workflow designer re-hosted project.

Step 1 - Create a WPF Windows application

  • In Visual Studio 2017, take File -> New -> Project
  • Choose Windows Classic Desktop on the left pane and WPF App on the right pane
  • Name the project WorkflowDesignerRehostingDemo and click OK

Designer Re-hosting

Figure 3

By default, a WPF window name MainWindow.xaml will be created. You can rename it to if you wish. I am going to delete it and create a new WPF window.

Step 2 Add a WPF Window for acting as the workflow designer

  • Right-click on the project -> Add -> New Item
  • Choose WPF on the left pane and WPF Window on the right pane
  • Name it as DesignerWindow.xaml and click Add

Designer Re-hosting

Figure 4

Step 3 - Add a File menu and Areas for Toolbox, Properties grid and workflow view

Now, I am going to give a little makeover to the window so that it will be fit for showing the workflow designer components for editing a workflow.

First, let’s divide the window into three columns – one for Toolbox, one for property grid and one for workflow view.

  1. <Grid.RowDefinitions>  
  2.     <RowDefinition Height="25" />  
  3.     <RowDefinition Height="*" />  
  4.     <RowDefinition Height="40" />  
  5. </Grid.RowDefinitions>  
  6. <Grid.ColumnDefinitions>  
  7.     <ColumnDefinition Width="200" MinWidth="100" />  
  8.     <ColumnDefinition Width="*" MinWidth="300" />  
  9.     <ColumnDefinition Width="220" MinWidth="100" />  
  10. </Grid.ColumnDefinitions>  

Listing 1

Then, add a File menu and a few menu options to it. 

  1. <StackPanel Grid.ColumnSpan="3">  
  2.             <Menu Height="20"   
  3.                   VerticalAlignment="Top">  
  4.                 <MenuItem Header="_File">   
  5.                     <MenuItem Header="_New"   
  6.                               Command="{Binding Path=NewCommand}"/>  
  7.                     <MenuItem Header="_Open"   
  8.                               Command="{Binding Path=OpenCommand}"/>  
  9.                     <Separator />  
  10.                     <MenuItem Header="_Save"   
  11.                               Command="{Binding Path=SaveCommand}"/>  
  12.                     <MenuItem Header="Save _As"   
  13.                               Command="{Binding Path=SaveAsCommand}"/>  
  14.                     <Separator />  
  15.                     <MenuItem Header="E_xit"   
  16.                               Command="{Binding Path=ExitCommand}"/>  
  17.                 </MenuItem>  
  18.             </Menu>  
  19.         </StackPanel>  

Listing 2

The binding variables we used here like NewCommand, OpenCommand etc. will be declared later.

Then add 3 Tab Controls, one each for each column.

Name the tab item in the first column as Toolbox.

Name the tab item in the last column as Properties.

The second column needs two tab items – one for the designer view and one for the XAML view.

  1. <TabControl HorizontalAlignment="Stretch"   
  2.                     VerticalAlignment="Stretch"   
  3.                     Margin="0,0,4,0"   
  4.                     Grid.Column="0"   
  5.                     Grid.Row="1">  
  6.             <TabItem Header="Toolbox">  
  7.                 <ContentControl Content="{Binding WFToolboxControl}"/>  
  8.             </TabItem>  
  9.         </TabControl>  
  10.         <TabControl HorizontalAlignment="Stretch"   
  11.                     VerticalAlignment="Stretch"   
  12.                     Margin="0,0,0,0"   
  13.                     Grid.Column="1"   
  14.                     Grid.Row="1">  
  15.             <TabItem Header="Designer">  
  16.                 <ContentControl Content="{Binding WorkflowDesignerControl}"/>  
  17.             </TabItem>  
  18.             <TabItem Header="XAML"   
  19.                      GotFocus="RefreshXamlTabOnTabItemFocus" >  
  20.                 <TextBox Name="xamlTabTextBox" Text="{Binding XAML, Mode=OneWay}"  
  21.                          AcceptsReturn="True"   
  22.                          HorizontalScrollBarVisibility="Auto"  
  23.                          VerticalScrollBarVisibility="Auto" IsReadOnly="True">  
  24.                 </TextBox>  
  25.             </TabItem>  
  26.         </TabControl>  
  27.         <TabControl HorizontalAlignment="Stretch"   
  28.                     VerticalAlignment="Stretch"   
  29.                     Margin="5,0,0,0" Grid.Column="2"   
  30.                     Grid.Row="1">  
  31.             <TabItem Header="Properties">  
  32.                 <ContentControl Content="{Binding WorkflowPropertyControl}"/>  
  33.             </TabItem>  
  34.         </TabControl>  

Listing 3

The binding variables we used here like WFToolboxControl, WorkflowDesignerControl etc. will be declared later.

Step 4 - Add an empty workflow to be used for creating New Workflow

Right-click on the project, Add -> New Item

On the screen appeared, Choose Workflow from the left pane, and Activity from the right pane.

Name the file as sampleWorkflow and click Add.

Designer Re-hosting

Figure 5

We don’t have to do anything with this workflow. This is used as a template workflow when the user adds a new workflow using the New option from the File menu.

Step 5 - Add a Class – DesignerViewModel

This class will act as the backbone of the designer and will hold the data and handle the events of the designer window.

Add the fields, properties, events mentioned in the XAML to find different controls.

Also, add the methods for handling the events fired while selecting the File menu options on the designer window.

Since we are going to deal with workflows in this class, we need to add these namespaces related to workflow creation and manipulation.

  1. using System.Activities;  
  2. using System.Activities.Core.Presentation;  
  3. using System.Activities.Presentation;  
  4. using System.Activities.Presentation.Metadata;  
  5. using System.Activities.Presentation.Toolbox;  

Listing 4

We have created a File menu and added menu options to it in Listing 2. Now we need to add the commands in the ViewModel class which will get executed when user clicks on each menu option.

  1. public ICommand NewCommand { get; set; }  
  2. public ICommand OpenCommand { get; set; }  
  3. public ICommand SaveCommand { get; set; }  
  4. public ICommand SaveAsCommand { get; set; }  
  5. public ICommand ExitCommand { get; set; }  

Listing 5

To Implement the methods CanExecute and Execute, we will create a class. Then, we will use this class to create the instances of the command objects we created in Listing 5.

Step 6 - Add a class RunCommand which will invoke the right delegates and execute the command

Add a class named RunCommand and implement ICommand interface. So, when a user clicks on a File menu item, this class invokes the event handler associated with the clicked menu item and executes it. This class may look like as shown in Listing 6.

  1. private readonly Predicate<object> canExecute;  
  2.   
  3.         /// <summary>  
  4.         /// Action to be executed when the command is invoked  
  5.         /// </summary>  
  6.         private readonly Action<object> execute;  
  7.   
  8.         /// <summary>  
  9.         /// Creates a new command  
  10.         /// </summary>  
  11.         /// <param name="execute"></param>  
  12.         /// <param name="canExecute"></param>  
  13.         public RunCommand(Action<object> execute, Predicate<object> canExecute)  
  14.         {  
  15.             if (execute == null)  
  16.             {  
  17.                 throw new ArgumentNullException("execute");  
  18.             }  
  19.   
  20.             this.execute = execute;  
  21.             this.canExecute = canExecute;  
  22.         }  
  23.   
  24.         [DebuggerStepThrough]  
  25.         public bool CanExecute(object parameter)  
  26.         {  
  27.             return this.canExecute == null || this.canExecute(parameter);  
  28.         }  
  29.   
  30.         public void Execute(object parameter)  
  31.         {  
  32.             this.execute(parameter);  
  33.         }  
  34.   
  35.         public event EventHandler CanExecuteChanged  
  36.         {  
  37.             add  
  38.             {  
  39.                 CommandManager.RequerySuggested += value;  
  40.             }  
  41.   
  42.             remove  
  43.             {  
  44.                 CommandManager.RequerySuggested -= value;  
  45.             }  
  46.         }  

Listing 6

Step 7 - Add properties to DesignerViewModel class.

Now, coming back to DesignerViewModel class, let’s create the properties which we must bind to various controls in the DesingerWindow.xaml. 

  1.         public string FileName  
  2.         {  
  3.             get  
  4.             {  
  5.                 return this.fileName;  
  6.             }  
  7.   
  8.             private set  
  9.             {  
  10.                 this.fileName = value;  
  11.                 this.Title = string.Format("{0} - {1}", title, this.FileName);  
  12.             }  
  13.         }  
  14.   
  15. public WorkflowDesigner RehostedWFDesigner  
  16.         {  
  17.             get  
  18.             {  
  19.                 return this.rehostedWFDesigner;  
  20.             }  
  21.   
  22.             private set  
  23.             {  
  24.                 this.rehostedWFDesigner = value;  
  25.                 this.NotifyChanged("WorkflowDesignerControl");  
  26.                 this.NotifyChanged("WorkflowPropertyControl");  
  27.             }  
  28.         }  
  29.   
  30.         public object WorkflowDesignerControl  
  31.         {  
  32.             get  
  33.             {  
  34.                 return this.RehostedWFDesigner.View;  
  35.             }  
  36.         }  
  37.   
  38.         public object WorkflowPropertyControl  
  39.         {  
  40.             get  
  41.             {  
  42.                 return this.RehostedWFDesigner.PropertyInspectorView;  
  43.             }  
  44.         }  
  45.   
  46.         public string XAML  
  47.         {  
  48.             get  
  49.             {  
  50.                 if (this.RehostedWFDesigner.Text != null)  
  51.                 {  
  52.                     this.RehostedWFDesigner.Flush();  
  53.                     return this.RehostedWFDesigner.Text;  
  54.                 }  
  55.   
  56.                 return null;  
  57.             }  
  58.         }  

Listing 7

Step 8 - Create instances of Commands and Load Workflow Toolbox items and their icons

Add the code given in Listing 8 to the constructor.

Here, we will initialize the commands of File menu items, load Workflow (WF) toolbox items and icons for the toolbox items. 

  1. public DesignerViewModel()  
  2. {  
  3.     (new DesignerMetadata()).Register();  
  4.     LoadBuiltInActivityIcons();  
  5.     this.WFToolboxControl = CreateWFToolbox();  
  6.     this.NewCommand = new RunCommand(this.ExecuteNew, CanExecuteNew);  
  7.     this.OpenCommand = new RunCommand(this.ExecuteOpen, CanExecuteOpen);  
  8.     this.SaveCommand = new RunCommand(this.ExecuteSave, CanExecuteSave);  
  9.     this.SaveAsCommand = new RunCommand(this.ExecuteSaveAs, CanExecuteSaveAs);  
  10.     this.ExitCommand = new RunCommand(this.ExecuteExit, CanExecuteExit);  
  11.     this.RehostedWFDesigner = new WorkflowDesigner();  
  12. }  

Listing 8

Step 9 - Implement the methods for Loading WF toolbox items and icons

We need to implement the methods called in the constructor as shown in Listing 9.

These methods will load the workflow toolbox item and icons for each of these toolbox items to our application.

  1.        private static void LoadBuiltInActivityIcons()  
  2.        {  
  3.            try  
  4.            {  
  5.                var sourcAssembly = Assembly.LoadFrom(@"Lib\Microsoft.VisualStudio.Activities.dll");  
  6.   
  7.                var builder = new AttributeTableBuilder();  
  8.   
  9.                if (sourcAssembly != null)  
  10.                {  
  11.                    var stream =  
  12.                        sourcAssembly.GetManifestResourceStream(  
  13.                            "Microsoft.VisualStudio.Activities.Resources.resources");  
  14.                    if (stream != null)  
  15.                    {  
  16.                        var resourceReader = new ResourceReader(stream);  
  17.   
  18.                        foreach (var itemType in  
  19.                            typeof(Activity).Assembly.GetTypes().Where(  
  20.                                t => t.Namespace == "System.Activities.Statements"))  
  21.                        {  
  22.                            SetToolboxBitmapAttribute(builder, resourceReader, itemType);  
  23.                        }  
  24.                    }  
  25.                }  
  26.   
  27.                MetadataStore.AddAttributeTable(builder.CreateTable());  
  28.            }  
  29.            catch (FileNotFoundException)  
  30.            {  
  31.   
  32.            }  
  33.        }  
  34.          
  35.        /// <summary>  
  36.        /// Creates a Workflow toolbox.  
  37.        /// </summary>  
  38.        /// <param name="obj"></param>  
  39.  private static ToolboxControl CreateWFToolbox()  
  40.        {  
  41.            var toolboxControl = new ToolboxControl();  
  42.   
  43.            toolboxControl.Categories.Add(  
  44.                new ToolboxCategory("Control Flow")  
  45.                    {  
  46.                        new ToolboxItemWrapper(typeof(DoWhile)),  
  47.                        new ToolboxItemWrapper(typeof(ForEach<>)),  
  48.                        new ToolboxItemWrapper(typeof(If)),  
  49.                        new ToolboxItemWrapper(typeof(Parallel)),  
  50.                        new ToolboxItemWrapper(typeof(ParallelForEach<>)),  
  51.                        new ToolboxItemWrapper(typeof(Pick)),  
  52.                        new ToolboxItemWrapper(typeof(PickBranch)),  
  53.                        new ToolboxItemWrapper(typeof(Sequence)),  
  54.                        new ToolboxItemWrapper(typeof(Switch<>)),  
  55.                        new ToolboxItemWrapper(typeof(While)),  
  56.                    });  
  57.   
  58.            toolboxControl.Categories.Add(  
  59.                new ToolboxCategory("Primitives")  
  60.                    {  
  61.                        new ToolboxItemWrapper(typeof(Assign)),  
  62.                        new ToolboxItemWrapper(typeof(Delay)),  
  63.                        new ToolboxItemWrapper(typeof(InvokeMethod)),  
  64.                        new ToolboxItemWrapper(typeof(WriteLine)),  
  65.                    });  
  66.   
  67.            toolboxControl.Categories.Add(  
  68.                new ToolboxCategory("Error Handling")  
  69.                    {  
  70.                        new ToolboxItemWrapper(typeof(Rethrow)),  
  71.                        new ToolboxItemWrapper(typeof(Throw)),  
  72.                        new ToolboxItemWrapper(typeof(TryCatch)),  
  73.                    });  
  74.   
  75.            return toolboxControl;  
  76.        }  
  77.   
  78.        private void ExecuteNew(object obj)  
  79.        {  
  80.            this.RehostedWFDesigner = new WorkflowDesigner();  
  81.            this.RehostedWFDesigner.ModelChanged += this.WorkflowDesignerModelChanged;  
  82.            if (File.Exists(SampleWorkflow))  
  83.            {  
  84.                this.RehostedWFDesigner.Load(SampleWorkflow);  
  85.            }  
  86.            else  
  87.            {  
  88.                this.RehostedWFDesigner.Load(new Sequence());  
  89.            }  
  90.   
  91.            this.RehostedWFDesigner.Flush();  
  92.            this.FileName = UnNamedFile;  
  93.        }  
  94.   
  95.        private void ExecuteOpen(object obj)  
  96.        {  
  97.            var openFileDialog = new OpenFileDialog();  
  98.            if (openFileDialog.ShowDialog(Application.Current.MainWindow).Value)  
  99.            {  
  100.                this.LoadWF(openFileDialog.FileName);  
  101.            }  
  102.        }  
  103.   
  104.        private void ExecuteSave(object obj)  
  105.        {  
  106.            if (this.FileName == UnNamedFile)  
  107.            {  
  108.                this.ExecuteSaveAs(obj);  
  109.            }  
  110.            else  
  111.            {  
  112.                this.Save();  
  113.            }  
  114.        }  
  115.   
  116.        private void ExecuteSaveAs(object obj)  
  117.        {  
  118.            var saveFileDialog = new SaveFileDialog  
  119.            {  
  120.                AddExtension = true,  
  121.                DefaultExt = "xaml",  
  122.                FileName = this.FileName,  
  123.                Filter = "xaml files (*.xaml) | *.xaml;*.xamlx| All Files | *.*"  
  124.            };  
  125.   
  126.            if (saveFileDialog.ShowDialog().Value)  
  127.            {  
  128.                this.FileName = saveFileDialog.FileName;  
  129.                this.Save();  
  130.            }  
  131.        }  
  132.   
  133.        private void ExecuteExit(object obj)  
  134.        {  
  135.            Application.Current.Shutdown();  
  136. }   

Listing 9

Step 10 - Add Class to load Workflow assemblies

In Listing 9, we are trying to load the WF related assemblies for loading the workflow designer into our application. Let’s add a class to implement this functionality.

Add a new class named WorkflowCLRAssembly to the Project.

We will add code to load workflow assemblies and for registering the assemblies as shown in Listing 10.

  1. private void GetAsssemblyName()  
  2.        {  
  3.            var parts = this.ClrNamespace.Split(';');  
  4.   
  5.            if (parts.Length == 2)  
  6.            {  
  7.                var namevalue = parts[1].Split('=');  
  8.   
  9.                if (namevalue.Length == 2)  
  10.                {  
  11.                    var name = namevalue[1].Trim();  
  12.                    this.assemblyName = new AssemblyName(name);  
  13.                }  
  14.            }  
  15.        }  
  16.   
  17.        private void LoadWFDesignerAssembly()  
  18.        {  
  19.            if (this.assembly != null && !this.IsFrameworkAssembly())  
  20.            {  
  21.                var file = new Uri(assembly.CodeBase).AbsolutePath;  
  22.                var name = file.Insert(file.LastIndexOf('.'), ".design");  
  23.   
  24.                try  
  25.                {  
  26.                    this.DesignerAssembly = Assembly.LoadFile(name);  
  27.   
  28.                    this.CopyWFAssemblies(Assembly);  
  29.                    this.CopyWFAssemblies(DesignerAssembly);  
  30.                    RegisterWFDesigner();  
  31.                }  
  32.                catch (FileLoadException)  
  33.                {  
  34.                }  
  35.                catch (FileNotFoundException)  
  36.                {  
  37.                }  
  38.            }  
  39.        }  
  40.   
  41.        private void RegisterWFDesigner()  
  42.        {  
  43.            foreach (var metadataType in DesignerAssembly.GetTypes().Where(t => typeof(IRegisterMetadata).IsAssignableFrom(t)))  
  44.            {  
  45.                var metadata = Activator.CreateInstance(metadataType) as IRegisterMetadata;  
  46.                if (metadata != null)  
  47.                {  
  48.                    metadata.Register();  
  49.                }  
  50.            }  
  51.        }  
  52.   
  53.        private bool TryLoadWorkflow()  
  54.        {  
  55.            try  
  56.            {  
  57.                this.Assembly = Assembly.Load(this.assemblyName);  
  58.                return true;  
  59.            }  
  60.            catch (FileLoadException)  
  61.            {  
  62.            }  
  63.            catch (FileNotFoundException)  
  64.            {  
  65.            }  
  66.   
  67.            return false;  
  68.        }  
  69.   
  70.        private bool TryLoadWorkflow(string fxKey)  
  71.        {  
  72.            try  
  73.            {  
  74.                this.Assembly = Assembly.Load(this.GetNameByKey(fxKey));  
  75.                return true;  
  76.            }  
  77.            catch (FileLoadException)  
  78.            {  
  79.            }  
  80.            catch (FileNotFoundException)  
  81.            {  
  82.            }  
  83.            return false;  
  84.        }  
  85.   
  86.        public bool LoadWorkflow()  
  87.        {  
  88.            return this.IsFrameworkAssembly() ? this.TryLoadWorkflow(PublicTokenKey1) || this.TryLoadWorkflow(PublicTokenKey2) : this.TryLoadWorkflow();  
  89.        }  
  90.   
  91.        public bool LoadWorkflow(string fileName)  
  92.        {  
  93.            try  
  94.            {  
  95.                this.Assembly = Assembly.LoadFile(fileName);  
  96.                return true;  
  97.            }  
  98.            catch (FileLoadException)  
  99.            {  
  100.            }  
  101.            catch (FileNotFoundException)  
  102.            {  
  103.            }  
  104.   
  105.            return false;  
  106.        }  

Listing 10

Step 11 - Add a wrapper class to call the WorkflowCLRAssembly class

Now let’s create a wrapper class, which will call the WorkflowCLRAssembly and hold the assemblies of the WF designer. We will call this class from DesignerViewModel class to load WF assemblies.

Add a new class named WorkflowCLR to the project.

Add the below code to this class,

  1. public List<WorkflowCLRAssembly> References { get; set; }  
  2.   
  3. public WorkflowCLR(string file)  
  4. {  
  5.     this.References = new List<WorkflowCLRAssembly>();  
  6.     var doc = XElement.Load(file);  
  7.     var query = from attr in doc.Attributes() where attr.IsNamespaceDeclaration && attr.Value.ToLower().StartsWith("clr-namespace") select attr.Value;  
  8.     foreach (var assemblyName in query)  
  9.     {  
  10.         this.References.Add(new WorkflowCLRAssembly(assemblyName));  
  11.     }  
  12. }  
  13.   
  14. public WorkflowCLR LoadWorkflow()  
  15. {  
  16.     foreach (var workflowCLR in References)  
  17.     {  
  18.         workflowCLR.LoadWorkflow();  
  19.     }  
  20.   
  21.     return this;  
  22. }  
  23.   
  24. internal static WorkflowCLR LoadWorkflow(string name)  
  25. {  
  26.     return new WorkflowCLR(name).LoadWorkflow();  
  27. }  

Listing 11

Step 12 - Add methods to ViewModel class to load the workflow designer assemblies.

Now, add a few methods to DesignerViewModel class to load the workflow designer. 

  1. private void LoadWF(string name)  
  2. {  
  3.     this.ResolveAssemblies(name);  
  4.     this.FileName = name;  
  5.     this.RehostedWFDesigner = new WorkflowDesigner();  
  6.     this.RehostedWFDesigner.ModelChanged += this.WorkflowDesignerModelChanged;  
  7.     this.RehostedWFDesigner.Load(name);  
  8. }  
  9.   
  10. private void ResolveAssemblies(string name)  
  11. {  
  12.     var references = WorkflowCLR.LoadWorkflow(name);  
  13.   
  14.     var query = from reference in references.References where !reference.Loaded select reference;  
  15.     foreach (var workflowCLRAssembly in query)  
  16.     {  
  17.         this.CheckAssemblies(workflowCLRAssembly);  
  18.     }  
  19. }  
  20.   
  21. private void CheckAssemblies(WorkflowCLRAssembly workflowCLRAssembly)  
  22. {  
  23.     var openFileDialog = new OpenFileDialog  
  24.     {  
  25.         FileName = workflowCLRAssembly.CodeBase,  
  26.         CheckFileExists = true,  
  27.         Filter = "Assemblies (*.dll;*.exe)|*.dll;*.exe|All Files|*.*",  
  28.     };  
  29.   
  30.     if (openFileDialog.ShowDialog(Application.Current.MainWindow).Value)  
  31.     {  
  32.         if (!workflowCLRAssembly.LoadWorkflow(openFileDialog.FileName))  
  33.         {  
  34.             MessageBox.Show("Error loading assembly...");  
  35.         }  
  36.     }  
  37. }  

Listing 12

Yes, we have completed the workflow designer re-hosting. Now, let’s try running the application. You can set the DesignerWindow.xaml as the startup item of the project. For setting this,

Open App.xaml file.

In the <Application> tag, check if "DesignerWindow.xaml" is set as the value for StartupUri attribute. If not, set it as the Startup element.

Save the file and close it.

  1. <Application x:Class="WorkflowDesignerRehostingDemo.App"  
  2.              xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"  
  3.              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"  
  4.              xmlns:local="clr-namespace:WorkflowDesignerRehostingDemo"  
  5.              StartupUri="DesignerWindow.xaml">  

Listing 13

Now, build and run the application.

You will get a screen like one in Figure 6 while the application is loaded.

Designer Re-hosting

Figure 6

The menu will be in collapsed mode on load, I have expanded it to showing here.

You can click on New from menu items and you will get a screen like one shown in Figure 7.

You can add whatever activities you want from the Toolbox in the left pane.

The beauty of it is, you can drag and drop the Toolbox items to the workflow view.

Designer Re-hosting

Figure 7 

If you open an already existing workflow, you will be able to view and edit it.

I had created a workflow rule for providing a suggestion as per one’s BMI value.

Let’s see how it will look like when opening that rule.

Designer Re-hosting

Figure 8

You can add more activities by dragging from the Toolbox and dropping to the workflow view. Or you can edit the values of the existing activities if you wish. Once you have finished editing, you can choose Save from File menu and the workflow will be saved. If you wish to save it in a different name, you can choose Save As option from the File menu options.

Conclusion

The important thing to know is, editing a workflow using designer re-hosting doesn’t ensure updating the workflow dynamically until the edited workflow is rebuilt.

Workflow designer re-hosting is very useful when a user or a non-developer who knows the business well, want to create or edit a workflow. This will reduce the time and effort required for conveying the business to the developers and they are coming back with a solution which may still need review and correction. Whereas, if the user creates the workflow, the only job for the developer is to include it in the application source and rebuild the application and deploy the application again.

Thank you!

X

Build smarter apps with Machine Learning, Bots, Cognitive Services - Start free.

Start Learning Now