Search code examples
c#workflow-foundation

WF 4.5. Using C# expressions with external class references


I am trying to compile Dynamic activity using the new WF 4.5 features - C# expression. It works until I add an external class in the expression.

If the expression contains basic objects it's complied. If I add a reference to an external class it's generates an error "The type or namespace name 'xxxxx' could not be found (are you missing a using directive or an assembly reference?)"

So, the question is how can I reference external classes for C# expressions?

P.S. It works fine with VisualBasic expression type

Thanks

//Compiled without errors
var errorCodeWorkflow = new DynamicActivity
{
    Name = "DynamicActivity",
    Implementation = () => new WriteLine
    {
        Text = new CSharpValue<String>
        {
            ExpressionText = "new Random().Next(1, 101).ToString()"
        }
     }
};

CompileExpressions(errorCodeWorkflow);
WorkflowInvoker.Invoke(errorCodeWorkflow);


//Error 

using System;
using System.Activities;
using System.Activities.Expressions;
using System.Activities.Statements;
using System.Activities.XamlIntegration;
using System.Collections.Generic;
using System.Linq;
using Microsoft.CSharp.Activities;

namespace CSharpExpression 
{
    class Program
    {
        static void Main()
        {
            var errorCodeWorkflow = new DynamicActivity
            {
                Name = "MyScenario.MyDynamicActivity3",
                Properties =
                {
                    new DynamicActivityProperty
                    {
                        Name = "Address",
                        Type = typeof(InArgument<MailAddress>),
                    },
                },
                Implementation = () => new WriteLine
                {
                    Text = new CSharpValue<String>
                    {
                        ExpressionText = "\"MyDynamicActivity \" + Address.DisplayName"
                    }
                }
            };

            CompileExpressions(errorCodeWorkflow);
            WorkflowInvoker.Invoke(errorCodeWorkflow, new Dictionary<String, Object> { { "Address", new MailAddress { DisplayName = "TestDisplayName" } } });
        }

        static void CompileExpressions(DynamicActivity dynamicActivity)
        {
            var activityName = dynamicActivity.Name;
            var activityType = activityName.Split('.').Last() + "_CompiledExpressionRoot";
            var activityNamespace = string.Join(".", activityName.Split('.').Reverse().Skip(1).Reverse());

            var settings = new TextExpressionCompilerSettings
            {
                Activity = dynamicActivity,
                Language = "C#",
                ActivityName = activityType,
                ActivityNamespace = activityNamespace,
                RootNamespace = "CSharpExpression",
                GenerateAsPartialClass = false,
                AlwaysGenerateSource = true,
                ForImplementation = true
            };

            var results = new TextExpressionCompiler(settings).Compile();

            if (results.HasErrors)
            {
                throw new Exception("Compilation failed.");
            }

            var compiledExpressionRoot = Activator.CreateInstance(results.ResultType, new object[] { dynamicActivity }) as ICompiledExpressionRoot;
            CompiledExpressionInvoker.SetCompiledExpressionRootForImplementation(dynamicActivity, compiledExpressionRoot);
        }
    }

    public class MailAddress
    {
        public String Address { get; set; }
        public String DisplayName { get; set; }
    }
}

Solution

  • I ran into an identical issue working with non-dynamic activities and have now checked what is required to get this to work:

    Summary

    1. Add a reference to System.Xaml

    2. Move MailAddress to a different namespace.

    3. Add some information to the Dynamic Activity about where your classes are coming from:

    var impl = new AttachableMemberIdentifier(typeof(TextExpression), "NamespacesForImplementation");
    var namespaces = new List<string> { typeof(MailAddress).Namespace };
    TextExpression.SetReferencesForImplementation(dynamicActivity, new AssemblyReference { Assembly = typeof(MailAddress).Assembly });
    AttachablePropertyServices.SetProperty(dynamicActivity, impl, namespaces);
    

    To get at this answer I needed to dig around in the TextExpressionCompiler source so there might be something a lot more elegant.


    Non-Dynamic Activities

    If you are not using dynamic activities the call to CompileExpressions would change as described here: http://msdn.microsoft.com/en-us/library/jj591618.aspx.

    Adding information to the activity would be modified by removing the "ForImplementation" parts:

    var impl = new AttachableMemberIdentifier(typeof(TextExpression), "Namespaces");
    var namespaces = new List<string> { typeof(MailAddress).Namespace };
    TextExpression.SetReferences(nonDynamicActivity, new AssemblyReference { Assembly = typeof(MailAddress).Assembly });
    AttachablePropertyServices.SetProperty(nonDynamicActivity, impl, namespaces);
    

    Working code

    using System;
    using System.Activities;
    using System.Activities.Expressions;
    using System.Activities.Statements;
    using System.Activities.XamlIntegration;
    using System.Collections.Generic;
    using System.Linq;
    using System.Xaml;
    using ExternalNamespace;
    using Microsoft.CSharp.Activities;
    
    namespace CSharpExpression 
    {
        class Program
        {
            static void Main()
            {
                var errorCodeWorkflow = new DynamicActivity
                {
                    Name = "MyScenario.MyDynamicActivity3",
                    Properties =
                    {
                        new DynamicActivityProperty
                        {
                            Name = "Address",
                            Type = typeof(InArgument<MailAddress>),
                        },
                    },
                    Implementation = () => new WriteLine
                    {
                        Text = new CSharpValue<String>
                        {
                            ExpressionText = "\"MyDynamicActivity \" + Address.DisplayName"
                        }
                    }
                };
    
                var impl = new AttachableMemberIdentifier(typeof(TextExpression), "NamespacesForImplementation");
                var namespaces = new List<string> { typeof(MailAddress).Namespace };
                TextExpression.SetReferencesForImplementation(errorCodeWorkflow, new AssemblyReference { Assembly = typeof(MailAddress).Assembly });
                AttachablePropertyServices.SetProperty(errorCodeWorkflow, impl, namespaces);
    
                CompileExpressions(errorCodeWorkflow);
                WorkflowInvoker.Invoke(errorCodeWorkflow, new Dictionary<String, Object> { { "Address", new MailAddress { DisplayName = "TestDisplayName" } } });
            }
    
            static void CompileExpressions(DynamicActivity dynamicActivity)
            {
                var activityName = dynamicActivity.Name;
                var activityType = activityName.Split('.').Last() + "_CompiledExpressionRoot";
                var activityNamespace = string.Join(".", activityName.Split('.').Reverse().Skip(1).Reverse());
    
                var settings = new TextExpressionCompilerSettings
                {
                    Activity = dynamicActivity,
                    Language = "C#",
                    ActivityName = activityType,
                    ActivityNamespace = activityNamespace,
                    RootNamespace = "CSharpExpression",
                    GenerateAsPartialClass = false,
                    AlwaysGenerateSource = true,
                    ForImplementation = true
                };
    
                var results = new TextExpressionCompiler(settings).Compile();
    
                if (results.HasErrors)
                {
                    throw new Exception("Compilation failed.");
                }
    
                var compiledExpressionRoot = Activator.CreateInstance(results.ResultType, new object[] { dynamicActivity }) as ICompiledExpressionRoot;
                CompiledExpressionInvoker.SetCompiledExpressionRootForImplementation(dynamicActivity, compiledExpressionRoot);
            }
        }
    }
    
    namespace ExternalNamespace
    {
        public class MailAddress
        {
            public String Address { get; set; }
            public String DisplayName { get; set; }
        }
    }