Compartir vía


Crear flujos de trabajo, actividades y expresiones mediante código imperativo

Una definición de flujo de trabajo es un árbol de objetos de actividad configurados. Este árbol de actividades se puede definir de muchas maneras, incluido XAML editado a mano o el uso del Diseñador de flujo de trabajo para generar XAML. El uso de XAML, sin embargo, no es un requisito. También se pueden crear definiciones de flujo de trabajo mediante programación. Este tema proporciona información general sobre cómo crear definiciones de flujo de trabajo, actividades y expresiones mediante código. Para obtener ejemplos de cómo trabajar con flujos de trabajo XAML mediante código, consulte Serializar flujos de trabajo y actividades hacia y desde XAML.

Crear definiciones de flujo de trabajo

Se puede crear una definición de flujo de trabajo creando instancias de un tipo de actividad y configurando las propiedades del objeto de la actividad. En el caso de actividades que no contengan actividades secundarias, puede llevarse a cabo con algunas líneas de código.

Activity wf = new WriteLine
{
    Text = "Hello World."
};

WorkflowInvoker.Invoke(wf);

Nota

En los ejemplos de este tema se usa WorkflowInvoker para ejecutar los flujos de trabajo de ejemplo. Para obtener más información sobre cómo invocar flujos de trabajo, pasar argumentos y las diferentes opciones de hospedaje disponibles, consulte Usar WorkflowInvoker y WorkflowApplication.

En este ejemplo se crea un flujo de trabajo que consta de una única actividad WriteLine. Se establece el argumento WriteLine de la actividad Text y se invoca el flujo de trabajo. Si una actividad contiene actividades secundarias, el método de construcción es similar. En el siguiente ejemplo se usa una actividad Sequence que contiene dos actividades WriteLine.

Activity wf = new Sequence
{
    Activities =
    {
        new WriteLine
        {
            Text = "Hello"
        },
        new WriteLine
        {
            Text = "World."
        }
    }
};

WorkflowInvoker.Invoke(wf);

Usar inicializadores de objeto

Los ejemplos de este tema usan la sintaxis de inicialización del objeto. Esta sintaxis puede ser una forma útil de crear definiciones de flujo de trabajo en el código porque proporciona una vista jerárquica de las actividades en el flujo de trabajo y muestra la relación entre las actividades. No hay ningún requisito para usar la sintaxis de inicialización de objeto al crear los flujos de trabajo mediante programación. El ejemplo siguiente es equivalente en su funcionamiento al ejemplo anterior.

WriteLine hello = new WriteLine();
hello.Text = "Hello";

WriteLine world = new WriteLine();
world.Text = "World";

Sequence wf = new Sequence();
wf.Activities.Add(hello);
wf.Activities.Add(world);

WorkflowInvoker.Invoke(wf);

