none
How do you host a xamlx file in a WorkflowServiceHost in .NET 4.5?

    Question

  • How does one host a .NET 4.5 XAMLX workflow service in a WorkflowServiceHost?  It doesn't seem to work.

    XamlServices.Load("...xamlx") won't work - it doesn't compile the C# expressions and you get this error:

    9: WorkflowInstance "Sequential Service" Unhandled Exception Source "ReceiveRequ
    est" Exception <System.NotSupportedException: Expression Activity type 'CSharpRe
    ference`1' requires compilation in order to run.  Please ensure that the workflo
    w has been compiled.

    OK, so I find ActivityXamlServices.Load(...,ActivityXamlServicesSettings { CompileExpressions = true}).  Doesn't work - it will only load activities, not a workflowservice instance.

    Unhandled Exception: System.ArgumentException: ActivityXamlServices.Load only su
    pports Activity-based types.  Invalid type (System.ServiceModel.Activities.Workf
    lowService) was provided.

    My basic use case is to try unit testing a workflow, and Microsoft.Activities.UnitTesting doesn't seem to support .NET 4.5 (probably same thing broken as described above).  So I'm a bit lost unless anyone has any ideas.

    Wednesday, May 16, 2012 7:09 AM

Answers

  • TestActivity is a xaml based activity created with the designer. However it gets compiled when you build the project since it has MSBuild:Compile as its custom tool property.

    To convert the xaml file into an activity, create a new xaml based activity (right click on the project -> add -> Activity). Open the xamlx into the designer, select all, copy, go to the new activity and paste.

    Regards.



    Thursday, May 17, 2012 10:48 AM

All replies

  • Hi,

    I have a console application with this code and just works:

    static void InspectWorkflow()
    {
        object serviceImplementation = XamlServices.Load(@"....\ExpenseNotesWorkflow.xamlx");
        WorkflowService service = serviceImplementation as WorkflowService;
        Activity activity = service.Body;
        InspectActivity(activity, 0);
        //Console.ReadLine();
     
    }
    
    static void InspectActivity(Activity root, int indent)
    {
        // Inspect the activity tree using WorkflowInspectionServices.
        IEnumerator<Activity> activities =
            WorkflowInspectionServices.GetActivities(root).GetEnumerator();
    
        if (root.DisplayName.StartsWith("EnvironmentLocationReference"))
        {
            return;
        }
        Console.WriteLine("{0}{1}{2}", new string(' ', indent), root.DisplayName, "\t" + root.Id);
    
        while (activities.MoveNext())
        {
            InspectActivity(activities.Current, indent + 2);
        }
    }
    
    
    static void Main(string[] args)
    {
    
        InspectWorkflow();
    }
    The workflow is a 4.5 workflow service and has lots of C# expressions.

    Wednesday, May 16, 2012 7:54 AM
  • Well, the problem is when it tries to execute.  It loads fine and I'm sure I can traverse it fine.  In fact it even runs the first activity which is just a writeline.  But when it gets to the actual instantiating ReceiveActivity (I tested with just the default generated workflow service) I get:

    8: Fault source [12] "ReceiveRequest"
    9: WorkflowInstance "Sequential Service" Unhandled Exception Source "ReceiveRequ
    est" Exception <System.NotSupportedException: Expression Activity type 'CSharpRe
    ference`1' requires compilation in order to run.  Please ensure that the workflo
    w has been compiled.

    Wednesday, May 16, 2012 8:19 AM
  • Hi,

    Please, could you show me the code you use to run the workflow?

    I think one workaround could be to put all the logic in a separate activity and insert this activity in the xamlx. The separate activity is precompiled so it should work.

    Regards.

    Wednesday, May 16, 2012 8:40 AM
  • I tried what I suggested and failed. But this code worked. TestActivity has C# expresions and a receive and send reply:

    static void RunWorkflow()
    {
        Activity activity = new TestActivity();
        var service = new WorkflowService();
        service.Name = "Test";
        service.Body = activity;
        WorkflowServiceHost serviceHost = new WorkflowServiceHost(service, new Uri("http://localhost:8080/Test"));
        serviceHost.Open();
    
        var client = new ServiceReference.WorkflowClient(new BasicHttpBinding(), new EndpointAddress(new Uri("http://localhost:8080/Test")));
        client.Start();
        Console.WriteLine("Workflow Started");
    
        Console.WriteLine("Press enter to end..");
        Console.ReadLine();
    
    }

    Wednesday, May 16, 2012 9:59 AM
  • Thanks for looking at this - so TestActivity is a code-based workflow, right?

    How would I convert a Xamlx file into a working activity?

    Wednesday, May 16, 2012 4:31 PM
  • I guess my Google-fu wasn't good enough, a co worker found this as an open issue on Connect:

    https://connect.microsoft.com/VisualStudio/feedback/details/741537/cannot-self-host-a-workflow-service-using-c-expressions


    Wednesday, May 16, 2012 5:59 PM
  • TestActivity is a xaml based activity created with the designer. However it gets compiled when you build the project since it has MSBuild:Compile as its custom tool property.

    To convert the xaml file into an activity, create a new xaml based activity (right click on the project -> add -> Activity). Open the xamlx into the designer, select all, copy, go to the new activity and paste.

    Regards.



    Thursday, May 17, 2012 10:48 AM
  • Thanks Jesús López. This solution worked for me to use c# expressions in workflow 4.5(using .xaml). But, I did not want to loose the versioning support that comes with .xamlx. So, I went on searching for solution. Then, I found a way to use .xamlx(with C# expressions) itself using custom hosting. This can be done in 3 simple steps explained below.

    1. Customized Service Host class:

    Note that CompileExpressions method implementation is from MSDN site http://msdn.microsoft.com/en-us/library/jj591618.aspx

    using System;
    using System.Activities;
    using System.Activities.Expressions;
    using System.Activities.XamlIntegration;
    using System.Linq;
    using System.ServiceModel.Activities.Activation;
    namespace HostUtil
    {
        public class CompiledXamlxHost : WorkflowServiceHostFactory
        {
            protected override System.ServiceModel.Activities.WorkflowServiceHost CreateWorkflowServiceHost(System.ServiceModel.Activities.WorkflowService service, Uri[] baseAddresses)
            {
                // Compile the C# expressions in the workflow by passing the Body to CompileExpressions.
                CompileExpressions(service.Body);
                return base.CreateWorkflowServiceHost(service, baseAddresses);
            }
            static void CompileExpressions(Activity activity)
            {
                // activityName is the Namespace.Type of the activity that contains the
                // C# expressions.
                string activityName = activity.GetType().ToString();
                // Split activityName into Namespace and Type.Append _CompiledExpressionRoot to the type name
                // to represent the new type that represents the compiled expressions.
                // Take everything after the last . for the type name.
                string activityType = activityName.Split('.').Last() + "_CompiledExpressionRoot";
                // Take everything before the last . for the namespace.
                string activityNamespace = string.Join(".", activityName.Split('.').Reverse().Skip(1).Reverse());
                // Create a TextExpressionCompilerSettings.
                TextExpressionCompilerSettings settings = new TextExpressionCompilerSettings
                {
                    Activity = activity,
                    Language = "C#",
                    ActivityName = activityType,
                    ActivityNamespace = activityNamespace,
                    RootNamespace = null,
                    GenerateAsPartialClass = false,
                    AlwaysGenerateSource = true,
                    ForImplementation = false
                };
                // Compile the C# expression.
                TextExpressionCompilerResults results =
                    new TextExpressionCompiler(settings).Compile();
                // Any compilation errors are contained in the CompilerMessages.
                if (results.HasErrors)
                {
                    throw new Exception("Compilation failed.");
                }
                // Create an instance of the new compiled expression type.
                ICompiledExpressionRoot compiledExpressionRoot =
                    Activator.CreateInstance(results.ResultType,
                        new object[] { activity }) as ICompiledExpressionRoot;
                // Attach it to the activity.
                CompiledExpressionInvoker.SetCompiledExpressionRoot(
                    activity, compiledExpressionRoot);
            }
        }
    }

    2. Web.config changes

    Below entry should be added in web.config under system.serviceModel/serviceHostingEnvironment/serviceActivations node for ExampleWF.xamlx(for example) file.

    <add relativeAddress="ExampleWF.xamlx" service="ExampleWF.xamlx" factory="HostUtil.CompiledXamlxHost" />

    3. In the workflow designer, System.ServiceModel should be added to Imported namespace.

    That is it. When you build and run the project now, that exception should have been vanished.

    • Edited by Srinivasan R Friday, December 28, 2012 6:18 PM formatting
    • Proposed as answer by Srinivasan R Friday, December 28, 2012 6:23 PM
    Friday, December 28, 2012 6:15 PM