WF - Compile and execute an activity/workflow at runtime

Sometimes you will have a situation where you want to compile (and execute) a workflow at runtime.

This is relatively easy when your workflow is pure .XOML and derives from classes available through the TypeProvider that implement everything it needs. When you want to test an activity that implements its own custom code, this will not work for several reasons.
There are not many examples around at this point in time how to do this. So this should give you a place to start from. 

public Assembly CompileWorkflow(string[] sources,

                                string[] references,

                                string[] libraryPaths)

{

    try

    {

        WorkflowCompiler compiler = new WorkflowCompiler();

        WorkflowCompilerParameters parameters =

                            new WorkflowCompilerParameters();

        parameters.LibraryPaths.AddRange(libraryPaths);

        parameters.ReferencedAssemblies.AddRange(references);

        parameters.OutputAssembly = "MySelfCompiledCustomWorkflow"

                                    + Guid.NewGuid().ToString()
                                    + ".dll";

        parameters.GenerateInMemory = true;

        WorkflowCompilerResults results = compiler.Compile(parameters,

                                                           sources);

        if (!results.Errors.HasErrors)

        {

            return (results.CompiledAssembly);

        }

        else

        {

            return null;

        }

    }

    catch (Exception)

    {

        return null;

    }

}

This will give you the assembly you need.
"sources" are the ".cs" and ".xoml"-source files, "references" the filenames or file locations of the referenced assembly-DLLs and libraryPaths are the names of the directories that are used to look for the referenced assembly-DLLs. Please note that "references" does not need to specify the full path to the DLL, however I recommend giving the full path because otherwise it might happen more easily that you find two identical assembly-filenames in the "libraryPaths" resulting in undefined behaviour.
To execute the assembly you will need to extract the right activity.
I assume you know the type name of the activity. This example tries to find the first activity with a matching name. If no activity name is given, it will still try to find the first activity in the assembly (this code does not try to be elegant): 

public Activity GetFirstActivityWithMatchingName(Assembly assembly,
                                                 string activityName)

{

    object myObject;

    if (String.IsNullOrEmpty(activityName))

    {

        foreach (Type type in assembly.GetTypes())

        {

            {

                myObject = assembly.CreateInstance(type.FullName);

                try

                {

                    Activity ac = (Activity)myObject;

                    return (ac);

                }

                catch (Exception) { }

            }

        }

        return null;

    }

    else

    {

        try

        {

            myObject = assembly.CreateInstance(activityName);

            if (myObject != null)

            {

                Activity ac = (Activity)myObject;

                return (ac);

            }

            else

            {

                foreach (Type type in assembly.GetTypes())

                {

                    if (type.Name == activityName)

                    {

                        myObject =
                          assembly.CreateInstance(type.FullName);

                        try

                        {

                            Activity ac = (Activity)myObject;

                            return (ac);

                        }

                        catch (Exception) { }

                    }

                }

 

            }

        }

        catch (Exception) { }

    }

    return null;

}

To execute the activity, you can use this little piece of code:

public void ExecuteWorkflow(Activity ac,

                Dictionary<string, object> namedArgumentValues)

{

    using (WorkflowRuntime wr = new WorkflowRuntime())

    {

        WorkflowInstance wi = wr.CreateWorkflow(ac.GetType(),

                                                namedArgumentValues);

        AutoResetEvent waitHandle = new AutoResetEvent(false);

        wr.WorkflowCompleted += delegate(object sender,

                                        WorkflowCompletedEventArgs e)

        {

            waitHandle.Set();

        };

        wr.WorkflowTerminated += delegate(object sender,

                                       WorkflowTerminatedEventArgs e)

        {

            Console.WriteLine(e.Exception.ToString());

            waitHandle.Set();

        };

        wi.Start();

        waitHandle.WaitOne();

    }

}


Note that for some reason, it does not seem to work to use the ManualWorkflowScheduler in this specific case. I appreciate feedback about this :)

How to put those parts together - compile, retrieve activity and execute:

public void CompileAndExecuteWF(string[] sources,
                string[] references,

                string[] libraryPaths,
                string activityTypeName,

                Dictionary<string, object> namedArgumentValues)

{

    Assembly assembly=CompileWorkflow(sources, references,
                                      libraryPaths);

    if (assembly != null)

    {

        Activity ac = GetFirstActivityWithMatchingName(assembly,

                          activityTypeName);

        if (ac != null)

        {

            ExecuteWorkflow(ac, namedArgumentValues);

        }

    }

}

The following example-call that assumes that all assemblies needed for compilation and execution are referenced in the compiling project. Replace the helper functions "GetReferencedAssemblyFileNames" and "GetReferencedAssemblyPaths" with your own logic to retrieve the necessary assemblies.
The example will compile two files: "MyActivity.xoml" and "MyActivity.xoml.cs" located in a project named "WFActviityLibrary1" in the same solution.

public void ExampleCompileAndExecuteWF()

