Saturday, November 17, 2012

How to Load XAML into WorkflowDesigner from Assembly Embedded Resource?

As promised, from the previous post. I would like to share how to load XAML from a compiled Workflow  dll's embedded resource. Thanks to Serena, she mentioned that after we have created our workflow and compiled it into dll, the XAML will be stored as a embedded resource. Therefore, I use reflector to checkout my compiled dll. And, yes! I see XAML there but do not be happy so soon, the XAML cannot be used directly by the WorkflowDesigner. We need to do some hacking.

Here are the way to get the XAML from the assembly embedded resource and load it into the WorkflowDesigner.


        public void LoadWorkflowDesigner()
        {
            this.WorkflowDesigner = new WorkflowDesigner();
            this.DebuggerService = this.WorkflowDesigner.DebugManagerView;
            this.WorkflowActivity = new LeaveWorkflowService();

            //Original Way:
            //Load physical workflow xaml file into the designer
            //this.WorkflowDesigner.Load("LeaveWorkflowService.xaml");

            //New Way:
            //Load workflow xaml file from assembly resource
            var resourceNames = this.WorkflowActivity.GetType().Assembly.GetManifestResourceNames();
            Stream workflowXamlStream = this.WorkflowActivity.GetType().Assembly.GetManifestResourceStream(resourceNames[0]);

            using (var reader = new StreamReader(workflowXamlStream))
            {
                var builder = new StringBuilder(reader.ReadToEnd());

                //Hack the XAML from the resource to make it able to load properly with Workflow Designer
                //Without the hack, you will encounter error
                XmlDocument xml = new XmlDocument();
                xml.LoadXml(builder.ToString());
           
                //Replace the first XML node name with Activity
                builder.Replace(xml.DocumentElement.Name, "Activity");

                this.WorkflowDesigner.Text = builder.ToString();
                this.WorkflowDesigner.Load();

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

                workflowXamlStream.Dispose();
            }
        }



If you check the string builder value, the XAML is different from what you see in your workflow physical .xaml file. The hack is try to change the first root element to mimic the same root element in the physical .xaml file.
If you load the XAML directly from the embedded resource, you will get the following error:

Could not find member 'Implementation' in type '<your workflow assembly name>'.

My guess for this error is because we do not create the Activity with code. Something like below where you can find it in the Workflow Hello World sample project. Every Activity has a property call "Implementation". If we write code to construct workflow, we add workflow controls into the Implementation property.

    public sealed class AppendString : Activity<string>
    {
        // Input argument.
        [RequiredArgument]
        public InArgument<string> Name { get; set; }

        public AppendString()
        {
            // Define the implementation of this activity.
            this.Implementation = () => new Assign<string>
            {
                Value = new LambdaValue<string>(ctx => Name.Get(ctx) + " says hello world"),
                To = new LambdaReference<string>(ctx => Result.Get(ctx)),
            };
        }
    }

Instead, we normally draw the model in Visual Studio and it get generated into XAML automatically. When the WorkflowDesigner try to load the XAML, the XAML does not contain the Implementation property information after being deserialized, hence we get the error.
Please leave me a comment if my guess is wrong.



A little more of sharing. The WorkflowDesigner.Load() method actually support 3 different arguments.

1. Load()
Render the workflow base on the value in the WorkflowDesigner.Text property.

2. Load(object instance)
Render the workflow with the pass in Activity type object. The workflow model that we drew and compiled into assembly is considered as an custom Activity object. Below screenshot is how it look like if we load the Activity with the following code. But, we do not want it this way because we will get error when we run the visual tracking code.

        public void LoadWorkflowDesigner()
        {
            this.WorkflowDesigner = new WorkflowDesigner();
            this.DebuggerService = this.WorkflowDesigner.DebugManagerView;
            this.WorkflowActivity = new LeaveWorkflowService();

            this.WorkflowDesigner.Load(this.WorkflowActivity);

            this.WorkflowGrid.Children.Add(this.WorkflowDesigner.View);
        }




However, I tried to load Hello World sample workflow which is created with code, it get loaded correctly and showing the activity blocks nicely. Below are the code from the sample project.


        private Activity CreateWF()
        {
            Variable<string> message = new Variable<string>();

            return new Sequence()
            {
                Variables = { message },
                Activities =
                {
                    new AppendString()
                    {
                        Name = ".NET WF",
                        Result = message
                    },
                    new PrependString()
                    {
                        Name = message,
                        Result = message,
                    },
                    new WriteLine()
                    {
                        Text = message
                    }
                }
            };
        }


And, here is the screenshot of the loaded Activity return from the above code.




3. Load(string filename)
Render the workflow with a physical XAML file. It works perfectly, but we need to ensure the .xaml physical always exists in the bin folder and sitting inside the same folder with the application, or at least specify the file path correctly.

        public void LoadWorkflowDesigner()
        {
            this.WorkflowDesigner = new WorkflowDesigner();
            this.DebuggerService = this.WorkflowDesigner.DebugManagerView;
            this.WorkflowActivity = new LeaveWorkflowService();

            this.WorkflowDesigner.Load("LeaveWorkflowService.xaml");


            this.WorkflowGrid.Children.Add(this.WorkflowDesigner.View);
        }






5 comments:

  1. Hi,

    I have been following your example and unable to display Activities and its children in the rehosted Workflow designer.

    Could you please share your solution ?

    Many Thanks.

    ReplyDelete
  2. Amendment to above .... The root Activity displays buts children are not displayed.

    ReplyDelete
  3. Replies
    1. I figured out the solution to this and you need to do the following.

      You need to add the following code to the constructor of your Window:
      var designerMetadata = new DesignerMetadata();
      designerMetadata.Register();

      Hope this helps.

      Delete

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...