Редагувати

Поділитися через


C# Expressions

Starting with .NET Framework 4.5, C# expressions are supported in Windows Workflow Foundation (WF). New C# workflow projects created in Visual Studio 2012 that target .NET Framework 4.5 use C# expressions, and Visual Basic workflow projects use Visual Basic expressions. Existing .NET Framework 4 workflow projects that use Visual Basic expressions can be migrated to .NET Framework 4.6.1 regardless of the project language and are supported. This topic provides an overview of C# expressions in WF.

Using C# expressions in workflows

Using C# expressions in the Workflow Designer

Starting with .NET Framework 4.5, C# expressions are supported in Windows Workflow Foundation (WF). C# workflow projects created in Visual Studio 2012 that target .NET Framework 4.5 use C# expressions, while Visual Basic workflow projects use Visual Basic expressions. To specify the desired C# expression, type it into the box labeled Enter a C# expression. This label is displayed in the properties window when the activity is selected in the designer, or on the activity in the workflow designer. In the following example, two WriteLine activities are contained within a Sequence inside a NoPersistScope.

Screenshot that shows an automatically created sequence activity.

Note

C# expressions are supported only in Visual Studio, and are not supported in the re-hosted workflow designer. For more information about new WF45 features supported in the re-hosted designer, see Support for New Workflow Foundation 4.5 Features in the Rehosted Workflow Designer.

Backwards compatibility

Visual Basic expressions in existing .NET Framework 4 C# workflow projects that have been migrated to .NET Framework 4.6.1 are supported. When the Visual Basic expressions are viewed in the workflow designer, the text of the existing Visual Basic expression is replaced with Value was set in XAML, unless the Visual Basic expression is valid C# syntax. If the Visual Basic expression is valid C# syntax, then the expression is displayed. To update the Visual Basic expressions to C#, you can edit them in the workflow designer and specify the equivalent C# expression. It is not required to update the Visual Basic expressions to C#, but once the expressions are updated in the workflow designer they are converted to C# and may not be reverted to Visual Basic.

Using C# expressions in code workflows

C# expressions are supported in .NET Framework 4.6.1 code based workflows, but before the workflow can be invoked the C# expressions must be compiled using TextExpressionCompiler.Compile. Workflow authors can use CSharpValue to represent the r-value of an expression, and CSharpReference to represent the l-value of an expression. In the following example, a workflow is created with an Assign activity and a WriteLine activity contained in a Sequence activity. A CSharpReference is specified for the To argument of the Assign, and represents the l-value of the expression. A CSharpValue is specified for the Value argument of the Assign, and for the Text argument of the WriteLine, and represents the r-value for those two expressions.

Variable<int> n = new Variable<int>
{
    Name = "n"
};

Activity wf = new Sequence
{
    Variables = { n },
    Activities =
    {
        new Assign<int>
        {
            To = new CSharpReference<int>("n"),
            Value = new CSharpValue<int>("new Random().Next(1, 101)")
        },
        new WriteLine
        {
            Text = new CSharpValue<string>("\"The number is \" + n")
        }
    }
};

CompileExpressions(wf);

WorkflowInvoker.Invoke(wf);

After the workflow is constructed, the C# expressions are compiled by calling the CompileExpressions helper method and then the workflow is invoked. The following example is the CompileExpressions method.

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);
}

Note

If the C# expressions are not compiled, a NotSupportedException is thrown when the workflow is invoked with a message similar to the following: Expression Activity type 'CSharpValue1' requires compilation in order to run. Please ensure that the workflow has been compiled.`

If your custom code based workflow uses DynamicActivity, then some changes to the CompileExpressions method are required, as demonstrated in the following code example.