{

    String[] sources = new String[] {

               "..\\..\\..\\WFActivityLibrary1\\MyActivity.xoml",

               "..\\..\\..\\WFActivityLibrary1\\MyActivity.xoml.cs"

                            };

    String[] references = GetReferencedAssemblyFileNames();

    String[] libraryPaths = GetReferencedAssemblyPaths();

    String activityTypeName = "MyActivity";

    Dictionary<string, object> namedArgumentValues =

                               new Dictionary<string, object>();

    namedArgumentValues.Add("WorkflowArgument", "test");

    CompileAndExecuteWF(sources, references, libraryPaths,

                      activityTypeName, namedArgumentValues);

}

 

private string[] GetReferencedAssemblyPaths()

{

    List<String> list = new List<string>();

    Assembly thisAssembly;

    thisAssembly = Assembly.GetAssembly(this.GetType());

    foreach (AssemblyName assemblyName

            in thisAssembly.GetReferencedAssemblies())

    {

        Assembly assembly = Assembly.Load(assemblyName);

        list.Add(Path.GetDirectoryName(assembly.Location));

    }

    return (list.ToArray());

}

 

private string[] GetReferencedAssemblyFileNames()

{

    List<String> list = new List<string>();

    Assembly thisAssembly;

    thisAssembly = Assembly.GetAssembly(this.GetType());

    foreach (AssemblyName assemblyName

            in thisAssembly.GetReferencedAssemblies())

    {

        Assembly assembly = Assembly.Load(assemblyName);

        list.Add(assembly.Location);

        // list.Add(Path.GetFileName( assembly.Location));

        /* If you use

        * the "Path.GetFileName(assembly.Location)" version

        * it will require to set appropriate paths

        * and might lead to name-conflicts if assemblies

        * with identical filenames exist in multiple

        * directories

        */

    }

    return (list.ToArray());

}

Have fun compiling your own custom activities/workflows :)


Posted Aug 04 2007, 01:47 PM by Andreas Erben

Comments

tgrk wrote re: WF - Compile and execute an activity/workflow at runtime
on 08-15-2007 11:12

Thanks Andreas, this was exacly what I am doing these days. It helped me a lot.  There is only a few resources for Xoml compilation. Great post!

DL wrote re: WF - Compile and execute an activity/workflow at runtime
on 03-31-2008 11:23

How about compiling a 3rd file... I also need rules for my workflow... so, i have 3 files (xoml, xoml.cs, rules)... what about the rules file? ... i guess it cant be as simple, just to add it to "String[] sources" :)

elyes85 wrote re: WF - Compile and execute an activity/workflow at runtime
on 07-19-2008 17:12

thanks for your great article Andreas, I would like to know how to assign to the compiled assembly the Culture and Version and PublicKeyToken ....with the workflow designer, when I have only xoml file ????

Please I need your help !

Andreas Erben wrote re: WF - Compile and execute an activity/workflow at runtime
on 07-19-2008 18:43

have you tried adding a .cs file with your AssemblyInfo to the sources to be compiled?

elyes85 wrote re: WF - Compile and execute an activity/workflow at runtime
on 07-23-2008 10:56

Thanks I did it !!

I have a problem with rule file ????

How can I do ???

                          Thank you !

Andreas Erben wrote re: WF - Compile and execute an activity/workflow at runtime
on 07-23-2008 14:55

Hi to Algeria,

I am not sure what the problems are... but

did you try to set the CompilerOptions on the WorkflowCompilerParameters object?

If you have a namespace for your project "DummyName" and your activity is called "OnTheFlyActivity" and your file is "MyCoolRules.rules", then you would write

it in the format:

/resource:FileName[,Name]

parameters.CompilerOptions = "/resource:MyCoolRules.rules,DummyName.OnTheFlyActivity.rules"

Hope that helped.

elyes85 wrote re: WF - Compile and execute an activity/workflow at runtime
on 07-23-2008 16:30

Oh !! this does not function !!!

it can't validate the loop activity "while" I think some thing miss !

it displays this error :

failure to validate "whileActivity1": impossible to find "Condition1"  ??

I didn't understand what happen, anyway, I executed all the instaructions that  you provided , Thanks for your support again!

tell me what's the problem  ?

Thanks !

Andreas Erben wrote re: WF - Compile and execute an activity/workflow at runtime
on 07-23-2008 16:55

elyes85: Please provide an email address so we can exchange this offline. I need to see your code to reproduce the problem. After that we can talk about the solution.

You can post your address here, it will not be visible to the public because I need to "publish" the comment first and I will not do this with your email address.

elyes85 wrote re: WF - Compile and execute an activity/workflow at runtime
on 07-23-2008 17:20

Hi Andreas Erben !!!

Great greetings for you !!

Finally ! I did it !!!

You know what was the problem ?

I am developing workflow designer with nop code so I am generating only xoml files, so when I tested your command the first time, I just missed the right namespace so it failed .

But I get finally !

Thanks for you !!!

Andreas Erben wrote re: WF - Compile and execute an activity/workflow at runtime
on 07-23-2008 17:35

elyes85: feel free to find me in xing//openbc or linkedin. you are working on interesting stuff :)

developers.de is a .Net Community Blog powered by daenet GmbH.