Para obtener más información sobre los inicializadores de objeto, consulte Procedimiento para inicializar objetos usando un inicializador de objeto (Guía de programación de C#) y Cómo: Declarar un objeto usando un inicializador de objeto (Visual Basic).

Trabajar con variables, valores literales y expresiones

Al crear una definición de flujo de trabajo mediante código, tenga en cuenta qué código se ejecuta como parte de la creación de la definición de flujo de trabajo y qué código se ejecuta como parte de la ejecución de una instancia de ese flujo de trabajo. Por ejemplo, el siguiente flujo de trabajo está pensado para generar un número aleatorio y escribirlo en la consola.

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

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

Cuando se ejecuta este código de definición de flujo de trabajo, se realiza la llamada a Random.Next y el resultado se almacena en la definición de flujo de trabajo como un valor literal. Se pueden invocar muchas instancias de este flujo de trabajo y todas mostrarían el mismo número. Para que la generación de números aleatorios se produzcan durante la ejecución del flujo de trabajo, se debe usar una expresión que se evalúa con caja ejecución del flujo de trabajo. En el ejemplo siguiente, se usa una expresión de Visual Basic con VisualBasicValue<TResult>.

new Assign<int>
{
    To = n,
    Value = new VisualBasicValue<int>("New Random().Next(1, 101)")
}

La expresión del ejemplo anterior también se puede implementar mediante CSharpValue<TResult> y una expresión de C#.

new Assign<int>  
{  
    To = n,  
    Value = new CSharpValue<int>("new Random().Next(1, 101)")  
}  

Es necesario compilar las expresiones de C# antes de que se invoque el flujo de trabajo que las contiene. Si las expresiones de C# no se compilan, se produce una excepción NotSupportedException cuando se invoca el flujo de trabajo con un mensaje similar al siguiente: Expression Activity type 'CSharpValue`1' requires compilation in order to run. Please ensure that the workflow has been compiled. En la mayoría de los escenarios en los que se involucran flujos de trabajo creados en Visual Studio, la expresiones de C# se compilan automáticamente, pero en algunos escenarios, como los flujos de trabajo de código, es necesario compilar las expresiones de C# manualmente. Para obtener un ejemplo de cómo compilar expresiones de C#, consulte la sección Usar expresiones de C# en flujos de trabajo de código en el tema Expresiones de C#.

VisualBasicValue<TResult> representa una expresión en la sintaxis de Visual Basic que se puede usar como valor r en una expresión y CSharpValue<TResult> representa una expresión en la sintaxis de C# que se puede usar como valor r en una expresión. Se evalúan estas expresiones cada vez que se ejecuta la actividad que contienen. El resultado de la expresión se asigna a la variable de flujo de trabajo n. La actividad siguiente usa estos resultados en el flujo de trabajo. Para tener acceso al valor de la variable de flujo de trabajo n en tiempo de ejecución, se necesita ActivityContext. Se puede tener acceso a esto con la siguiente expresión lambda.

Nota

Observe que ambos códigos son ejemplos que usan C# como lenguaje de programación, pero uno emplea VisualBasicValue<TResult> y el otro CSharpValue<TResult>. VisualBasicValue<TResult> y CSharpValue<TResult> se pueden usar en los proyectos de Visual Basic y C#. De forma predeterminada, las expresiones creadas en el diseñador de flujo de trabajo coinciden con el lenguaje del proyecto de hospedaje. Al crear flujos de trabajo en código, el lenguaje deseado está a discreción del autor del flujo de trabajo.

En estos ejemplos, el resultado de la expresión se asigna a la variable de flujo de trabajo n. La actividad siguiente usa estos resultados en el flujo de trabajo. Para tener acceso al valor de la variable de flujo de trabajo n en tiempo de ejecución, se necesita ActivityContext. Se puede tener acceso a esto con la siguiente expresión lambda.

new WriteLine
{
    Text = new InArgument<string>((env) => "The number is " + n.Get(env))
}

Para obtener más información sobre las expresiones lambda, consulte Expresiones lambda (referencia de C#) o Lambda (expresiones) (Visual Basic).

Las expresiones lambda no son serializables para el formato XAML. Si se intenta serializar un flujo de trabajo con expresiones lambda, se produce LambdaSerializationException con el mensaje siguiente: “Este flujo de trabajo contiene expresiones lambda especificadas en código. Estas expresiones no se pueden serializar mediante XAML. Para que el flujo de trabajo se pueda serializar mediante XAML, use VisualBasicValue/VisualBasicReference o ExpressionServices.Convert(lambda). Esto convertirá las expresiones lambda en actividades de expresión". Para que esta expresión sea compatible con XAML, use ExpressionServices y Convert, como se muestra en el ejemplo siguiente.

new WriteLine
{
    //Text = new InArgument<string>((env) => "The number is " + n.Get(env))
    Text = ExpressionServices.Convert((env) => "The number is " + n.Get(env))
}

También se puede utilizar VisualBasicValue<TResult>. Observe que no se necesita ninguna expresión lambda al usar una expresión de Visual Basic.

new WriteLine
{
    //Text = new InArgument<string>((env) => "The number is " + n.Get(env))
    //Text = ExpressionServices.Convert((env) => "The number is " + n.Get(env))
    Text = new VisualBasicValue<string>("\"The number is \" + n.ToString()")
}

En tiempo de ejecución, las expresiones de Visual Basic se compilan en expresiones LINQ. Ambos ejemplos anteriores son serializables en XAML, pero si el XAML serializado está diseñado para que se vea y edite en el diseñador de flujo de trabajo, use VisualBasicValue<TResult> para las expresiones. Los flujos de trabajo serializados que usan ExpressionServices.Convert se pueden abrir en el diseñador, pero el valor de la expresión estará en blanco. Para obtener más información sobre cómo trabajar con flujos de trabajo serializados en XAML, consulte Serializar flujos de trabajo y actividades hacia y desde XAML.

Expresiones literales y tipos de referencia

Las expresiones literales se representan en flujos de trabajo mediante la actividad Literal<T>. Las siguientes actividades WriteLine tienen una funcionalidad equivalente.

new WriteLine  
{  
    Text = "Hello World."  
},  
new WriteLine  
{  
    Text = new Literal<string>("Hello World.")  
}  

No se puede inicializar una expresión literal con cualquier tipo de referencia excepto String. En el ejemplo siguiente, la propiedad Assign de una actividad Value se inicializa con una expresión literal mediante List<string>.

new Assign  
{  
    To = new OutArgument<List<string>>(items),  
    Value = new InArgument<List<string>>(new List<string>())  
},  

Cuando el flujo de trabajo que contiene esta actividad se valida, se devuelve el error de validación siguiente: "El literal solo admite tipos de valor y el tipo inmutable System.String. El tipo System.Collections.Generic.List`1[System.String] no se puede usar como literal". Si se invoca el flujo de trabajo, se produce una excepción InvalidWorkflowException que contiene el texto del error de validación. Esto es un error de validación porque al crear una expresión literal con un tipo de referencia no se crea una nueva instancia del tipo de referencia para cada instancia del flujo de trabajo. Para solucionarlo, reemplace la expresión literal con una que cree y devuelva una nueva instancia de tipo de referencia.

new Assign  
{  
    To = new OutArgument<List<string>>(items),  
    Value = new InArgument<List<string>>(new VisualBasicValue<List<string>>("New List(Of String)"))  
},  

Para obtener más información sobre las expresiones, consulte Expresiones.

Invocar métodos en objetos mediante expresiones y la actividad InvokeMethod

La actividad InvokeMethod<TResult> se puede usar para invocar métodos estáticos y de instancia de clases de .NET Framework. En un ejemplo anterior de este tema, se generaba un número aleatorio mediante la clase Random.

new Assign<int>
{
    To = n,
    Value = new VisualBasicValue<int>("New Random().Next(1, 101)")
}

La actividad InvokeMethod<TResult> podría haberse usado también para llamar al método Next de la clase Random.

new InvokeMethod<int>  
{  
    TargetObject = new InArgument<Random>(new VisualBasicValue<Random>("New Random()")),  
    MethodName = "Next",  
    Parameters =
    {  
        new InArgument<int>(1),  
        new InArgument<int>(101)  
    },  
    Result = n  
}  

Debido a que Next no es un método estático, se proporciona una instancia de la clase Random para la propiedad de TargetObject. En este ejemplo se crea una nueva instancia mediante una expresión de Visual Basic, pero también podría haberse creado previamente y almacenado en una variable de flujo de trabajo. En este ejemplo, sería más fácil usar la actividad Assign<T> en lugar de la actividad InvokeMethod<TResult>. Si la llamada al método invocada en última instancia por las actividades Assign<T> o InvokeMethod<TResult> es larga, InvokeMethod<TResult> tiene una ventaja porque tiene una propiedad RunAsynchronously. Cuando esta propiedad se establece en true, el método invocado se ejecutará de forma asincrónica con respecto al flujo de trabajo. Si hay actividades en paralelo, no se bloquearán mientras el método se esté ejecutando de forma asincrónica. Además, si el método que se va a invocar no tiene ningún valor devuelto, InvokeMethod<TResult> es la manera adecuada de invocar el método.

Argumentos y actividades dinámicas

Una definición de flujo de trabajo se crea en código ensamblando las actividades en un árbol de actividad y configurando propiedades y argumentos. Se pueden enlazar los argumentos existentes, aunque los nuevos no pueden agregarse a las actividades. Esto incluye argumentos de flujo de trabajo pasados a la actividad raíz. En código imperativo, los argumentos de flujo de trabajo se especifican como propiedades en un nuevo tipo CLR. En XAML se declaran usando x:Class y x:Member. Dado que no se ha creado ningún nuevo tipo de CLR cuando una definición de flujo de trabajo se crea como un árbol de objetos en memoria, no se pueden agregar argumentos. Sin embargo, los argumentos se pueden agregar a DynamicActivity. En este ejemplo, se crea un DynamicActivity<TResult> que toma dos argumentos enteros y los agrega juntos y devuelve el resultado. DynamicActivityProperty se crea para cada argumento y el resultado de la operación se asigna al argumento Result de DynamicActivity<TResult>.

InArgument<int> Operand1 = new InArgument<int>();
InArgument<int> Operand2 = new InArgument<int>();

DynamicActivity<int> wf = new DynamicActivity<int>
{
    Properties =
    {
        new DynamicActivityProperty
        {
            Name = "Operand1",
            Type = typeof(InArgument<int>),
            Value = Operand1
        },
        new DynamicActivityProperty
        {
            Name = "Operand2",
            Type = typeof(InArgument<int>),
            Value = Operand2
        }
    },

    Implementation = () => new Sequence
    {
        Activities =
        {
            new Assign<int>
            {
                To = new ArgumentReference<int> { ArgumentName = "Result" },
                Value = new InArgument<int>((env) => Operand1.Get(env) + Operand2.Get(env))
            }
        }
    }
};

Dictionary<string, object> wfParams = new Dictionary<string, object>
{
    { "Operand1", 25 },
    { "Operand2", 15 }
};

int result = WorkflowInvoker.Invoke(wf, wfParams);
Console.WriteLine(result);

Para obtener más información sobre las actividades dinámicas, consulte Crear una actividad en el tiempo de ejecución con DynamicActivity.

Actividades compiladas

Las actividades dinámicas son una manera de definir una actividad que contiene argumentos mediante código, pero las actividades también se pueden crear en código y compilar en tipos. Se pueden crear actividades simples que derivan de CodeActivity y actividades asincrónicas que derivan de AsyncCodeActivity. Estas actividades pueden tener argumentos, valores devueltos y definir la lógica mediante código imperativo. Para obtener ejemplos de cómo crear estos tipos de actividad, consulte Crear una actividad de flujo de trabajo mediante la clase CodeActivity y Crear actividades asincrónicas en WF.

Las actividades que derivan de NativeActivity pueden definir su lógica mediante código imperativo y también pueden contener actividades secundarias que definen la lógica. También tienen acceso completo a las características del runtime como crear marcadores. Para obtener ejemplos de creación de una actividad basada en NativeActivity, consulte NativeActivity Base (clase), Procedimiento para crear una actividad y Compuesto personalizado utilizando la actividad Native.

Las actividades que derivan de Activity definen la lógica únicamente con el uso de actividades secundarias. Estas actividades se crean normalmente mediante el diseñador de flujo de trabajo, pero también pueden definirse mediante código. En el siguiente ejemplo, se define una actividad Square que deriva de Activity<int>. La actividad Square tiene un único InArgument<T> denominado Value y define su lógica especificando una actividad Sequence mediante la propiedad Implementation. La actividad Sequence contiene una actividad WriteLine y una actividad Assign<T>. Juntas, estas tres actividades implementan la lógica de la actividad Square.

class Square : Activity<int>  
{  
    [RequiredArgument]  
    public InArgument<int> Value { get; set; }  
  
    public Square()  
    {  
        this.Implementation = () => new Sequence  
        {  
            Activities =  
            {  
                new WriteLine  
                {  
                    Text = new InArgument<string>((env) => "Squaring the value: " + this.Value.Get(env))  
                },  
                new Assign<int>  
                {  
                    To = new OutArgument<int>((env) => this.Result.Get(env)),  
                    Value = new InArgument<int>((env) => this.Value.Get(env) * this.Value.Get(env))  
                }  
            }  
        };  
    }  
}  

En el siguiente ejemplo se invoca una definición de flujo de trabajo formada por una sola actividad Square mediante WorkflowInvoker.

Dictionary<string, object> inputs = new Dictionary<string, object> {{ "Value", 5}};  
int result = WorkflowInvoker.Invoke(new Square(), inputs);  
Console.WriteLine("Result: {0}", result);  

Cuando se invoca el flujo de trabajo, en la consola se muestra el siguiente resultado:

Calculando raíz cuadrada del valor: 5
Resultado: 25