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.
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.
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.
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.
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.
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)),
};
}
}
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);
}
Hi,
ReplyDeleteI 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.
Amendment to above .... The root Activity displays buts children are not displayed.
ReplyDeleteI have the same problem...
ReplyDeleteI figured out the solution to this and you need to do the following.
DeleteYou need to add the following code to the constructor of your Window:
var designerMetadata = new DesignerMetadata();
designerMetadata.Register();
Hope this helps.
PERFECT !!! : )
ReplyDelete