static void CompileExpressions(DynamicActivity dynamicActivity)
{
    // activityName is the Namespace.Type of the activity that contains the
    // C# expressions. For Dynamic Activities this can be retrieved using the
    // name property , which must be in the form Namespace.Type.
    string activityName = dynamicActivity.Name;

    // 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 = dynamicActivity,
        Language = "C#",
        ActivityName = activityType,
        ActivityNamespace = activityNamespace,
        RootNamespace = null,
        GenerateAsPartialClass = false,
        AlwaysGenerateSource = true,
        ForImplementation = true
    };

    // 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[] { dynamicActivity }) as ICompiledExpressionRoot;

    // Attach it to the activity.
    CompiledExpressionInvoker.SetCompiledExpressionRootForImplementation(
        dynamicActivity, compiledExpressionRoot);
}

There are several differences in the CompileExpressions overload that compiles the C# expressions in a dynamic activity.

  • The parameter to CompileExpressions is a DynamicActivity.

  • The type name and namespace are retrieved using the DynamicActivity.Name property.

  • TextExpressionCompilerSettings.ForImplementation is set to true.

  • CompiledExpressionInvoker.SetCompiledExpressionRootForImplementation is called instead of CompiledExpressionInvoker.SetCompiledExpressionRoot.

For more information about working with expressions in code, see Authoring Workflows, Activities, and Expressions Using Imperative Code.

Using C# expressions in XAML workflows

C# expressions are supported in XAML workflows. Compiled XAML workflows are compiled into a type, and loose XAML workflows are loaded by the runtime and compiled into an activity tree when the workflow is executed.

Compiled Xaml

C# expressions are supported in compiled XAML workflows that are compiled to a type as part of a C# workflow project that targets .NET Framework 4.6.1. Compiled XAML is the default type of workflow authoring in Visual Studio, and C# workflow projects created in Visual Studio that target .NET Framework 4.6.1 use C# expressions.

Loose Xaml

C# expressions are supported in loose XAML workflows. The workflow host program that loads and invokes the loose XAML workflow must target .NET Framework 4.6.1, and CompileExpressions must be set to true (the default is false). To set CompileExpressions to true, create an ActivityXamlServicesSettings instance with its CompileExpressions property set to true, and pass it as a parameter to ActivityXamlServices.Load. If CompileExpressions Is not set to true, a NotSupportedException will be thrown with a message similar to the following: Expression Activity type 'CSharpValue1' requires compilation in order to run. Please ensure that the workflow has been compiled.`

ActivityXamlServicesSettings settings = new ActivityXamlServicesSettings
{
    CompileExpressions = true
};

DynamicActivity<int> wf = ActivityXamlServices.Load(new StringReader(serializedAB), settings) as DynamicActivity<int>;

For more information about working with XAML workflows, see Serializing Workflows and Activities to and from XAML.

Using C# expressions in XAMLX workflow services

C# expressions are supported in XAMLX workflow services. When a workflow service is hosted in IIS or WAS then no additional steps are required, but if the XAML workflow service is self-hosted, then the C# expressions must be compiled. To compile the C# expressions in a self-hosted XAMLX workflow service, first load the XAMLX file into a WorkflowService, and then pass the Body of the WorkflowService to the CompileExpressions method described in the previous Using C# expressions in code workflows section. In the following example, a XAMLX workflow service is loaded, the C# expressions are compiled, and then the workflow service is opened and waits for requests.

// Load the XAMLX workflow service.
WorkflowService workflow1 =
    (WorkflowService)XamlServices.Load(xamlxPath);

// Compile the C# expressions in the workflow by passing the Body to CompileExpressions.
CompileExpressions(workflow1.Body);

// Initialize the WorkflowServiceHost.
var host = new WorkflowServiceHost(workflow1, new Uri("http://localhost:8293/Service1.xamlx"));

// Enable Metadata publishing/
ServiceMetadataBehavior smb = new ServiceMetadataBehavior();
smb.HttpGetEnabled = true;
smb.MetadataExporter.PolicyVersion = PolicyVersion.Policy15;
host.Description.Behaviors.Add(smb);

// Open the WorkflowServiceHost and wait for requests.
host.Open();
Console.WriteLine("Press enter to quit");
Console.ReadLine();

If the C# expressions are not compiled, the Open operation succeeds but the workflow will fail when it is invoked. The following CompileExpressions method is the same as the method from the previous Using C# expressions in code workflows section.

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);
}