Monday, November 12, 2012

Visual Workflow Tracking

As you read my previous posts about the workflow tracking, you know that the tracking records are either can be displayed in the event viewer or inserted into the database.

Today I want to share how to make use of these tracking records and display it in a nice graphical way. First of all, I would like to highlight that the code that I will share below is actually got it from MSDN workflow sample project and did some modification on it.

As you know, we normally design our workflow in the workflow designer in Visual Studio. The original sample source code from MSDN demonstrate how the designed workflow activities run from one activity to another activity. But, I want to display only which workflow activity encounter error and causing the workflow instance get suspended.

How?

First, we need to track which activity encounter error and fall into faulted state. In this case, we need to use SQL Tracking because tracking records which are stored in the database are easier to retrieve compare to the log in the event viewer. Then, make sure you have enabled the ActivityStateRecord tracking.

Next, create a new WPF application.

Then, create a User Control (WPF / not windows form) in your WPF project.



Then, create a grid with a name. Later, we will add a workflow designer control to that grid.


<UserControl x:Class="PersistenceStoreSample.UI.VisualTracking.WorkflowDesignerHost"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             mc:Ignorable="d"
             d:DesignHeight="300" d:DesignWidth="300">
    <Grid>
        <Grid x:Name="WorkflowGrid" />
    </Grid>
</UserControl>


I am going to create a host for WorkflowDesigner which you can see in Visual Studio for you to draw the workflow model. I would want it to be hosted and display it in my WorkflowGrid which is in my WPF user control, then later get it added into my WPF main window.

Below is the source code to load workflow into the workflow designer. My designed workflow is a physical .xaml file which is sitting in a separate workflow project.

You need to add following assembly reference first:
System.Activities
System.Activities.Core.Presentation
System.Activities.Presentation


    public partial class WorkflowDesignerHost : UserControl
    {
        public WorkflowDesigner WorkflowDesigner { get; set; }
        public IDesignerDebugView DebuggerService { get; set; }
        public Activity WorkflowActivity { get; set; }

        public WorkflowDesignerHost()
        {
            InitializeComponent();
            RegisterMetadata();
        }

        public void LoadWorkflowDesigner()
        {
            this.WorkflowDesigner = new WorkflowDesigner();
            this.DebuggerService = this.WorkflowDesigner.DebugManagerView;
            this.WorkflowActivity = new LeaveWorkflowService();
           
            //Load physical workflow xaml file into the designer
            this.WorkflowDesigner.Load("LeaveWorkflowService.xaml");

            //Load the workflow designer view into the Grid
            this.WorkflowGrid.Children.Add(this.WorkflowDesigner.View);
        }

        private void RegisterMetadata()
        {
            (new DesignerMetadata()).Register();
        }
    }


Back to the Main Window, add the newly create UserControl into it.
Add the assembly reference to the window first.


<Window x:Class="PersistenceStoreSample.UI.VisualTracking.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:wf="clr-namespace:PersistenceStoreSample.UI.VisualTracking"
        Title="MainWindow" Height="600" Width="800">


Then, do your own UI design, then add the UserControl into the grid.


<wf:WorkflowDesignerHost x:Name="WorkflowHost" Grid.Row="2"
                HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />



Then, place the following code to call the method from UserControl to load workflow designer, either in a button or the MainWindow constructor method as you wish.

WorkflowHost.LoadWorkflowDesigner();

Due to the WorkflowDesigner.Load() method load the workflow with a physical .xaml file, you need to make sure .xaml file is exist in the compiled folder.


P/S: If you do not like to have WorkflowDesigner to load physical .xaml file due to the file maintainability concern, I will share in my next post about how to load workflow xaml from the assembly embedded resource.

Here is how the UI look like after running the project. You can see that the whole workflow designer that you can see in Visual Studio is now appearing in your WPF application.



Alright, the UI is ready. Now we want to make use of the yellow highlight that we normally can see while debugging code to pin point which activity actually had failed.

