Nota
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
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, incluida la edición manual de XAML o mediante el Diseñador de flujos de trabajo para generar XAML. Sin embargo, el uso de XAML no es un requisito. Las definiciones de flujo de trabajo también se pueden crear mediante programación. En este tema se proporciona información general sobre la creación de definiciones, actividades y expresiones de flujo de trabajo mediante código. Para obtener ejemplos de cómo trabajar con flujos de trabajo XAML mediante código, consulta Serializar flujos de trabajo y actividades hacia y desde XAML.
Creación de definiciones de flujo de trabajo
Se puede crear una definición de flujo de trabajo instanciando un tipo de actividad y configurando las propiedades del objeto de 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:
Los ejemplos de este tema usan 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 distintas opciones de hospedaje disponibles, consulte Uso de WorkflowInvoker y WorkflowApplication.
En este ejemplo, se crea un flujo de trabajo que consta de una sola WriteLine actividad. 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 ejemplo siguiente se usa una Sequence actividad que contiene dos WriteLine actividades.
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 objetos, vea Cómo: Inicializar objetos sin llamar a un constructor (Guía de programación de C#) y Cómo: Declarar un objeto mediante un inicializador de objeto.
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 se produzca la generación aleatoria de números durante la ejecución del flujo de trabajo, se debe usar una expresión que se evalúe cada vez que se ejecute el 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 podría implementar mediante una expresión CSharpValue<TResult> y una expresión en C#.
new Assign<int>
{
To = n,
Value = new CSharpValue<int>("new Random().Next(1, 101)")
}
Las expresiones de C# deben compilarse antes de que se invoque el flujo de trabajo que los 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 Uso de expresiones de C# en flujos de trabajo de código del 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 un CSharpValue<TResult> objeto representa una expresión en la sintaxis de C# que se puede usar como valor r en una expresión. Las expresiones se evalúan cada vez que se ejecuta la actividad que las contiene. El resultado de la expresión se asigna a la variable n
de flujo de trabajo y la actividad siguiente del flujo de trabajo usa estos resultados. Para acceder al valor de la variable n
de flujo de trabajo en tiempo de ejecución, ActivityContext es necesario. Se puede acceder a esto mediante la siguiente expresión lambda.
Nota:
Tenga en cuenta que ambos ejemplos de código usan C# como lenguaje de programación, pero uno usa un VisualBasicValue<TResult> y otro usa .CSharpValue<TResult> VisualBasicValue<TResult> y CSharpValue<TResult> se pueden usar en proyectos de Visual Basic y C#. De forma predeterminada, las expresiones creadas en el diseñador de flujo de trabajo coinciden con el idioma del proyecto de hospedaje. Al crear flujos de trabajo en el código, el idioma deseado es a discreción del autor del flujo de trabajo.
En estos ejemplos, el resultado de la expresión se asigna a la variable n
de flujo de trabajo y la actividad siguiente del flujo de trabajo usa estos resultados. Para acceder al valor de la variable n
de flujo de trabajo en tiempo de ejecución, ActivityContext es necesario. Se puede acceder a esto mediante 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, vea Expresiones lambda (referencia de C#) o Expresiones lambda (Visual Basic).
Las expresiones lambda no se pueden serializar en formato XAML. Si se realiza un intento de serializar un flujo de trabajo con expresiones lambda, se lanza una excepción LambdaSerializationException con el siguiente mensaje: "Este flujo de trabajo contiene expresiones lambda especificadas en el código. Estas expresiones no son serializables en XAML. Para hacer que el flujo de trabajo sea serializable en 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 podría usar un VisualBasicValue<TResult>. Tenga en cuenta que no se requiere 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 a XAML, pero si el XAML serializado está pensado para que se vea y edite en el diseñador de flujos 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 serializar flujos de trabajo en XAML, consulta 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 Literal<T> actividad. Las actividades siguientes WriteLine son funcionalmente equivalentes.
new WriteLine
{
Text = "Hello World."
},
new WriteLine
{
Text = new Literal<string>("Hello World.")
}
No es válido 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 se valida el flujo de trabajo que contiene esta actividad, se devuelve el siguiente error de validación: "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 un InvalidWorkflowException que contiene el texto del error de validación. Se trata de un error de validación porque la creación de una expresión literal con un tipo de referencia no crea una nueva instancia del tipo de referencia para cada instancia del flujo de trabajo. Para resolverlo, reemplace la expresión literal por una que crea y devuelve una nueva instancia del 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, vea Expresiones.
Invocar métodos en objetos mediante expresiones y la actividad InvokeMethod
La InvokeMethod<TResult> actividad se puede usar para invocar métodos estáticos e de instancia de clases en .NET Framework. En un ejemplo anterior de este tema, se generó un número aleatorio mediante la Random clase .
new Assign<int>
{
To = n,
Value = new VisualBasicValue<int>("New Random().Next(1, 101)")
}
La InvokeMethod<TResult> actividad también podría haberse utilizado 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
}
Puesto que Next no es un método estático, se proporciona una instancia de la Random clase para la TargetObject propiedad . En este ejemplo se crea una nueva instancia mediante una expresión de Visual Basic, pero también se podría haber creado anteriormente y almacenado en una variable de flujo de trabajo. En este ejemplo, sería más sencillo usar la Assign<T> actividad en lugar de la InvokeMethod<TResult> actividad. Si la llamada al método invocada en última instancia por las actividades Assign<T> o InvokeMethod<TResult> es de ejecución prolongada, InvokeMethod<TResult> tiene una ventaja ya que tiene RunAsynchronously propiedad. Cuando esta propiedad se establece true
en , el método invocado se ejecutará de forma asincrónica con respecto al flujo de trabajo. Si otras actividades están en paralelo, no se bloquearán mientras el método se ejecuta 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
Para crear una definición de flujo de trabajo en el código, se ensamblan actividades en un árbol de actividad y se configuran las propiedades y argumentos. Los argumentos existentes se pueden enlazar, pero no se pueden agregar nuevos argumentos a las actividades. Esto incluye argumentos de flujo de trabajo pasados a la actividad raíz. En el código imperativo, los argumentos de flujo de trabajo se especifican como propiedades en un nuevo tipo CLR y, en XAML, se declaran mediante x:Class
y x:Member
. Dado que no hay ningún nuevo tipo CLR creado cuando se crea una definición de flujo de trabajo como un árbol de objetos en memoria, no se pueden agregar argumentos. Sin embargo, se pueden agregar argumentos a un DynamicActivity. En este ejemplo, se crea un DynamicActivity<TResult> objeto que toma dos argumentos enteros, los agrega juntos y devuelve el resultado. Se crea un DynamicActivityProperty para cada argumento, y el resultado de la operación se asigna al argumento Result del 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 Creación de una actividad en tiempo de ejecución.
Actividades compiladas
Las actividades dinámicas son una manera de definir una actividad que contiene argumentos mediante código, pero también se pueden crear actividades en el código y compilarse 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 su lógica mediante código imperativo. Para obtener ejemplos de cómo crear estos tipos de actividades, vea Clase base CodeActivity y Creación de actividades asincrónicas.
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 total a las características del entorno de ejecución, como la creación de marcadores. Para obtener ejemplos de la creación de una actividad basada en NativeActivity, vea NativeActivity Base Class, How to: Create an Activity (Cómo: Crear una actividad) y el ejemplo Custom Composite using Native Activity (Composición personalizada utilizando actividad nativa).
Las actividades que derivan de Activity definen la lógica únicamente con el uso de actividades secundarias. Normalmente, estas actividades se crean mediante el diseñador de flujo de trabajo, pero también se pueden definir mediante código. En el ejemplo siguiente, se define una Square
actividad que deriva de Activity<int>
. La Square
actividad tiene un único InArgument<T> llamado Value
, y define su lógica especificando una Sequence actividad mediante la Implementation propiedad. La Sequence actividad contiene una WriteLine actividad y una Assign<T> actividad. Juntas, estas tres actividades implementan la lógica de la Square
actividad.
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 ejemplo siguiente, se invoca una definición de flujo de trabajo que consta de una sola Square
actividad 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, se muestra la salida siguiente en la consola:
Cálculo de la raíz cuadrada del valor: 5
Resultado: 25