First, we need to find out what activities we have inside the loaded WorkflowDesigner, then collect the information of the location of each of the activities in the WorkflowDesigner.


        private Dictionary<object, SourceLocation> UpdateSourceLocationMappingInDebuggerService()
        {
            object rootInstance = WorkflowHelper.GetRootInstance(this.WorkflowDesigner);
            Dictionary<object, SourceLocation> sourceLocationMapping = new Dictionary<object, SourceLocation>();
            Dictionary<object, SourceLocation> designerSourceLocationMapping = new Dictionary<object, SourceLocation>();

            if (rootInstance != null)
            {
                Activity documentRootElement = WorkflowHelper.GetRootWorkflowElement(rootInstance);

                SourceLocationProvider.CollectMapping(
                    WorkflowHelper.GetRootRuntimeWorkflowElement(this.WorkflowActivity),
                    documentRootElement, sourceLocationMapping,
                    this.WorkflowDesigner.Context.Items.GetValue<WorkflowFileItem>().LoadedFile);

                SourceLocationProvider.CollectMapping(
                    documentRootElement,
                    documentRootElement,
                    designerSourceLocationMapping,
                   this.WorkflowDesigner.Context.Items.GetValue<WorkflowFileItem>().LoadedFile);

            }

            // Notify the DebuggerService of the new sourceLocationMapping.
            // When rootInstance == null, it'll just reset the mapping.
            // DebuggerService debuggerService = debuggerService as DebuggerService;
            if (this.DebuggerService != null)
            {
                ((DebuggerService)this.DebuggerService).UpdateSourceLocations(designerSourceLocationMapping);
            }

            return sourceLocationMapping;
        }


Then, create a map for all activities. Later we can find the location of a specific activity in the WorkflowDesigner easily with an activity Id.


        private Dictionary<string, Activity> BuildActivityIdToWfElementMap(Dictionary<object, SourceLocation> wfElementToSourceLocationMap)
        {
            Dictionary<string, Activity> map = new Dictionary<string, Activity>();

            Activity wfElement;
            foreach (object instance in wfElementToSourceLocationMap.Keys)
            {
                wfElement = instance as Activity;
                if (wfElement != null)
                {
                    map.Add(wfElement.Id, wfElement);
                }
            }

            return map;
        }


Lastly, create a method to trace the particular activity and highlight it in the UI.


        public void TraceWorkflow(CustomActivityStateRecord lastActivity)
        {
            Dictionary<object, SourceLocation> wfElementToSourceLocationMap =
                UpdateSourceLocationMappingInDebuggerService();

            Dictionary<string, Activity> activityIdToWfElementMap =
                BuildActivityIdToWfElementMap(wfElementToSourceLocationMap);

            ShowDebug(wfElementToSourceLocationMap[activityIdToWfElementMap[lastActivity.ActivityId]]);
        }



        private void ShowDebug(SourceLocation srcLoc)
        {
            this.Dispatcher.Invoke(DispatcherPriority.Render
                , (Action)(() =>
                {
                    this.WorkflowDesigner.DebugManagerView.CurrentLocation = srcLoc;

                }));
        }


Now, go and run your application and then purposely create some errors. As mentioned earlier, you need to enable SQL tracking. If you had done so already, you should expect to see a tracking record had been added into the activity instance table. Here is how my SQL tracking activity table look like:


Normally, what you would want to see is which workflow instance is suspended. Hence, you either can refer to AppFabric dashboard or query the Workflow Persistence Store database.



Now, take the suspended workflow instance Id which is shown in the persistence store table and use it in your WPF Visual Tracking application. After clicking the [Trace] button, the yellow highlight indicate which last activity had failed to run properly base on last tracking record.




The tool above will be useful for technical support. They can easily trace what had happened to the suspended workflow instances. You can also enhance the tool by making it able to restart or resume the suspended workflow instance.


If you wish to download my complete implementation, feel free to get it from HERE.


No comments:

Post a Comment

Send Transactional SMS with API

This post cover how to send transactional SMS using the Alibaba Cloud Short Message Service API. Transactional SMS usually come with One Tim...