Introducción del desarrollador a Windows Workflow Foundation (WF) en .NET 4
Matt Milner, Pluralsight
Noviembre de 2009
Actualizado a la versión: abril de 2010
Visión general
Como saben los desarrolladores de software, escribir aplicaciones puede ser difícil y estamos buscando constantemente herramientas y marcos para simplificar el proceso y ayudarnos a centrarnos en los desafíos empresariales que estamos intentando resolver. Hemos pasado de escribir código en lenguajes de máquina como ensamblador a lenguajes de nivel superior, como C# y Visual Basic que facilitan el desarrollo, quitan problemas de nivel inferior, como la administración de memoria, y aumentan nuestra productividad como desarrolladores. Para los desarrolladores de Microsoft, el traslado a .NET permite que Common Language Runtime (CLR) asigne memoria, limpie objetos innecesarios y controle construcciones de bajo nivel como punteros.
Gran parte de la complejidad de una aplicación reside en la lógica y el procesamiento que pasa en segundo plano. Los problemas como la ejecución asincrónica o paralela y, por lo general, coordinar las tareas para responder a las solicitudes de usuario o las solicitudes de servicio pueden llevar rápidamente a los desarrolladores de aplicaciones a la codificación de bajo nivel de identificadores, devoluciones de llamada, sincronización, etc. Como desarrolladores, necesitamos la misma potencia y flexibilidad de un modelo de programación declarativo para los internos de una aplicación, ya que tenemos para la interfaz de usuario en Windows Presentation Foundation (WPF). Windows Workflow Foundation (WF) proporciona el marco declarativo para compilar la lógica de aplicaciones y servicios y proporciona a los desarrolladores un lenguaje de nivel superior para controlar tareas asincrónicas, paralelas y otro procesamiento complejo.
Tener un tiempo de ejecución para administrar la memoria y los objetos nos ha liberado para centrarnos más en los aspectos empresariales importantes de la escritura de código. Del mismo modo, tener un entorno de ejecución que pueda administrar las complejidades de coordinar el trabajo asincrónico proporciona un conjunto de características que mejora la productividad del desarrollador. WF es un conjunto de herramientas para declarar el flujo de trabajo (la lógica de negocios), las actividades para ayudar a definir la lógica y el flujo de control, y un entorno de ejecución para ejecutar la definición de aplicación resultante. En resumen, WF trata de usar un lenguaje de nivel superior para escribir aplicaciones, con el objetivo de hacer que los desarrolladores sean más productivos, aplicaciones más fáciles de administrar y cambiar más rápido para implementar. El tiempo de ejecución de WF no solo ejecuta sus flujos de trabajo, sino que también proporciona servicios y características importantes al escribir lógica de aplicación, como la persistencia del estado, el marcador y la reanudación de la lógica de negocios, lo que conduce a la agilidad de subprocesos y procesos, lo que permite escalar y escalar horizontalmente los procesos empresariales.
Para obtener más información conceptual sobre cómo usar WF para compilar sus aplicaciones puede hacer que sea más productivo, le recomiendo que lea "The Workflow Way" de David Chappell, que se encuentra en la sección Recursos adicionales.
Novedades de WF4
En la versión 4 de Microsoft® .NET Framework, Windows Workflow Foundation presenta una cantidad significativa de cambios de las versiones anteriores de la tecnología que se incluye como parte de .NET 3.0 y 3.5. De hecho, el equipo ha revisado el núcleo del modelo de programación, el tiempo de ejecución y las herramientas y ha rediseñado cada uno para aumentar el rendimiento y la productividad, así como para abordar los comentarios importantes obtenidos de las interacciones de los clientes con las versiones anteriores. Los cambios significativos realizados fueron necesarios para proporcionar la mejor experiencia para los desarrolladores que adoptan WF y para permitir que WF siga siendo un componente fundamental sólido en el que puede basarse en sus aplicaciones. Presentaré los cambios de alto nivel aquí, y en todo el documento cada tema obtendrá un tratamiento más profundo.
Antes de continuar, es importante comprender que la compatibilidad con versiones anteriores también era un objetivo clave en esta versión. Los nuevos componentes del marco se encuentran principalmente en los ensamblados System.Activities.* mientras que los componentes del marco compatibles con versiones anteriores se encuentran en los ensamblados System.Workflow.*. Los ensamblados System.Workflow.* forman parte de .NET Framework 4 y proporcionan compatibilidad completa con versiones anteriores para poder migrar la aplicación a .NET 4 sin cambios en el código de flujo de trabajo. En este documento usaré el nombre WF4 para hacer referencia a los nuevos componentes que se encuentran en los ensamblados System.Activities.* y WF3 para hacer referencia a los componentes que se encuentran en los ensamblados System.Workflow.*.
Diseñadores
Una de las áreas más visibles de mejora se encuentra en el diseñador de flujos de trabajo. La facilidad de uso y el rendimiento eran objetivos clave para el equipo de la versión de VS 2010. El diseñador ahora admite la capacidad de trabajar con flujos de trabajo mucho más grandes sin una degradación del rendimiento y los diseñadores se basan en Windows Presentation Foundation (WPF), aprovechando al máximo la experiencia de usuario enriquecida que se puede crear con el marco de interfaz de usuario declarativo. Los desarrolladores de actividades usarán XAML para definir la forma en que sus actividades se ven e interactúan con los usuarios en un entorno de diseño visual. Además, volver a hospedar el diseñador de flujos de trabajo en sus propias aplicaciones para permitir a los no desarrolladores ver e interactuar con los flujos de trabajo ahora es mucho más fácil.
Flujo de datos
En WF3, el flujo de datos de un flujo de trabajo era opaco. WF4 proporciona un modelo claro y conciso para el flujo de datos y el ámbito en el uso de argumentos y variables. Estos conceptos, familiarizados con todos los desarrolladores, simplifican la definición del almacenamiento de datos, así como el flujo de los datos hacia y hacia fuera de flujos de trabajo y actividades. El modelo de flujo de datos también hace más evidente las entradas y salidas esperadas de una actividad determinada y mejora el rendimiento del tiempo de ejecución a medida que los datos se administran con mayor facilidad.
Diagrama de flujo
Se ha agregado una nueva actividad de flujo de control denominada Flowchart para que los desarrolladores usen el modelo flowchart para definir un flujo de trabajo. El diagrama de flujo se asemeja más a los conceptos y procesos de pensamiento que muchos analistas y desarrolladores pasan al crear soluciones o diseñar procesos empresariales. Por lo tanto, tenía sentido proporcionar una actividad para facilitar el modelado del pensamiento conceptual y la planificación que ya se había hecho. El diagrama de flujo permite conceptos como volver a pasos anteriores y dividir la lógica en función de una sola condición o una lógica Switch/Case.
Modelo de programación
El modelo de programación wf se ha renovado para que sea más sencillo y sólido. La actividad es el tipo base principal del modelo de programación y representa los flujos de trabajo y las actividades. Además, ya no es necesario crear un workflowRuntime para invocar un flujo de trabajo, simplemente puede crear una instancia y ejecutarla, simplificando las pruebas unitarias y los escenarios de aplicación en los que no desea pasar por los problemas de configuración de un entorno específico. Por último, el modelo de programación de flujo de trabajo se convierte en una composición totalmente declarativa de actividades, sin código adicional, lo que simplifica la creación de flujos de trabajo.
Integración de Windows Communication Foundation (WCF)
Las ventajas de WF se aplican sin duda a la creación de servicios y a la creación o coordinación de interacciones de servicio. Un gran esfuerzo ha ido mejorando la integración entre WCF y WF. Las nuevas actividades de mensajería, la correlación de mensajes y la compatibilidad mejorada con el hospedaje, junto con la definición de servicio declarativa completa son las principales áreas de mejora.
Introducción al flujo de trabajo
La mejor manera de entender WF es empezar a usarla y aplicar los conceptos. Trataré varios conceptos básicos sobre los fundamentos del flujo de trabajo y, a continuación, le guiaré por la creación de algunos flujos de trabajo sencillos para ilustrar cómo se relacionan esos conceptos entre sí.
Estructura de flujo de trabajo
Las actividades son los bloques de creación de WF y todas las actividades derivan en última instancia de Activity. Nota de terminología: las actividades son una unidad de trabajo en WF. Las actividades se pueden componer en actividades más grandes. Cuando se usa una actividad como punto de entrada de nivel superior, se denomina "Flujo de trabajo", al igual que Main es simplemente otra función que representa un punto de entrada de nivel superior a los programas CLR. Por ejemplo, la figura 1 muestra un flujo de trabajo simple que se compila en código.
Secuencia s = nueva secuencia
{
Activities = {
new WriteLine {Text = "Hello"},
new Sequence {
Actividades =
{
new WriteLine {Text = "Workflow"},
new WriteLine {Text = "World"}
}
}
}
};
Figura 1: una de flujo de trabajo simple
Observe en la figura 1 que la actividad Sequence se usa como actividad raíz para definir el estilo de flujo de control raíz para el flujo de trabajo. Cualquier actividad se puede usar como raíz o flujo de trabajo y ejecutarse, incluso una WriteLine simple. Al establecer la propiedad Activities en la secuencia con una colección de otras actividades, he definido la estructura de flujo de trabajo. Además, las actividades secundarias pueden tener actividades secundarias, creando un árbol de actividades que componen la definición general del flujo de trabajo.
Plantillas de flujo de trabajo y el Diseñador de flujos de trabajo
WF4 incluye muchas actividades y Visual Studio 2010 incluye una plantilla para definir actividades. Las dos actividades más comunes que se usan para el flujo de control raíz son Sequence y Flowchart. Aunque cualquier actividad se puede ejecutar como un flujo de trabajo, estas dos proporcionan los patrones de diseño más comunes para definir la lógica de negocios. La figura 2 muestra un ejemplo de un modelo de flujo de trabajo secuencial que define el procesamiento de un pedido que se recibe, guarda y, a continuación, las notificaciones enviadas a otros servicios.
Figura 2: de diseño de flujo de trabajo secuencial
El tipo de flujo de trabajo flowchart se está introduciendo en WF4 para abordar las solicitudes comunes de los usuarios existentes, como poder volver a los pasos anteriores en un flujo de trabajo y porque se parece más al diseño conceptual realizado por analistas y desarrolladores que trabajan en la definición de la lógica de negocios. Por ejemplo, considere un escenario que implique la entrada de usuario a la aplicación. En respuesta a los datos proporcionados por el usuario, el programa debe continuar en el proceso o volver a un paso anterior para solicitar la entrada de nuevo. Con un flujo de trabajo secuencial, esto implicaría algo similar a lo que se muestra en la figura 3, donde se usa una actividad DoWhile para continuar el procesamiento hasta que se cumpla alguna condición.
Figura 3: Secuencial para la bifurcación de decisiones
El diseño de la figura 3 funciona, pero como desarrollador o analista que examina el flujo de trabajo, el modelo no representa la lógica original ni los requisitos descritos. El flujo de trabajo diagrama de flujo de la figura 4 proporciona un resultado técnico similar al modelo secuencial que se usa en la figura 3, pero el diseño del flujo de trabajo coincide más estrechamente con el pensamiento y los requisitos tal como se definió originalmente.
Figura 4: flujo de trabajo de diagrama de flujo
Flujo de datos en flujos de trabajo
La primera idea que la mayoría de las personas tienen cuando piensan en el flujo de trabajo es el proceso de negocio o el flujo de la aplicación. Sin embargo, tan crítico como el flujo es los datos que impulsan el proceso y la información que se recopila y almacena durante la ejecución del flujo de trabajo. En WF4, el almacenamiento y la administración de datos han sido un área privilegiada de consideración de diseño.
Hay tres conceptos principales para comprender con respecto a los datos: Variables, argumentos y expresiones. Las definiciones simples de cada una son que las variables son para almacenar datos, los argumentos son para pasar datos y las expresiones son para manipular datos.
Variables: almacenamiento de datos
Las variables de los flujos de trabajo son muy similares a las variables que se usan en lenguajes imperativos: describen una ubicación con nombre para que los datos se almacenen y siguen ciertas reglas de ámbito. Para crear una variable, primero debe determinar en qué ámbito debe estar disponible la variable. Al igual que puede tener variables en el código que están disponibles en el nivel de clase o método, las variables de flujo de trabajo se pueden definir en distintos ámbitos. Considere el flujo de trabajo en la figura 5. En este ejemplo, puede definir una variable en el nivel raíz del flujo de trabajo o en el ámbito definido por la actividad Recopilar secuencia de datos de fuente.
Figura 5: Variables con ámbito de actividades
Argumentos: pasar datos
Los argumentos se definen en las actividades y definen el flujo de datos dentro y fuera de la actividad. Puede pensar en argumentos en actividades muy similares a los argumentos que se usan para los métodos en código imperativo. Los argumentos pueden ser In, Out o In/Out y tener un nombre y un tipo. Al compilar un flujo de trabajo, puede definir argumentos en la actividad raíz que permite pasar datos al flujo de trabajo cuando se invoca. Los argumentos se definen en el flujo de trabajo en gran medida que las variables mediante la ventana de argumentos.
A medida que agregue actividades al flujo de trabajo, deberá configurar los argumentos de las actividades y esto se hace principalmente haciendo referencia a variables en el ámbito o mediante expresiones, que analizaré a continuación. De hecho, la clase base Argument contiene una propiedad Expression que es una actividad que devuelve un valor del tipo de argumento, por lo que todas estas opciones están relacionadas y dependen de Activity.
Al usar el diseñador de flujo de trabajo para editar argumentos, puede escribir expresiones que representen valores literales en la cuadrícula de propiedades o usar nombres de variable para hacer referencia a una variable en el ámbito, como se muestra en la figura 6, donde emailResult y emailAddress son variables definidas en el flujo de trabajo.
Figura 6: Configuración de argumentos sobre actividades
Expresiones: actuar en datos
Las expresiones son actividades que puede usar en el flujo de trabajo para operar en datos. Las expresiones se pueden usar en lugares donde se usa Activity y están interesados en un valor devuelto, lo que significa que puede usar expresiones en argumentos de configuración o para definir condiciones en actividades como while o if. Recuerde que la mayoría de las cosas de WF4 derivan de Activity y expressions no son diferentes, son un derivado de Activity<TResult> lo que significa que devuelven un valor de un tipo específico. Además, WF4 incluye varias expresiones comunes para hacer referencia a variables y argumentos, así como a expresiones de Visual Basic. Debido a esta capa de expresiones especializadas, las expresiones que se usan para definir argumentos pueden incluir referencias de código, valores literales y variables. La tabla 1 proporciona un pequeño muestreo de los tipos de expresiones que puede usar al definir argumentos mediante el diseñador de flujo de trabajo. Al crear expresiones en el código hay varias opciones más, como el uso de expresiones lambda.
Expresión | Tipo de expresión |
---|---|
"Hola mundo" |
Valor de cadena literal |
10 |
Valor int32 literal |
System.String.Concat("hello", " ", "world") |
Invocación de método imperativo |
"hola" & "mundo" |
Expresión de Visual Basic |
argInputString |
Referencia de argumentos (nombre de argumento) |
varResult |
Referencia de variable (nombre de variable) |
"hello: " & argInputString |
Literales y argumentos/variables mixtos |
tabla 1: Expresiones de ejemplo
Creación del primer flujo de trabajo
Ahora que he tratado los conceptos básicos sobre la actividad y el flujo de datos, puedo crear un flujo de trabajo con estos conceptos. Empezaré con un sencillo flujo de trabajo hola mundo para centrarme en los conceptos en lugar de en la verdadera propuesta de valor de WF. Para empezar, cree un nuevo proyecto de prueba unitaria en Visual Studio 2010. Para usar el núcleo de WF, agregue una referencia al ensamblado System.Activities y agregue instrucciones using para System.Activities, System.Activities.Statements y System.IO en el archivo de clase de prueba. A continuación, agregue un método de prueba como se muestra en la figura 7 para crear un flujo de trabajo básico y ejecutarlo.
[TestMethod]
public void TestHelloWorldStatic()
{
StringWriter writer = new StringWriter();
Console.SetOut(writer);
Sequence wf = new Sequence
{
Activities = {
new WriteLine {Text = "Hello"},
new WriteLine {Text = "World"}
}
};
WorkflowInvoker.Invoke(wf);
Assert.IsTrue(String.Compare(
"Hello\r\nWorld\r\n",
escritor. GetStringBuilder(). ToString()) == 0,
"Cadena incorrecta escrita");
}
Figura 7: Crear hola mundo en el código
La propiedad Text de la actividad WriteLine es una cadena inArgument<> y en este ejemplo he pasado un valor literal a esa propiedad.
El siguiente paso consiste en actualizar este flujo de trabajo para usar variables y pasar esas variables a los argumentos de actividad. En la figura 8 se muestra la nueva prueba actualizada para usar las variables.
[TestMethod]
public void TestHelloWorldVariables()
{
StringWriter writer = new StringWriter();
Console.SetOut(writer);
Sequence wf = new Sequence
{
Variables = {
new Variable<cadena>{Default = "Hello", Name = "greeting"},
new Variable<cadena> { Default = "Bill", Name = "name" } },
Activities = {
new WriteLine { Text = new VisualBasicValue<cadena>("greeting"),
new WriteLine { Text = new VisualBasicValue<cadena>(
"name + \"Gates\"")}
}
};
WorkflowInvoker.Invoke(wf);
}
Figura 8: Flujo de trabajo de código con variables
En este caso, las variables se definen como tipo Variable<cadena> y se le asigna un valor predeterminado. Las variables se declaran dentro de la actividad Sequence y, a continuación, se hace referencia desde el argumento Text de las dos actividades. Como alternativa, podría usar expresiones para inicializar las variables como se muestra en la figura 9, donde se usa la clase<TResult> visualBasicValue pasando una cadena que representa la expresión. En el primer caso, la expresión hace referencia al nombre de la variable y, en el segundo caso, la variable se concatena con un valor literal. La sintaxis usada en expresiones textuales es Visual Basic, incluso al escribir código en C#.
Activities = {
new WriteLine { Text = new VisualBasicValue<cadena>("greeting"),
TextWriter = writer },
new WriteLine { Text = new VisualBasicValue<cadena>("name + \"Gates\""),
TextWriter = writer }
}
Figura 9: Uso de expresiones para definir argumentos
Aunque los ejemplos de código ayudan a ilustrar puntos importantes y, por lo general, se sienten cómodos para los desarrolladores, la mayoría de las personas usarán el diseñador para crear flujos de trabajo. En el diseñador, se arrastran las actividades a la superficie de diseño y se usa una cuadrícula de propiedades actualizada pero familiar para establecer argumentos. Además, el diseñador de flujos de trabajo contiene regiones expandibles en la parte inferior para editar argumentos para el flujo de trabajo y las variables. Para crear un flujo de trabajo similar en el diseñador, agregue un nuevo proyecto a la solución, elija la plantilla Biblioteca de actividades y agregue una nueva actividad.
Cuando se muestra por primera vez el diseñador de flujo de trabajo, no contiene ninguna actividad. El primer paso para definir la actividad es elegir la actividad raíz. En este ejemplo, agregue una actividad Flowchart al diseñador arrastrándola desde la categoría Diagrama de flujo del cuadro de herramientas. En el diseñador de diagramas de flujo, arrastre dos actividades WriteLine desde el cuadro de herramientas y agréguelas una debajo de la otra al diagrama de flujo. Ahora tiene que conectar las actividades juntas para que el diagrama de flujo sepa la ruta de acceso que se debe seguir. Para ello, mantenga el puntero sobre el círculo verde "start" situado en la parte superior del diagrama de flujo para ver los identificadores de captura y, a continuación, haga clic y arrastre uno a la primera WriteLine y colóquelo en el controlador de arrastre que aparece en la parte superior de la actividad. Haga lo mismo para conectar la primera WriteLine a la segunda WriteLine. La superficie de diseño debe ser similar a la figura 10.
Figura 10: Diseño del diagrama de flujo hello
Una vez que la estructura está en su lugar, debe configurar algunas variables. Al hacer clic en la superficie de diseño y, a continuación, hacer clic en el botón Variables situado cerca de la parte inferior del diseñador, puede editar la colección de variables del diagrama de flujo. Agregue dos variables denominadas "greeting" y "name" para enumerar y establezca los valores predeterminados en "Hello" y "Bill", respectivamente: asegúrese de incluir las comillas al establecer los valores como esta es una expresión, por lo que es necesario citar cadenas literales. En la figura 11 se muestra la ventana variables configurada con las dos variables y sus valores predeterminados.
Figura 11: Variables definidas en el diseñador de flujos de trabajo
Para usar estas variables en las actividades WriteLine, escriba "greeting" (sin las comillas) en la cuadrícula de propiedades para el argumento Text de la primera actividad y "name" (de nuevo sin las comillas) para el argumento Text en la segunda WriteLine. En tiempo de ejecución, cuando se evalúan los argumentos Text, la actividad WriteLine resolverá y usará el valor de la variable.
En el proyecto de prueba, agregue una referencia al proyecto que contiene el flujo de trabajo. A continuación, puede agregar un método de prueba como el que se muestra en la figura 12 para invocar este flujo de trabajo y probar la salida. En la figura 12, puede ver que se crea el flujo de trabajo mediante la creación de instancias de una instancia de la clase creada. En este caso, el flujo de trabajo se definió en el diseñador y se compiló en una clase derivada de Activity que luego se puede invocar.
[TestMethod]
public void TestHelloFlowChart()
{
StringWriter tWriter = new StringWriter();
Console.SetOut(tWriter);
Workflows.HelloFlow wf = new Workflows.HelloFlow();
WorkflowInvoker.Invoke(wf);
Aserciones omitidas
}
Figura 12: Probar el diagrama de flujo
Hasta ahora he estado usando variables en el flujo de trabajo y las he usado para proporcionar valores a argumentos en las actividades WriteLine. El flujo de trabajo también puede tener argumentos definidos, lo que le permite pasar datos al flujo de trabajo al invocarlo y recibir la salida cuando se completa el flujo de trabajo. Para actualizar el diagrama de flujo del ejemplo anterior, quite la variable "name" (selecciónela y presione la tecla Eliminar) y, en su lugar, cree un argumento "name" de tipo cadena. Esto se hace de la misma manera, salvo que se usa el botón Argumentos para ver el editor de argumentos. Tenga en cuenta que los argumentos también pueden tener una dirección y no es necesario proporcionar un valor predeterminado, ya que el valor se pasará a la actividad en tiempo de ejecución. Dado que usa el mismo nombre para el argumento que hizo para la variable, los argumentos Text de las actividades WriteLine siguen siendo válidos. Ahora en tiempo de ejecución, esos argumentos evaluarán y resolverán el valor del argumento "name" en el flujo de trabajo y usarán ese valor. Agregue un argumento adicional de tipo cadena con una dirección de Out y un nombre de "fullGreeting"; se devolverá al código de llamada.
Figura 13: Definición de argumentos para el flujo de trabajo
En el diagrama de flujo, agregue una actividad Assign y conéctela a la última actividad WriteLine. En el argumento To, escriba "fullGreeting" (sin comillas) y, para el argumento Value, escriba "greeting & name" (sin comillas). Esto asignará la concatenación de la variable greeting con el argumento name al argumento de salida fullGreeting.
Ahora en la prueba unitaria, actualice el código para proporcionar el argumento al invocar el flujo de trabajo. Los argumentos se pasan al flujo de trabajo como dictionary<cadena,objeto> donde la clave es el nombre del argumento. Puede hacerlo simplemente cambiando la llamada para invocar el flujo de trabajo, como se muestra en la figura 14. Observe que los argumentos de salida también están incluidos en una cadena dictionary<, objeto> colección con clave en el nombre del argumento.
Cadena de<IDictionary, objeto> results = WorkflowInvoker.Invoke(wf,
new Dictionary<string,object> {
{"name", "Bill" } }
);
string outValue = results["fullGreeting"]. ToString();
Figura 14: Pasar argumentos a un flujo de trabajo
Ahora que ha visto los conceptos básicos de cómo reunir actividades, variables y argumentos, le guiaré en un recorrido por las actividades incluidas en el marco para permitir flujos de trabajo más interesantes centrados en la lógica de negocios en lugar de conceptos de bajo nivel.
Paseo por la paleta de actividades de flujo de trabajo
Con cualquier lenguaje de programación, se espera tener construcciones básicas para definir la lógica de la aplicación. Cuando se usa un marco de desarrollo de nivel superior como WF, esa expectativa no cambia. Al igual que tiene instrucciones en lenguajes .NET, como If/Else, Switch y While, para administrar el flujo de control, también necesita esas mismas funcionalidades al definir la lógica en un flujo de trabajo declarativo. Estas funcionalidades tienen la forma de la biblioteca de actividades base que se incluye con el marco de trabajo. En esta sección, le daré un paseo rápido por las actividades que se envían con el marco para darle una idea de la funcionalidad proporcionada fuera de la caja.
Actividades primitivas de actividad y recopilación
Al pasar a un modelo de programación declarativo, es fácil empezar a preguntarse cómo realizar tareas comunes de manipulación de objetos que son de segunda naturaleza al escribir código. Para esas tareas en las que se encuentra trabajando con objetos y necesita establecer propiedades, invocar comandos o administrar una colección de elementos, hay un conjunto de actividades diseñadas específicamente con esas tareas en mente.
Actividad | Descripción |
---|---|
Asignar |
Asigna un valor a una ubicación: habilitando variables de configuración. |
Demorar |
Retrasa la ruta de ejecución durante un período de tiempo especificado. |
InvokeMethod |
Invoca un método en un objeto .NET o un método estático en un tipo .NET, opcionalmente con un tipo de valor devuelto de T. |
WriteLine |
Escribe texto especificado en un escritor de texto: el valor predeterminado es Console.Out. |
AddToCollection<T> |
Agrega un elemento a una colección con tipo. |
RemoveFromCollection<T> |
Quita un elemento de una colección con tipo. |
ClearCollection<T> |
Quita todos los elementos de una colección. |
ExistsInCollection<T> |
Devuelve un valor booleano que indica si el elemento especificado existe en la colección. |
Actividades de flujo de control
Al definir la lógica de negocios o los procesos empresariales, tener control del flujo de ejecución es fundamental. Las actividades de flujo de control incluyen aspectos básicos, como Sequence, que proporciona un contenedor común cuando necesita ejecutar pasos en orden y lógica de bifurcación común, como las actividades If y Switch. Las actividades de flujo de control también incluyen lógica de bucle basada en datos (ForEach) y Condiciones(While). Lo más importante para simplificar la programación compleja son las actividades paralelas, que permiten realizar varias actividades asincrónicas al mismo tiempo.
Actividad | Descripción |
---|---|
Secuencia |
Para ejecutar actividades en serie |
While/DoWhile |
Ejecuta una actividad secundaria mientras una condición (expresión) es true |
ForEach<T> |
Recorre en iteración una colección enumerable y ejecuta la actividad secundaria una vez para cada elemento de la colección, esperando a que el elemento secundario se complete antes de iniciar la siguiente iteración. Proporciona acceso con tipo al elemento individual que conduce la iteración en forma de argumento con nombre. |
Si |
Ejecuta una de las dos actividades secundarias en función del resultado de la condición (expresión). |
Switch<T> |
Evalúa una expresión y programa la actividad secundaria con una clave coincidente. |
Paralelo |
Programa todas las actividades secundarias a la vez, pero también proporciona una condición de finalización para permitir que la actividad cancele las actividades secundarias pendientes si se cumplen ciertas condiciones. |
ParallelForEach<T> |
Recorre en iteración una colección enumerable y ejecuta la actividad secundaria una vez para cada elemento de la colección, programando todas las instancias al mismo tiempo. Al igual que forEach<T>, esta actividad proporciona acceso al elemento de datos actual en forma de argumento con nombre. |
Coger |
Programa todas las actividades de PickBranch secundarias y cancela todas las actividades, pero la primera para completar su desencadenador. La actividad PickBranch tiene un desencadenador y una acción; cada es una actividad. Cuando se completa una actividad de desencadenador, pick cancela todas sus demás actividades secundarias. |
Los dos ejemplos siguientes muestran varias de estas actividades en uso para ilustrar cómo componer estas actividades juntas. En el primer ejemplo, figura 15, se incluye el uso de ParallelForEach<T> para usar una lista de direcciones URL y obtener de forma asincrónica la fuente RSS en la dirección especificada. Una vez que se devuelve la fuente, se usa el> forEach<para iterar los elementos de fuente y procesarlos.
Tenga en cuenta que el código declara y define una instancia de VisualBasicSettings con referencias a los tipos System.ServiceModel.Syndication. A continuación, este objeto de configuración se usa al declarar VisualBasicValue<instancias de T> que hacen referencia a tipos de variables de ese espacio de nombres para habilitar la resolución de tipos para esas expresiones. Además, tenga en cuenta que el cuerpo de la actividad ParallelForEach toma una activityAction que se menciona en la sección sobre la creación de actividades personalizadas. Estas acciones usan DelegateInArgument y DelegateOutArgument de la misma manera que las actividades usan InArgument y OutArgument.
Figura 15: de actividades de flujo de control
El segundo ejemplo, la figura 16, es un patrón común de esperar una respuesta con un tiempo de espera. Por ejemplo, esperando a que un administrador apruebe una solicitud y envíe un aviso si la respuesta no ha llegado al tiempo asignado. Una actividad DoWhile provoca la repetición de esperar una respuesta mientras se usa la actividad Pick para ejecutar una actividad ManagerResponse y una actividad Delay al mismo tiempo que los desencadenadores. Cuando el retraso finaliza primero, se envía el aviso y, cuando la actividad ManagerResponse finaliza primero, la marca se establece para salir del bucle DoWhile.
Figura 16: Actividades Elegir y DoWhile
En el ejemplo de la figura 16 también se muestra cómo se pueden usar los marcadores en los flujos de trabajo. Una actividad crea un marcador para marcar un lugar en el flujo de trabajo, de modo que el procesamiento pueda reanudarse desde ese momento más adelante. La actividad Delay tiene un marcador que se reanuda una vez transcurrido un período de tiempo determinado. La actividad ManagerResponse es una actividad personalizada que espera la entrada y reanuda el flujo de trabajo una vez que llegan los datos. Las actividades de mensajería, que se describen en breve, son las principales actividades para la ejecución de marcadores. Cuando un flujo de trabajo no procesa activamente el trabajo, cuando solo está esperando a que se reanudan los marcadores, se considera inactivo y se puede conservar en un almacén duradero. Los marcadores se tratarán con más detalle en la sección sobre la creación de actividades personalizadas.
Migración
Para los desarrolladores que usan WF3, la actividad de interoperabilidad puede desempeñar un papel fundamental en la reutilización de los recursos existentes. La actividad, que se encuentra en el ensamblado System.Workflow.Runtime, ajusta el tipo de actividad existente y expone las propiedades de la actividad como argumentos en el modelo WF4. Dado que las propiedades son argumentos, puede usar expresiones para definir los valores. En la figura 17 se muestra la configuración de la actividad de interoperabilidad para llamar a una actividad WF3. Los argumentos de entrada se definen con referencias a variables dentro del ámbito dentro de la definición de flujo de trabajo. Las actividades integradas en WF3 tenían propiedades en lugar de argumentos, por lo que cada propiedad recibe un argumento de entrada y salida correspondiente, lo que le permite diferenciar los datos que envía a la actividad y los datos que espera recuperar después de que se ejecute la actividad. En un nuevo proyecto WF4 no encontrará esta actividad en el cuadro de herramientas porque la plataforma de destino está establecida en perfil de cliente de .NET Framework 4. Cambie la plataforma de destino de las propiedades del proyecto a .NET Framework 4 y la actividad aparecerá en el cuadro de herramientas.
Figura 17: configuración de la actividad de interoperabilidad
Diagrama de flujo
Al diseñar flujos de trabajo de diagrama de flujo hay varias construcciones que se pueden usar para administrar el flujo de ejecución en el diagrama de flujo. Estas construcciones proporcionan pasos sencillos, puntos de decisión simples basados en una sola condición o una instrucción switch. La eficacia real del diagrama de flujo es la capacidad de conectar estos tipos de nodo al flujo deseado.
Construcción/actividad | Descripción |
---|---|
Diagrama de flujo |
El contenedor de una serie de pasos de flujo, cada paso de flujo puede ser cualquier actividad o una de las siguientes construcciones, pero para ejecutarse, debe estar conectada dentro del diagrama de flujo. |
FlowDecision |
Proporciona lógica de bifurcación basada en una condición. |
FlowSwitch<T> |
Habilita varias ramas basadas en el valor de una expresión. |
FlowStep |
Representa un paso del diagrama de flujo con la capacidad de conectarse a otros pasos. Este tipo no se muestra en el cuadro de herramientas, ya que el diseñador agrega implícitamente. Esta actividad ajusta otras actividades del flujo de trabajo y proporciona la semántica de navegación para los pasos siguientes del flujo de trabajo. |
Es importante tener en cuenta que, aunque hay actividades específicas para el modelo de diagrama de flujo, se pueden usar otras actividades dentro del flujo de trabajo. Del mismo modo, se puede agregar una actividad de diagrama de flujo a otra actividad para proporcionar la semántica de ejecución y diseño de un diagrama de flujo, dentro de ese flujo de trabajo. Esto significa que puede tener una secuencia con varias actividades y un diagrama de flujo justo en el medio.
Actividades de mensajería
Uno de los principales focos en WF4 es una integración más estrecha entre WF y WCF. En términos de flujos de trabajo, esto significa actividades para modelar operaciones de mensajería, como enviar y recibir mensajes. En realidad, hay varias actividades diferentes incluidas en el marco de trabajo para la mensajería, cada una con funcionalidades y propósitos ligeramente diferentes.
Actividad | Descripción |
---|---|
Envío y recepción |
Una manera en que las actividades de mensajería envían o reciben un mensaje. Estas mismas actividades se componen en interacciones de estilo de solicitud/respuesta. |
ReceiveAndSendReply |
Modela una operación de servicio que recibe un mensaje y devuelve una respuesta. |
SendAndReceiveReply |
Invoca una operación de servicio y recibe la respuesta. |
InitializeCorrelation |
Permite inicializar los valores de correlación explícitamente como parte de la lógica de flujo de trabajo, en lugar de extraer los valores de un mensaje. |
CorrelationScope |
Define un ámbito de ejecución en el que se puede acceder a un identificador de correlación para recibir y enviar actividades que simplifican la configuración de un identificador compartido por varias actividades de mensajería. |
TransactedReceiveScope |
Permite que la lógica de flujo de trabajo se incluya en la misma transacción que fluye en una operación WCF mediante la actividad Receive. |
Para invocar una operación de servicio desde dentro de un flujo de trabajo, siga los pasos conocidos para agregar una referencia de servicio al proyecto de flujo de trabajo. Después, el sistema de proyecto de Visual Studio consumirá los metadatos del servicio y creará una actividad personalizada para cada operación de servicio que se encuentre en el contrato. Puede considerar esto como el equivalente de flujo de trabajo de un proxy WCF que se crearía en un proyecto de C# o Visual Basic. Por ejemplo, tomando un servicio existente que busca reservas de hoteles con el contrato de servicio que se muestra en la figura 18; agregar una referencia de servicio a ella produce una actividad personalizada que se muestra en la figura 19.
[ServiceContract]
interfaz pública IHotelService
{
[OperationContract]
Enumerar<HotelSearchResult> SearchHotels(
HotelSearchRequest requestDetails);
}
Figura 18: de contrato de servicio
Figura 19: de actividad WCF personalizada
Puede encontrar más información sobre la creación de flujos de trabajo expuestos como servicios WCF en la sección Servicios de flujo de trabajo más adelante en este documento.
Transacciones y control de errores
Escribir sistemas confiables puede ser difícil y requiere que realice un buen trabajo para controlar errores y administrar para mantener un estado coherente en la aplicación. Para un flujo de trabajo, los ámbitos para administrar el control de excepciones y las transacciones se modelan mediante actividades con propiedades de tipo ActivityAction o Activity para permitir a los desarrolladores modelar la lógica de procesamiento de errores. Además de estos patrones comunes de coherencia, WF4 también incluye actividades que permiten modelar la coordinación distribuida de larga duración a través de la compensación y la confirmación.
Actividad | Descripción |
---|---|
CancellationScope |
Se usa para permitir que el desarrollador del flujo de trabajo reaccione si se cancela un cuerpo de trabajo. |
TransactionScope |
Habilita la semántica similar al uso de un ámbito de transacción en el código ejecutando todas las actividades secundarias del ámbito en una transacción. |
TryCatch/Catch<T> |
Se usa para modelar el control de excepciones y detectar excepciones con tipo. |
Tirar |
Se puede usar para producir una excepción de la actividad. |
Volver a iniciar |
Se usa para volver a iniciar una excepción, generalmente una que se ha detectado mediante la actividad TryCatch. |
CompensableActivity |
Define la lógica para ejecutar actividades secundarias que pueden necesitar compensar sus acciones después del éxito. Proporciona un marcador de posición para la lógica de compensación, la lógica de confirmación y el control de cancelación. |
Compensar |
Invoca la lógica de control de compensación para una actividad compensable. |
Confirmar |
Invoca la lógica de confirmación para una actividad compensable. |
La actividad TryCatch proporciona una manera familiar de definir el ámbito de un conjunto de trabajo para detectar las excepciones que pueden producirse y la actividad Catch<T> proporciona el contenedor para la lógica de control de excepciones. Puede ver un ejemplo de cómo se usa esta actividad en la figura 20, con cada bloque de excepciones con tipo definido por una actividad Catch<T>, proporcionando acceso a la excepción a través del argumento con nombre visto a la izquierda de cada captura. Si surge la necesidad, la actividad Rethrow se puede usar para volver a iniciar la excepción detectada o la actividad Throw para iniciar una nueva excepción propia.
Figura 20: de actividad TryCatch
Las transacciones ayudan a garantizar la coherencia en el trabajo de corta duración y la actividad TransactionScope proporciona el mismo tipo de ámbito que puede obtener en el código de .NET mediante la clase TransactionScope.
Por último, cuando necesite mantener la coherencia, pero no puede usar una transacción atómica en los recursos, puede usar la compensación. La compensación le permite definir un conjunto de trabajo que, si se completa, puede tener un conjunto de actividades para compensar los cambios realizados. Como ejemplo sencillo, considere la actividad compensable en la figura 21 donde se envía un correo electrónico. Si una vez completada la actividad, se produce una excepción, se puede invocar la lógica de compensación para devolver el sistema a un estado coherente; en este caso, un correo electrónico de seguimiento para retirar el mensaje anterior.
Figura 21: de actividad compensable
Creación y ejecución de flujos de trabajo
Al igual que con cualquier lenguaje de programación, hay dos cosas fundamentales que se hacen con los flujos de trabajo: definirlos y ejecutarlos. En ambos casos, WF proporciona varias opciones para ofrecerle flexibilidad y control.
Opciones para diseñar flujos de trabajo
Al diseñar o definir flujos de trabajo, hay dos opciones principales: código o XAML. XAML proporciona la experiencia verdaderamente declarativa y permite definir toda la definición del flujo de trabajo en el marcado XML, haciendo referencia a actividades y tipos creados mediante .NET. La mayoría de los desarrolladores probablemente usarán el diseñador de flujos de trabajo para crear flujos de trabajo que darán lugar a una definición de flujo de trabajo XAML declarativa. Dado que XAML es solo XML, sin embargo, cualquier herramienta se puede usar para crearlo, que es una de las razones por las que es un modelo tan eficaz para compilar aplicaciones. Por ejemplo, el XAML que se muestra en la figura 22 se creó en un editor de texto simple y se puede compilar o usar directamente para ejecutar una instancia del flujo de trabajo que se está definiendo.
<p:Activity x:Class="Workflows.HelloSeq" xmlns="https://schemas.microsoft.com/netfx/2009/xaml/activities/design" xmlns:p="https://schemas.microsoft.com/netfx/2009/xaml/activities" xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml">
< > x:Members
<x:Property Name="greeting" Type="p:InArgument(x:String)" />
<x:Property Name="name" Type="p:InArgument(x:String)" />
</x:Members>
<p:Sequence>
<p:WriteLine>[greeting & " from workflow"]</p:WriteLine>
<p:WriteLine>[name]</p:WriteLine>
</p:Sequence>
</p:Activity>
Figura 22: Flujo de trabajo definido en XAML
El diseñador genera este mismo CÓDIGO XAML y se puede generar mediante herramientas personalizadas, lo que facilita mucho la administración. Además, también es posible, como se mostró anteriormente, para compilar flujos de trabajo mediante código. Esto implica la creación de la jerarquía de actividades mediante el uso de las distintas actividades en el marco y las actividades personalizadas. Ha visto varios ejemplos de flujos de trabajo definidos completamente en el código. A diferencia de wf3, ahora es posible crear un flujo de trabajo en el código y serializarlo fácilmente en XAML; proporcionar más flexibilidad en el modelado y la administración de definiciones de flujo de trabajo.
Como mostraré, para ejecutar un flujo de trabajo, todo lo que necesitas es una actividad y que puede ser una instancia de código integrado o una creada a partir de XAML. El resultado final de cada una de las técnicas de modelado es una clase derivada de Activity o una representación XML que se puede deserializar o compilar en una actividad.
Opciones para ejecutar flujos de trabajo
Para ejecutar un flujo de trabajo, necesita una actividad que defina el flujo de trabajo. Hay dos formas habituales de obtener una actividad que se puede ejecutar: crearla en código o leer en un archivo XAML y deserializar el contenido en una actividad. La primera opción es sencilla y ya he mostrado varios ejemplos. Para cargar un archivo XAML, debes usar la clase ActivityXamlServices que proporciona un método Load estático. Simplemente pase un objeto Stream o XamlReader y obtenga la actividad representada en el XAML.
Una vez que tenga una actividad, la manera más sencilla de ejecutarla es mediante la clase WorkflowInvoker como hice anteriormente en las pruebas unitarias. El método Invoke de esta clase tiene un parámetro de tipo Activity y devuelve una cadena de<IDictionary, objeto>. Si necesita pasar argumentos al flujo de trabajo, primero debe definirlos en el flujo de trabajo y, a continuación, pasar los valores junto con activity, al método Invoke como diccionario de pares nombre-valor. Del mismo modo, los argumentos Out o In/Out definidos en el flujo de trabajo se devolverán como resultado de ejecutar el método. La figura 23 proporciona un ejemplo de carga de un flujo de trabajo desde un archivo XAML, pasando argumentos al flujo de trabajo y recuperando los argumentos de salida resultantes.
MathWF de actividad;
using (Stream mathXaml = File.OpenRead("Math.xaml"))
{
mathWF = ActivityXamlServices.Load(mathXaml);
}
var outputs = WorkflowInvoker.Invoke(mathWF,
new Dictionary<string, object> {
{ "operando1", 5 },
{ "operando2", 10 },
{ "operation", "add" } });
Assert.AreEqual<int>(15, (int)outputs["result"], "Incorrecto resultado devuelto");
Figura 23: Invocar el flujo de trabajo "XAML flexible" con argumentos de entrada y salida
Observe en este ejemplo que la actividad se carga desde un archivo XAML y que todavía puede aceptar y devolver argumentos. El flujo de trabajo se desarrolló con el diseñador en Visual Studio para facilitarlo, pero podría desarrollarse en un diseñador personalizado y almacenarse en cualquier lugar. El CÓDIGO XAML se puede cargar desde una base de datos, una biblioteca de SharePoint u otro almacén antes de entregarlo al tiempo de ejecución.
El uso de WorkflowInvoker proporciona el mecanismo más sencillo para ejecutar flujos de trabajo de corta duración. Básicamente, hace que el flujo de trabajo actúe como una llamada de método en la aplicación, lo que le permite aprovechar más fácilmente todas las ventajas de WF sin tener que hacer mucho trabajo para hospedar WF en sí mismo. Esto resulta especialmente útil cuando prueba unitaria las actividades y los flujos de trabajo, ya que reduce la configuración de prueba necesaria para ejercer un componente en prueba.
Otra clase de hospedaje común es WorkflowApplication que proporciona un identificador seguro a un flujo de trabajo que se ejecuta en tiempo de ejecución y le permite administrar flujos de trabajo de larga duración más fácilmente. Con WorkflowApplication, todavía puede pasar argumentos al flujo de trabajo de la misma manera que con WorkflowInvoker, pero se usa el método Run para iniciar realmente el flujo de trabajo en ejecución. En este punto, el flujo de trabajo comienza a ejecutarse en otro subproceso y el control vuelve al código que realiza la llamada.
Dado que el flujo de trabajo se está ejecutando de forma asincrónica, en el código de hospedaje probablemente querrá saber cuándo se completa el flujo de trabajo, o si produce una excepción, etc. Para estos tipos de notificaciones, la clase WorkflowApplication tiene un conjunto de propiedades de tipo Action<T> que se pueden usar como eventos como para agregar código para reaccionar a determinadas condiciones de la ejecución del flujo de trabajo, entre las que se incluyen: anuladas, excepciones no controladas, completadas, inactivas y descargadas. Al ejecutar un flujo de trabajo mediante WorkflowApplication, puede usar código similar al que se muestra en la figura 24 mediante acciones para controlar los eventos.
WorkflowApplication wf = new WorkflowApplication(new Flowchart1());
Wf. Completed = delegate(WorkflowApplicationCompletedEventArgs e)
{
Console.WriteLine("Workflow {0} complete", e.InstanceId);
};
Wf. Aborted = delegate(WorkflowApplicationAbortedEventArgs e)
{
Console.WriteLine(e.Reason);
};
Wf. OnUnhandledException =
delegate(WorkflowApplicationUnhandledExceptionEventArgs e)
{
Console.WriteLine(e.UnhandledException.ToString());
return UnhandledExceptionAction.Terminate;
};
Wf. Run();
Figura 24: Acciones workflowApplication
En este ejemplo, el objetivo del código host, una aplicación de consola sencilla, es notificar al usuario a través de la consola cuando se complete el flujo de trabajo o si se produce un error. En un sistema real, el host estará interesado en estos eventos y probablemente reaccionará a ellos de forma diferente para proporcionar a los administradores información sobre errores o para administrar mejor las instancias.
Extensiones de flujo de trabajo
Una de las características principales de WF, desde WF3, ha sido que es lo suficientemente ligera como para hospedarse en cualquier dominio de aplicación de .NET. Dado que el tiempo de ejecución puede ejecutarse en dominios diferentes y puede necesitar semántica de ejecución personalizada, es necesario externalizar varios aspectos de los comportamientos en tiempo de ejecución desde el tiempo de ejecución. Aquí es donde entran en juego las extensiones de flujo de trabajo. Las extensiones de flujo de trabajo le permiten al desarrollador escribir el código de host, si lo desea, para agregar el comportamiento al tiempo de ejecución extendiéndolo con código personalizado.
Dos tipos de extensión que el entorno de ejecución de WF conoce son las extensiones de persistencia y seguimiento. La extensión de persistencia proporciona la funcionalidad básica para guardar el estado de un flujo de trabajo en un almacén duradero y recuperar ese estado cuando sea necesario. El trasvase de extensiones de persistencia con el marco incluye compatibilidad con Microsoft SQL Server, pero las extensiones se pueden escribir para admitir otros sistemas de base de datos o almacenes duraderos.
Persistencia
Para poder usar la extensión de persistencia, primero debe configurar una base de datos para contener el estado, que se puede realizar mediante los scripts SQL que se encuentran en %windir%\Microsoft.NET\Framework\v4.0.30319\sql\<language> (por ejemplo, c:\windows\microsoft.net\framework\v4.0.30319\sql\en\). Después de crear una base de datos, ejecute los dos scripts para crear la estructura (SqlWorkflowInstanceStoreSchema.sql) y los procedimientos almacenados (SqlWorkflowInstanceStoreLogic.sql) dentro de la base de datos.
Una vez configurada la base de datos, se usa SqlWorkflowInstanceStore junto con la clase WorkflowApplication. En primer lugar, cree el almacén y configúrelo y, a continuación, proporcione a WorkflowApplication como se muestra en la figura 25.
WorkflowApplication application = new WorkflowApplication(activity);
InstanceStore instanceStore = new SqlWorkflowInstanceStore(
@"Data Source=.\\SQLEXPRESS;Integrated Security=True");
Vista InstanceView = instanceStore.Execute(
instanceStore.CreateInstanceHandle(), new CreateWorkflowOwnerCommand(),
TimeSpan.FromSeconds(30));
instanceStore.DefaultInstanceOwner = view. InstanceOwner;
aplicación. InstanceStore = instanceStore;
Figura 25: Agregar el proveedor de persistencia de SQL
Hay dos maneras de que el flujo de trabajo se pueda conservar. La primera es mediante el uso directo de la actividad Persist en un flujo de trabajo. Cuando esta actividad se ejecuta, hace que el estado del flujo de trabajo se conserve en la base de datos, guardando el estado actual del flujo de trabajo. Esto proporciona al autor del flujo de trabajo el control sobre cuándo es importante guardar el estado actual del flujo de trabajo. La segunda opción permite que la aplicación de hospedaje conserve el estado del flujo de trabajo cuando se produzcan varios eventos en la instancia de flujo de trabajo; lo más probable es que el flujo de trabajo esté inactivo. Al registrarse para la acción PersistableIdle en WorkflowApplication, el código host puede responder al evento para indicar si la instancia debe conservarse, descargarse o tampoco. En la figura 26 se muestra un registro de aplicación host para recibir notificaciones cuando el flujo de trabajo está inactivo y lo hace que persista.
Wf. PersistableIdle = (waie) => PersistableIdleAction.Persist;
Figura 26: Descarga del flujo de trabajo cuando está inactivo
Una vez que un flujo de trabajo se ha inactivo y se ha conservado, el entorno de ejecución de WF puede descargarlo de la memoria y liberar recursos para otros flujos de trabajo que necesitan procesamiento. Puede optar por descargar la instancia de flujo de trabajo devolviendo la enumeración adecuada de la acción PersistableIdle. Una vez que se descarga un flujo de trabajo, en algún momento el host querrá volver a cargarlo fuera del almacén de persistencia para reanudarlo. Para ello, la instancia de flujo de trabajo debe cargarse mediante un almacén de instancias y el identificador de instancia. Esto provocará, a su vez, que el almacén de instancias se pida que cargue el estado. Una vez cargado el estado, se puede usar el método Run en WorkflowApplication o se puede reanudar un marcador como se muestra en la figura 27. Para más información sobre los marcadores, consulte la sección actividades personalizadas.
WorkflowApplication application = new WorkflowApplication(activity);
aplicación. InstanceStore = instanceStore;
aplicación. Load(id);
aplicación. ResumeBookmark(readLineBookmark, input);
Figura 27: Reanudación de un flujo de trabajo persistente
Uno de los cambios de WF4 es cómo se conserva el estado de los flujos de trabajo y se basa en gran medida en las nuevas técnicas de administración de datos de argumentos y variables. En lugar de serializar todo el árbol de actividad y mantener el estado durante la vigencia del flujo de trabajo, solo se conservan las variables de ámbito y los valores de argumento, junto con algunos datos en tiempo de ejecución, como la información del marcador. Observe en la figura 27 que cuando se vuelve a cargar la instancia persistente, además de usar el almacén de instancias, también se pasa la actividad que define el flujo de trabajo. Básicamente, el estado de la base de datos se aplica a la actividad proporcionada. Esta técnica permite una mayor flexibilidad para el control de versiones y da como resultado un mejor rendimiento, ya que el estado tiene una superficie de memoria menor.
Seguimiento
La persistencia permite al host admitir procesos de larga duración, equilibrar la carga de instancias entre hosts y otros comportamientos tolerantes a errores. Sin embargo, una vez completada la instancia de flujo de trabajo, el estado de la base de datos se elimina a menudo, ya que ya no es útil. En un entorno de producción, tener información sobre lo que está haciendo actualmente un flujo de trabajo y lo que ha hecho es fundamental para administrar flujos de trabajo y obtener información sobre el proceso de negocio. Poder realizar un seguimiento de lo que sucede en la aplicación es una de las características atractivas de usar el entorno de ejecución de WF.
El seguimiento consta de dos componentes principales: los participantes de seguimiento y los perfiles de seguimiento. Un perfil de seguimiento define qué eventos y datos desea que realice el seguimiento del tiempo de ejecución. Los perfiles pueden incluir tres tipos principales de consultas:
- ActivityStateQuery: se usa para identificar estados de actividad (por ejemplo, cerrados) y variables o argumentos para extraer datos.
- WorkflowInstanceQuery: se usa para identificar eventos de flujo de trabajo
- CustomTrackingQuery: se usa para identificar llamadas explícitas para realizar un seguimiento de los datos, normalmente dentro de actividades personalizadas.
Por ejemplo, en la figura 28 se muestra un objeto TrackingProfile que se crea, que incluye activityStateQuery y workflowInstanceQuery. Tenga en cuenta que las consultas indican cuándo recopilar información y también qué datos se van a recopilar. Para ActivityStateQuery, he incluido una lista de variables que deben tener su valor extraído y agregado a los datos de seguimiento.
Perfil de TrackingProfile = nuevo TrackingProfile
{
Name = "SimpleProfile",
Consultas = {
new WorkflowInstanceQuery {
States = { "*" }
},
new ActivityStateQuery {
ActivityName = "WriteLine",
States={ "*" },
Variables = {"Text" }
}
}
};
Figura 28: Crear un perfil de seguimiento
Un participante de seguimiento es una extensión que se puede agregar al tiempo de ejecución y es responsable de procesar los registros de seguimiento a medida que se emiten. La clase base TrackingParticipant define una propiedad para proporcionar un TrackingProfile y un método Track que controla el seguimiento. En la figura 29 se muestra un participante de seguimiento personalizado limitado que escribe datos en la consola. Para usar el participante de seguimiento, debe inicializarse con un perfil de seguimiento y, a continuación, agregarlo a la colección de extensiones en la instancia de flujo de trabajo.
public class ConsoleTrackingParticipant : TrackingParticipant
{
protected override void Track(TrackingRecord record, TimeSpan timeout)
{
ActivityStateRecord aRecord = record as ActivityStateRecord;
if (aRecord != null)
{
Console.WriteLine("{0} entró en estado {1}",
aRecord.Activity.Name, aRecord.State);
foreach (elemento var en aRecord.Arguments)
{
Console.ForegroundColor = ConsoleColor.Cyan;
Console.WriteLine("Variable:{0} tiene valor: {1}",
artículo. Clave, elemento. Valor);
Console.ResetColor();
}
}
}
}
Figura 29: de seguimiento de la consola
WF se distribuye con etwTrackingParticipant (ETW = Seguimiento de empresa para Windows) que puede agregar al entorno de ejecución para habilitar el seguimiento de alto rendimiento de los datos. ETW es un sistema de seguimiento que es un componente nativo en Windows y que usa muchos componentes y servicios en el sistema operativo, incluidos controladores y otro código de nivel de kernel. Los datos escritos en ETW se pueden consumir mediante código personalizado o mediante herramientas como el próximo AppFabric de Windows Server. Para los usuarios que migran desde WF3, este será un cambio, ya que no habrá envío de participantes de seguimiento de SQL como parte del marco. Sin embargo, AppFabric de Windows Server se enviará con consumidores ETW que recopilarán los datos ETW y los almacenarán en una base de datos SQL. Del mismo modo, puede crear un consumidor ETW y almacenar los datos en cualquier formato que prefiera, o escribir su propio participante de seguimiento, lo que tenga más sentido para su entorno.
Creación de actividades personalizadas
Aunque la biblioteca de actividades base incluye una amplia paleta de actividades para interactuar con servicios, objetos y colecciones, no proporciona actividades para interactuar con subsistemas como bases de datos, servidores de correo electrónico o los objetos y sistemas de dominio personalizados. Parte de la introducción a WF4 será averiguar qué actividades principales podría necesitar o desear al compilar flujos de trabajo. Lo mejor es que, a medida que cree unidades de trabajo principales, puede combinarlas en actividades más generales que los desarrolladores pueden usar en sus flujos de trabajo.
Jerarquía de clases de actividad
Aunque una actividad es una actividad, no es cierto que todas las actividades tengan los mismos requisitos o necesidades. Por ese motivo, en lugar de todas las actividades derivadas directamente de Activity, hay una jerarquía de clases base de actividad, que se muestra en la figura 30, entre las que puede elegir al crear una actividad personalizada.
Figura 30: jerarquía de clases de actividad
Para la mayoría de las actividades personalizadas, derivará de AsyncCodeActivity, CodeActivity o NativeActivity (o una de las variantes genéricas) o modelará la actividad mediante declaración. En un nivel alto, las cuatro clases base se pueden describir de la siguiente manera:
- Actividad: se usa para modelar actividades mediante la redacción de otras actividades, normalmente definidas mediante XAML.
- CodeActivity: una clase base simplificada cuando necesita escribir código para realizar el trabajo.
- AsyncCodeActivity: se usa cuando la actividad realiza algún trabajo de forma asincrónica.
- NativeActivity: cuando la actividad necesita acceso a los elementos internos del entorno de ejecución, por ejemplo, para programar otras actividades o crear marcadores.
En las secciones siguientes, crearé varias actividades para ver cómo usar cada una de estas clases base. En general, como piensa en qué clase base se va a usar, debe empezar con la clase base Activity y ver si puede compilar la actividad mediante lógica declarativa, como se muestra en la sección siguiente. A continuación, CodeActivity proporciona un modelo simplificado si determina que tiene que escribir código .NET para realizar la tarea y AsyncCodeActivity si desea que la actividad se ejecute de forma asincrónica. Por último, si está escribiendo actividades de flujo de control como las que se encuentran en el marco (por ejemplo, While, Switch, If), deberá derivar de la clase base NativeActivity para administrar las actividades secundarias.
Redacción de actividades mediante el diseñador de actividades
Cuando creas un nuevo proyecto de biblioteca de actividades o cuando agregas un nuevo elemento a un proyecto wf y seleccionas la plantilla Actividad, lo que obtienes es un archivo XAML con un elemento Activity vacío en él. En el diseñador, esto se presenta como una superficie de diseño donde puede crear el cuerpo de la actividad. Para empezar a trabajar con una actividad que tendrá más de un paso, normalmente ayuda a arrastrar una actividad de secuencia en como cuerpo y, a continuación, rellenarla con la lógica de actividad real, ya que el cuerpo representa una sola actividad secundaria.
Puede pensar en la actividad de forma muy similar a un método en un componente con argumentos. En la propia actividad, puede definir argumentos, junto con su direccionalidad, para definir la interfaz de la actividad. Las variables que desea usar dentro de la actividad deberán definirse en las actividades que componen el cuerpo, como la secuencia raíz que mencioné anteriormente. Por ejemplo, puede crear una actividad NotifyManager que componga dos actividades más sencillas: GetManager y SendMail.
En primer lugar, cree un nuevo proyecto ActivityLibrary en Visual Studio 2010 y cambie el nombre del archivo Activity1.xaml a NotifyManager.xaml. A continuación, arrastre una actividad Sequence desde el cuadro de herramientas y agréguela al diseñador. Una vez que la secuencia está en su lugar, puede rellenarla con actividades secundarias como se muestra en la figura 31. (Tenga en cuenta que las actividades usadas en este ejemplo son actividades personalizadas en un proyecto al que se hace referencia y no están disponibles en el marco).
Figura 31: Notificar de actividad del administrador
Esta actividad debe tomar argumentos para el nombre del empleado y el cuerpo del mensaje. La actividad GetManager busca el administrador del empleado y proporciona el correo electrónico como una cadena outArgument<>. Por último, la actividad SendMail envía un mensaje al administrador. El siguiente paso consiste en definir los argumentos de la actividad expandiendo la ventana Argumentos en la parte inferior del diseñador y escribiendo la información de los dos argumentos de entrada necesarios.
Para componer estos elementos, debe poder pasar los argumentos de entrada especificados en la actividad NotifyManager a las actividades secundarias individuales. Para la actividad GetManager, necesita el nombre del empleado, que es un argumento en la actividad. Puede usar el nombre del argumento en el cuadro de diálogo de propiedades para el argumento employeeName en la actividad GetManager como se muestra en la figura 31. La actividad SendMail, necesita la dirección de correo electrónico del administrador y el mensaje. Para el mensaje, puede escribir el nombre del argumento que contiene el mensaje. Sin embargo, para la dirección de correo electrónico, necesita una manera de pasar el argumento de salida de la actividad GetManager al argumento in de la actividad SendMail. Para ello, necesita una variable.
Después de resaltar la actividad Sequence, puede usar la ventana Variables para definir una variable denominada "mgrEmail". Ahora puede escribir ese nombre de variable para el argumento ManagerEmail out en la actividad GetManager y el argumento To en la actividad SendMail. Cuando se ejecuta la actividad GetManager, los datos de salida se almacenarán en esa variable y, cuando se ejecute la actividad SendMail, leerá datos de esa variable como en el argumento .
El enfoque descrito es un modelo puramente declarativo para definir el cuerpo de la actividad. En algunas circunstancias, es posible que prefiera especificar el cuerpo de la actividad en el código. Por ejemplo, la actividad puede incluir una colección de propiedades que, a su vez, impulsan un conjunto de actividades secundarias; Un conjunto de valores con nombre que impulsan la creación de un conjunto de actividades de asignación sería un caso en el que se prefería usar código. En esos casos, puede escribir una clase que derive de la actividad y escribir código en la propiedad Implementación para crear una actividad (y cualquier elemento secundario) para representar la funcionalidad de la actividad. El resultado final es el mismo en ambos casos: la lógica se define mediante la redacción de otras actividades, solo el mecanismo por el que se define el cuerpo es diferente. En la figura 32 se muestra la misma actividad notifyManager que se define en el código.
public class NotifyManager : Activity
{
public InArgument<cadena> EmployeeName { get; set; }
public InArgument<cadena> Message { get; set; }
invalidación protegida de la implementación de> de actividad de Func<
{
Obtener
{
return () =>
{
Cadena<variable> mgrEmail =
new Variable<cadena> { Name = "mgrEmail" };
Secuencia s = nueva secuencia
{
Variables = { mgrEmail },
Activities = {
new GetManager {
EmployeeName =
new VisualBasicValue<cadena>("EmployeeName"),
Result = mgrEmail,
},
new SendMail {
ToAddress = mgrEmail,
MailBody = new VisualBasicValue<cadena>("Message"),
From = "test@contoso.com",
Asunto = "Correo electrónico automatizado"
}
}
};
return s;
};
}
set { base. Implementación = valor; }
}
}
Figura 32: Composición de actividad mediante de código
Observe que la clase base es Activity y la propiedad Implementation es un func<Activity> para devolver una sola actividad. Este código es muy similar al que se mostró anteriormente al crear flujos de trabajo, y eso no debería ser sorprendente, ya que el objetivo en ambos casos es crear una actividad. Además, en los argumentos de enfoque de código se pueden establecer con variables, o puede usar expresiones para conectar un argumento a otro, como se muestra para los argumentos EmployeeName y Message, ya que se usan en las dos actividades secundarias.
Escritura de clases de actividad personalizadas
Si determina que la lógica de actividad no se puede realizar mediante la redacción de otras actividades, puede escribir una actividad basada en código. Al escribir actividades en el código que derive de la clase adecuada, defina argumentos y, a continuación, invalide el método Execute. El tiempo de ejecución llama al método Execute cuando es el momento en que la actividad realiza su trabajo.
Para compilar la actividad SendMail usada en el ejemplo anterior, primero necesito elegir el tipo base. Aunque es posible crear la funcionalidad de la actividad SendMail mediante la clase base Activity y redactar tryCatch<actividades T> e InvokeMethod, para la mayoría de los desarrolladores será más natural elegir entre las clases base CodeActivity y NativeActivity y escribir código para la lógica de ejecución. Dado que esta actividad no necesita crear marcadores ni programar otras actividades, puedo derivar de la clase base CodeActivity. Además, dado que esta actividad devolverá un único argumento de salida, debe derivarse de CodeActivity<> TResult, que proporciona una<TResult>. El primer paso es definir varios argumentos de entrada para las propiedades de correo electrónico. A continuación, invalida el método Execute para implementar la funcionalidad de la actividad. En la figura 33 se muestra la clase de actividad completada.
public class SendMail : CodeActivity<SmtpStatusCode>
{
public InArgument<cadena> To { get; set; }
public InArgument<cadena> From { get; set; }
public InArgument<cadena> Subject { get; set; }
public InArgument<cadena> Body { get; set; }
protected override SmtpStatusCode Execute(
Contexto CodeActivityContext)
{
probar
{
Cliente SmtpClient = nuevo SmtpClient();
cliente. Send(
From.Get(context), ToAddress.Get(context),
Subject.Get(context), MailBody.Get(context));
}
catch (SmtpException smtpEx)
{
return smtpEx.StatusCode;
}
return SmtpStatusCode.Ok;
}
}
Figura 33: de actividad personalizada SendMail
Hay varias cosas que se deben tener en cuenta sobre el uso de argumentos. He declarado varias propiedades estándar de .NET mediante los tipos InArgument<T>. Así es como una actividad basada en código define sus argumentos de entrada y salida. Sin embargo, dentro del código, no puedo hacer referencia a estas propiedades para obtener el valor del argumento, necesito usar el argumento como un tipo de identificador para recuperar el valor mediante activityContext proporcionado; en este caso, codeActivityContext. Use el método Get de la clase de argumento para recuperar el valor y el método Set para asignar un valor a un argumento.
Una vez que la actividad completa el método Execute, el tiempo de ejecución detecta esto y supone que la actividad se realiza y la cierra. En el caso del trabajo asincrónico o de larga duración, hay maneras de cambiar este comportamiento, que se explican en una sección próxima, pero, en este caso, una vez que se envía el correo electrónico, se completa el trabajo.
Ahora vamos a examinar una actividad mediante la clase base NativeActivity para administrar actividades secundarias. Crearé una actividad de Iterador que ejecuta alguna actividad secundaria un número determinado de veces. En primer lugar, necesito un argumento para contener el número de iteraciones que se van a realizar, una propiedad para que se ejecute la actividad y una variable que contenga el número actual de iteraciones. El método Execute llama a un método BeginIteration para iniciar la iteración inicial y las iteraciones posteriores se invocan de la misma manera. Observe en la figura 34 que las variables se manipulan de la misma manera que los argumentos mediante el método Get y Set.
public class Iterator : NativeActivity
{
public Activity Body { get; set; }
public InArgument<int> RequestedIterations { get; set; }
public Variable<int> CurrentIteration { get; set; }
public Iterator()
{
CurrentIteration = new Variable<int> { Default = 0 };
}
protected override void Execute(NativeActivityContext context)
{
BeginIteration(context);
}
private void BeginIteration(NativeActivityContext context)
{
if (RequestedIterations.Get(context) > CurrentIteration.Get(context))
{
contexto. ScheduleActivity(Body,
new CompletionCallback(ChildComplete),
new FaultCallback(ChildFaulted));
}
}
}
Figura 34: de actividad nativa de iterador
Si examina detenidamente el método BeginIteration, observará la llamada al método ScheduleActivity. Así es como una actividad puede interactuar con el entorno de ejecución para programar otra actividad para su ejecución y es porque necesita esta funcionalidad que deriva de NativeActivity. Tenga en cuenta también que, al llamar al método ScheduleActivity en ActivityExecutionContext, se proporcionan dos métodos de devolución de llamada. Dado que no sabe qué actividad se usará como cuerpo o cuánto tiempo tardará en completarse y, dado que WF es un entorno de programación muy asincrónico, las devoluciones de llamada se usan para notificar a la actividad cuando se ha completado una actividad secundaria, lo que le permite escribir código para reaccionar.
La segunda devolución de llamada es una faultCallback que se invocará si la actividad secundaria produce una excepción. En esta devolución de llamada, recibirá la excepción y tendrá la capacidad de, a través de ActivityFaultContext, para indicar al tiempo de ejecución que se ha controlado el error, lo que impide que se desenfoque y salga de la actividad. En la figura 35 se muestran los métodos de devolución de llamada para la actividad iterador, donde ambas programan la siguiente iteración y FaultCallback controla el error para permitir que la ejecución continúe con la siguiente iteración.
private void ChildComplete(NativeActivityContext context,
Instancia de ActivityInstance)
{
CurrentIteration.Set(context, CurrentIteration.Get(context) + 1);
BeginIteration(context);
}
private void ChildFaulted(NativeActivityFaultContext context, Exception ex,
Instancia de ActivityInstance)
{
CurrentIteration.Set(context, CurrentIteration.Get(context) + 1);
contexto. HandleFault();
}
Figura 35: Devoluciones de llamada de actividad
Cuando la actividad necesita realizar el trabajo que puede ser de larga duración, lo ideal es que el trabajo se entregue a otro subproceso para permitir que el subproceso de flujo de trabajo se use para continuar procesando otras actividades o usarse para procesar otros flujos de trabajo. Sin embargo, simplemente iniciar subprocesos y devolver el control al tiempo de ejecución puede causar problemas, ya que el tiempo de ejecución no supervisa los subprocesos que cree. Si el tiempo de ejecución determinó que el flujo de trabajo estaba inactivo, el flujo de trabajo podría descargarse, desechar los métodos de devolución de llamada o el tiempo de ejecución podría suponer que la actividad se realiza y pasar a la siguiente actividad del flujo de trabajo. Para admitir la programación asincrónica en la actividad que derive de AsyncCodeActivity o AsyncCodeActivity<T>.
En la figura 36 se muestra un ejemplo de una actividad asincrónica que carga una fuente RSS. Esta firma del método execute es diferente para las actividades asincrónicas en que devuelve un IAsyncResult. Escriba el código en el método execute para iniciar la operación asincrónica. Invalide el método EndExecute para controlar la devolución de llamada cuando se complete la operación asincrónica y devuelva el resultado de la ejecución.
public sealed class GetFeed : AsyncCodeActivity<SyndicationFeed>
{
public InArgument<Uri> FeedUrl { get; set; }
invalidación protegida IAsyncResult BeginExecute(
Contexto AsyncCodeActivityContext, devolución de llamada de AsyncCallback,
estado del objeto)
{
var req = (HttpWebRequest)HttpWebRequest.Create(
FeedUrl.Get(context));
Req. Método = "GET";
contexto. UserState = req;
return req. BeginGetResponse(new AsyncCallback(callback), state);
}
invalidación protegida SyndicationFeed EndExecute(
Contexto AsyncCodeActivityContext, resultado de IAsyncResult)
{
HttpWebRequest req = context. UserState como HttpWebRequest;
WebResponse wr = req. EndGetResponse(result);
SyndicationFeed localFeed = SyndicationFeed.Load(
XmlReader.Create(wr. GetResponseStream()));
devolver localFeed;
}
}
Figura 36: Creación de actividades asincrónicas
Conceptos de actividad adicionales
Ahora que ha visto los conceptos básicos de la creación de actividades mediante las clases base, los argumentos y las variables, y la administración de la ejecución; Tocaré brevemente algunas características más avanzadas que puede usar en el desarrollo de actividades.
Los marcadores permiten a un autor de actividad crear un punto de reanudación en la ejecución de un flujo de trabajo. Una vez creado un marcador, se puede reanudar para continuar con el procesamiento del flujo de trabajo desde donde se dejó. Los marcadores se guardan como parte del estado, por lo que, a diferencia de las actividades asincrónicas, después de crear un marcador, no es un problema para que la instancia de flujo de trabajo se conserve y descargue. Cuando el host quiere reanudar el flujo de trabajo, carga la instancia de flujo de trabajo y llama al método ResumeBookmark para reanudar la instancia desde donde se dejó. Al reanudar marcadores, también se pueden pasar datos a la actividad. En la figura 37 se muestra una actividad ReadLine que crea un marcador para recibir entradas y registra un método de devolución de llamada que se va a invocar cuando llegan los datos. El tiempo de ejecución sabe cuándo una actividad tiene marcadores pendientes y no cerrará la actividad hasta que se reanude el marcador. El método ResumeBookmark se puede usar en la clase WorkflowApplication para enviar datos al marcador con nombre y indicar bookmarkCallback.
public class ReadLine : NativeActivity<cadena>
{
public OutArgument<cadena> InputText { get; set; }
protected override void Execute(NativeActivityContext context)
{
contexto. CreateBookmark("ReadLine",
new BookmarkCallback(BookmarkResumed));
}
private void BookmarkResumed(NativeActivityContext context,
Marcador bk, estado de objeto)
{
Result.Set(context, state);
}
}
Figura 37: Crear un marcador
Otra característica eficaz para los autores de actividades es el concepto de ActivityAction. ActivityAction es el equivalente de flujo de trabajo de la clase Action en código imperativo: describir un delegado común; pero para algunos, la idea de una plantilla puede ser más fácil de entender. Considere la actividad de<> T de ForEach, que permite iterar en un conjunto de datos y programar una actividad secundaria para cada elemento de datos. Necesita alguna manera de permitir que el consumidor de la actividad defina el cuerpo y pueda consumir el elemento de datos de tipo T. Básicamente, necesita alguna actividad que pueda aceptar un elemento de tipo T y actuar en él, ActivityAction<T> se usa para habilitar este escenario y proporciona una referencia al argumento y la definición de una actividad para procesar el elemento. Puede usar ActivityAction en las actividades personalizadas para permitir que los consumidores de la actividad agreguen sus propios pasos en los puntos adecuados. Esto le permite crear plantillas de flujo de trabajo o actividad de una ordenación, donde un consumidor puede rellenar las partes pertinentes para personalizar la ejecución para su uso. También puede usar el> ActivityFunc<TResult y las alternativas relacionadas cuando el delegado que la actividad necesita invocar devolverá un valor.
Por último, las actividades se pueden validar para asegurarse de que están configuradas correctamente comprobando la configuración individual, restringiendo las actividades secundarias permitidas, etc. Para la necesidad común de requerir un argumento determinado, puede agregar un atributo RequiredArgument a la declaración de propiedad en la actividad. Para una validación más implicada, en el constructor de la actividad, cree una restricción y agréguela a la colección de restricciones expuestas en la clase Activity. Una restricción es una actividad escrita para inspeccionar la actividad de destino y garantizar la validez. En la figura 38 se muestra el constructor de la actividad Iterator, que valida que la propiedad RequestedIterations está establecida. Por último, invalidando el método CacheMetadata, puede invocar el método AddValidationError en el parámetro ActivityMetdata para agregar errores de validación para la actividad.
var act = new DelegateInArgument<Iterator> { Name = "constraintArg" };
var vctx = new DelegateInArgument<ValidationContext>();
Restricción<iterador> cons = nueva restricción<iterador>
{
Body = new ActivityAction<Iterator, ValidationContext>
{
Argument1 = act,
Argument2 = vctx,
Controlador = new AssertValidation
{
Message = "Se debe proporcionar la iteración",
PropertyName = "RequestedIterations",
Aserción = new InArgument<bool>(
(e) => acto. Get(e). RequestedIterations != null)
}
}
};
base. Constraints.Add(cons);
Figura 38: Restricciones
Diseñadores de actividad
Una de las ventajas de WF es que permite programar la lógica de la aplicación de forma declarativa, pero la mayoría de las personas no quieren escribir XAML a mano, por lo que la experiencia del diseñador en WF es tan importante. Al crear actividades personalizadas, es probable que también quiera crear un diseñador para proporcionar la experiencia de interacción visual y de visualización para los consumidores de la actividad. El diseñador de WF se basa en Windows Presentation Foundation, lo que significa que tiene todo el poder de aplicar estilos, desencadenadores, enlace de datos y todas las demás herramientas excelentes para crear una interfaz de usuario enriquecida para el diseñador. Además, WF proporciona algunos controles de usuario que puede usar en el diseñador para simplificar la tarea de mostrar una actividad secundaria individual o una colección de actividades. Los cuatro controles principales son:
- ActivityDesigner: control WPF raíz usado en diseñadores de actividad
- WorkflowItemPresenter: se usa para mostrar una sola actividad
- WorkflowItemsPresenter: se usa para mostrar una colección de actividades secundarias
- ExpressionTextBox: se usa para habilitar la edición local de expresiones como argumentos.
WF4 contiene una plantilla de proyecto ActivityDesignerLibrary y una plantilla de elemento ActivityDesigner que se pueden usar para crear inicialmente los artefactos. Una vez inicializado el diseñador, puede empezar a usar los elementos anteriores para personalizar la apariencia de la actividad mediante la presentación de los datos y representaciones visuales de las actividades secundarias. Dentro del diseñador, tiene acceso a un ModelItem que es una abstracción sobre la actividad real y que muestra todas las propiedades de la actividad.
El control WorkflowItemPresenter se puede usar para mostrar una sola actividad que forma parte de la actividad. Por ejemplo, para proporcionar la capacidad de arrastrar una actividad a la actividad iterador mostrada anteriormente y mostrar la actividad contenida en la actividad iteractor, puedes usar el XAML que se muestra en la figura 39, enlazando WorkflowItemPresenter al ModelItem.Body. En la figura 40 se muestra el diseñador en uso en una superficie de diseño de flujo de trabajo .
<sap:ActivityDesigner x:Class="CustomActivities.Presentation.IteratorDesigner"
xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sap="clr-namespace:System.Activities.Presentation;
assembly=System.Activities.Presentation"
xmlns:sapv="clr-namespace:System.Activities.Presentation.View;
assembly=System.Activities.Presentation">
> grid de <
<Border BorderBrush="MidnightBlue" BorderThickness="3" CornerRadius="10">
<sap:WorkflowItemPresenter MinHeight="50"
Item="{Binding Path=ModelItem.Body, Mode=TwoWay}"
HintText="Agregar cuerpo aquí" />
</Border>
</Grid>
</sap:ActivityDesigner>
Figura 39: WorkflowItemPresenter en el diseñador de actividad
Figura 40: del diseñador de iteradores
WorkflowItemsPresenter proporciona una funcionalidad similar para mostrar varios elementos, como los elementos secundarios de una secuencia. Puede definir una plantilla y orientación para cómo desea que se muestren los elementos, así como una plantilla de espaciador para agregar elementos visuales entre actividades como conectores, flechas o algo más interesante. En la figura 41 se muestra un diseñador sencillo para una actividad de secuencia personalizada que muestra las actividades secundarias con una línea horizontal entre cada una.
<sap:ActivityDesigner x:Class="CustomActivities.Presentation.CustomSequenceDesigner"
xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sap="clr-namespace:System.Activities.Presentation;
assembly=System.Activities.Presentation"
xmlns:sapv="clr-namespace:System.Activities.Presentation.View;
assembly=System.Activities.Presentation">
> grid de <
> <StackPanel
<BorderBrush="Goldenrod" BorderThickness="3">
<sap:WorkflowItemsPresenter HintText="Drop Activities Here"
Items="{Binding Path=ModelItem.ChildActivities}">
<sap:WorkflowItemsPresenter.SpacerTemplate>
< > DataTemplate
<Rectangle Height="3" Width="40"
Fill="MidnightBlue" Margin="5" />
</DataTemplate>
</sap:WorkflowItemsPresenter.SpacerTemplate>
<sap:WorkflowItemsPresenter.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</sap:WorkflowItemsPresenter.ItemsPanel>
</sap:WorkflowItemsPresenter>
</Border>
</StackPanel>
</Grid>
</sap:ActivityDesigner>
Figura 41: Diseñador de secuencias personalizado con WorkflowItemsPresenter
Figura 42: del diseñador de secuencias
Por último, el control ExpressionTextBox habilita la edición local de argumentos de actividad dentro del diseñador además de la cuadrícula de propiedades. En Visual Studio 2010, esto incluye compatibilidad con IntelliSense para las expresiones que se escriben. En la figura 43 se muestra un diseñador para la actividad GetManager creada anteriormente, lo que permite la edición de los argumentos EmployeeName y ManagerEmail dentro del diseñador. El diseñador real se muestra en la figura 44.
<sap:ActivityDesigner x:Class="CustomActivities.Presentation.GetManagerDesigner"
xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sap="clr-namespace:System.Activities.Presentation;
assembly=System.Activities.Presentation"
xmlns:sapv="clr-namespace:System.Activities.Presentation.View;
assembly=System.Activities.Presentation"
xmlns:sapc="clr-namespace:System.Activities.Presentation.Converters;
assembly=System.Activities.Presentation">
<sap:ActivityDesigner.Resources>
<sapc:ArgumentToExpressionConverter
x:Key="ArgumentToExpressionConverter"
x:Uid="swdv:ArgumentToExpressionConverter_1" />
</sap:ActivityDesigner.Resources>
> grid de <
< > Grid.ColumnDefinitions
<ColumnDefinition Width="50" />
<ColumnDefinition Width="*" />
< > /Grid.ColumnDefinitions
< > Grid.RowDefinitions
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
< > /Grid.RowDefinitions
<TextBlock VerticalAlignment="Center"
HorizontalAlignment="Center">Employee:</TextBlock>
<sapv:ExpressionTextBox Grid.Column="1"
Expression="{Binding Path=ModelItem.EmployeeName, Mode=TwoWay,
Converter={StaticResource ArgumentToExpressionConverter},
ConverterParameter=In}" OwnerActivity="{Binding Path=ModelItem}"
MinLines="1" MaxLines="1" MinWidth="50"
HintText="< Nombre del empleado>" />
<TextBlock Grid.Row="1" VerticalAlignment="Center"
HorizontalAlignment="Center">Correo electrónico de mgr:</TextBlock>
<sapv:ExpressionTextBox Grid.Column="2" Grid.Row="1"
UseLocationExpression="True"
Expression="{Binding Path=ModelItem.ManagerEmail, Mode=TwoWay,
Converter={StaticResource ArgumentToExpressionConverter},
ConverterParameter=Out}" OwnerActivity="{Binding Path=ModelItem}"
MinLines="1" MaxLines="1" MinWidth="50" />
</Grid>
</sap:ActivityDesigner>
Figura 43: Usar ExpressionTextBox para editar argumentos en el diseñador
Figura 44: del diseñador de actividad GetManager
Los diseñadores de ejemplo que se presentan aquí eran sencillos de resaltar las características específicas, pero la potencia completa de WPF está a su disposición para que sus actividades sean más fáciles de configurar y más naturales para su uso para los consumidores. Una vez creado un diseñador, hay dos maneras de asociarlo a la actividad: aplicar un atributo a la clase de actividad o implementar la interfaz IRegisterMetadata. Para permitir que la definición de actividad impulse la elección del diseñador, simplemente aplique DesignerAttribute a la clase de actividad y proporcione el tipo para el diseñador; esto funciona exactamente como hizo en WF3. Para una implementación más flexible, puede implementar la interfaz IRegisterMetadata y definir los atributos que desea aplicar a la clase de actividad y las propiedades. En la figura 45 se muestra una implementación de ejemplo para aplicar los diseñadores para dos actividades personalizadas. Visual Studio detectará la implementación e la invocará automáticamente, mientras que un host de diseñador personalizado, descrito a continuación, llamaría explícitamente al método .
public class Metadata : IRegisterMetadata
{
public void Register()
{
AttributeTableBuilder builder = new AttributeTableBuilder();
constructor. AddCustomAttributes(typeof(SendMail), new Attribute[] {
new DesignerAttribute(typeof(SendMailDesigner))});
constructor. AddCustomAttributes(typeof(GetManager), new Attribute[]{
new DesignerAttribute(typeof(GetManagerDesigner))});
MetadataStore.AddAttributeTable(builder. CreateTable());
}
}
Figura 45: Registro de diseñadores para actividades
Rehospedaje del diseñador de flujo de trabajo
En el pasado, los desarrolladores a menudo han querido proporcionar a sus usuarios la capacidad de crear o editar flujos de trabajo. En versiones anteriores de Windows Workflow Foundation, esto era posible, pero no una tarea trivial. Con el traslado a WPF viene un nuevo diseñador y el rehospedaje era un caso de uso ideal para el equipo de desarrollo. Puedes hospedar el diseñador en una aplicación WPF personalizada como la que se muestra en la figura 46 con unas pocas líneas de CÓDIGO XAML y algunas líneas de código.
Figura 46: diseñador de flujos de trabajo rehospedados
El XAML de la ventana, figura 47, usa una cuadrícula para diseñar tres columnas y, a continuación, coloca un control de borde en cada celda. En la primera celda, el cuadro de herramientas se crea mediante declaración, pero también se puede crear en el código. Para la propia vista del diseñador y la cuadrícula de propiedades, hay dos controles de borde vacíos en cada una de las celdas restantes. Estos controles se agregan en el código.
<Ventana x:Class="WorkflowEditor.MainWindow"
xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System; assembly=mscorlib"
xmlns:sapt="clr-namespace:System.Activities.Presentation.Toolbox;
assembly=System.Activities.Presentation"
Title="Editor de flujo de trabajo" Height="500" Width="700" >
<Window.Resources>
<sys:String x:Key="AssemblyName">System.Activities, Version=4.0.0.0,
Culture=neutral, PublicKeyToken=31bf3856ad364e35</sys:String>
<sys:String x:Key="MyAssemblyName">CustomActivities</sys:String>
</Window.Resources>
<Grid x:Name="DesignerGrid">
< > Grid.ColumnDefinitions
<ColumnDefinition Width="2*" />
<ColumnDefinition Width="7*" />
<ColumnDefinition Width="3*" />
< > /Grid.ColumnDefinitions
> de borde de <
<sapt:ToolboxControl>
< > sapt:ToolboxControl.Categories
<sapt:ToolboxCategory CategoryName="Basic">
<sapt:ToolboxItemWrapper
AssemblyName="{StaticResource AssemblyName}" >
<sapt:ToolboxItemWrapper.ToolName>
System.Activities.Statements.Sequence
</sapt:ToolboxItemWrapper.ToolName>
</sapt:ToolboxItemWrapper>
<sapt:ToolboxItemWrapper
AssemblyName="{StaticResource MyAssemblyName}">
<sapt:ToolboxItemWrapper.ToolName>
CustomActivities.SendMail
</sapt:ToolboxItemWrapper.ToolName>
</sapt:ToolboxItemWrapper>
</sapt:ToolboxCategory>
</sapt:ToolboxControl.Categories>
</sapt:ToolboxControl>
</Border>
<Border Name="DesignerBorder" Grid.Column="1"
BorderBrush="Salmon" BorderThickness="3" />
<Border x:Name="PropertyGridExpander" Grid.Column="2" />
</Grid>
</Window>
Figura 47: Rehospedaje del diseñador XAML
El código para inicializar el diseñador y la cuadrícula de propiedades se muestra en la figura 48. El primer paso, en el método OnInitialized, es registrar los diseñadores para todas las actividades que se usarán en el diseñador de flujo de trabajo. La clase DesignerMetadata registra los diseñadores asociados para las actividades que se envían con el marco y la clase Metadata registra los diseñadores para mis actividades personalizadas. A continuación, cree la clase WorkflowDesigner y use las propiedades View y PropertyInspectorView para acceder a los objetos UIElement necesarios para representar. El diseñador se inicializa con una secuencia vacía, pero también puedes agregar menús y cargar el XAML del flujo de trabajo desde una variedad de orígenes, incluido el sistema de archivos o una base de datos.
public MainWindow()
{
InitializeComponent();
}
protected override void OnInitialized(EventArgs e) {
base. OnInitialized(e);
RegisterDesigners();
PlaceDesignerAndPropGrid();
}
private void RegisterDesigners() {
new DesignerMetadata(). Register();
new CustomActivities.Presentation.Metadata(). Register();
}
private void PlaceDesignerAndPropGrid() {
WorkflowDesigner designer = new WorkflowDesigner();
diseñador. Load(new System.Activities.Statements.Sequence());
DesignerBorder.Child = diseñador. Vista;
PropertyGridExpander.Child = diseñador. PropertyInspectorView;
}
Figura 48: de código de rehospedaje del diseñador
Con este poco de código y marcado, puede editar un flujo de trabajo, arrastrar y colocar actividades desde el cuadro de herramientas, configurar variables en el flujo de trabajo y manipular argumentos en las actividades. También puedes obtener el texto del XAML llamando a Flush en el diseñador y haciendo referencia a la propiedad Text del WorkflowDesigner. Hay mucho más que puede hacer y la introducción es mucho más fácil que antes.
Servicios de flujo de trabajo
Como se mencionó anteriormente, una de las grandes áreas de enfoque de esta versión de Windows Workflow Foundation es una integración más estrecha con Windows Communication Foundation. En el ejemplo del tutorial de actividad se muestra cómo llamar a un servicio desde un flujo de trabajo y en esta sección se explica cómo exponer un flujo de trabajo como un servicio WCF.
Para empezar, cree un nuevo proyecto y seleccione el proyecto servicio de flujo de trabajo de WCF. Una vez completada la plantilla, encontrarás un proyecto con un archivo web.config y un archivo con una extensión XAMLX. El archivo XAMLX es un servicio declarativo o un servicio de flujo de trabajo y, al igual que otras plantillas de WCF, proporciona un funcionamiento, aunque simple, implementación de servicio que recibe una solicitud y devuelve una respuesta. En este ejemplo, cambiaré el contrato para crear una operación para recibir un informe de gastos y devolver un indicador de que se ha recibido el informe. Para empezar, agregue una clase ExpenseReport al proyecto como la que se muestra en la figura 49.
[DataContract]
public class ExpenseReport
{
[DataMember]
public DateTime FirstDateOfName { get; set; }
[DataMember]
public double TotalAmount { get; set; }
[DataMember]
public string EmployeeName { get; set; }
}
Figura 49: tipo de contrato de informe de gastos
De nuevo en el diseñador de flujo de trabajo, cambie el tipo de la variable "data" de las variables al tipo ExpenseReport que acaba de definir (es posible que tenga que compilar el proyecto para que el tipo se muestre en el cuadro de diálogo del selector de tipos). Actualice la actividad Receive haciendo clic en el botón Contenido y cambiando el tipo del cuadro de diálogo al tipo ExpenseReport para que coincida. Actualice las propiedades de actividad para definir un nuevo OperationName y ServiceContractName como se muestra en la figura 50.
Figura 50: Configuración de la ubicación de recepción
Ahora la actividad SendReply puede tener el valor establecido en True para devolver el indicador de recepción. Obviamente, en un ejemplo más complicado, podría usar toda la eficacia del flujo de trabajo para guardar información en la base de datos, realizar la validación del informe, etc. antes de determinar la respuesta. La exploración al servicio con la aplicación WCFTestClient muestra cómo la configuración de actividad define el contrato de servicio expuesto como se muestra en la figura 51.
Figura 51: Contrato generado a partir del servicio de flujo de trabajo
Una vez que haya creado un servicio de flujo de trabajo, debe hospedarlo, igual que lo haría con cualquier servicio WCF. En el ejemplo anterior se usó la opción más sencilla para hospedar: IIS. Para hospedar un servicio de flujo de trabajo en su propio proceso, querrá usar la clase WorkflowServiceHost que se encuentra en el ensamblado System.ServiceModel.Activities. Tenga en cuenta que hay otra clase con este mismo nombre en el ensamblado System.WorkflowServices que se usa para hospedar servicios de flujo de trabajo creados con las actividades WF3. En el caso más sencillo de autohospedaje, puede usar código como el que se muestra en la figura 52 para hospedar el servicio y agregar puntos de conexión.
Host workflowServiceHost = nuevo
WorkflowServiceHost(XamlServices.Load("ExpenseReportService.xamlx"),
new Uri("https://localhost:9897/Services/Expense"));
anfitrión. AddDefaultEndpoints();
anfitrión. Description.Behaviors.Add(
new ServiceMetadataBehavior { HttpGetEnabled = true });
anfitrión. Open();
Console.WriteLine("Host is open");
Console.ReadLine();
Figura 52: Autohospedaje del servicio de flujo de trabajo
Si quiere aprovechar las opciones de hospedaje administradas en Windows, puede hospedar el servicio en IIS copiando el archivo XAMLX en el directorio y especificando cualquier información de configuración necesaria para comportamientos, enlaces, puntos de conexión, etc. en el archivo web.config. De hecho, como se ha visto anteriormente, al crear un nuevo proyecto en Visual Studio mediante una de las plantillas de proyecto del servicio de flujo de trabajo declarativo, obtendrá un proyecto con un archivo web.config y el servicio de flujo de trabajo definido en XAMLX, listo para implementarse.
Al usar la clase WorkflowServiceHost para hospedar el servicio de flujo de trabajo, debe poder configurar y controlar las instancias de flujo de trabajo. Para controlar el servicio hay un nuevo punto de conexión estándar denominado WorkflowControlEndpoint. Una clase complementaria, WorkflowControlClient, proporciona un proxy de cliente pregenerado. Puede exponer un WorkFlowControlEndpoint en el servicio y usar WorkflowControlClient para crear y ejecutar nuevas instancias de flujos de trabajo o controlar los flujos de trabajo en ejecución. Este mismo modelo se usa para administrar servicios en el equipo local o puede usarlos para administrar servicios en una máquina remota. En la figura 53 se muestra un ejemplo actualizado de exposición del punto de conexión de control de flujo de trabajo en el servicio.
Host workflowServiceHost = nuevo
WorkflowServiceHost("ExpenseReportService.xamlx",
new Uri("https://localhost:9897/Services/Expense"));
anfitrión. AddDefaultEndpoints();
WorkflowControlEndpoint wce = new WorkflowControlEndpoint(
new NetNamedPipeBinding(),
new EndpointAddress("net.pipe://localhost/Expense/WCE"));
anfitrión. AddServiceEndpoint(wce);
anfitrión. Open();
Figura 53: punto de conexión de control de flujo de trabajo
Dado que no va a crear WorkflowInstance directamente, hay dos opciones para agregar extensiones al entorno de ejecución. La primera es que puede agregar algunas extensiones a WorkflowServiceHost y se inicializarán correctamente con las instancias de flujo de trabajo. La segunda es usar la configuración. El sistema de seguimiento, por ejemplo, se puede definir completamente en el archivo de configuración de la aplicación. Los participantes de seguimiento y los perfiles de seguimiento se definen en el archivo de configuración y se usa un comportamiento, se aplica un participante determinado a un servicio como se muestra en la figura 54.
<system.serviceModel>
< > de seguimiento
<participantes>
<add name="EtwTrackingParticipant"
type="System.Activities.Tracking.EtwTrackingParticipant, System.Activities, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
profileName="HealthMonitoring_Tracking_Profile"/>
</participants>
perfiles de <>
<trackingProfile name="HealthMonitoring_Tracking_Profile">
<actividad de flujo de trabajoDefinitionId="*">
<workflowInstanceQuery>
<estados>
<state name="Started"/>
<state name="Completed"/>
</states>
</workflowInstanceQuery>
</workflow>
< > /trackingProfile
</profiles>
</tracking>
. . .
<comportamientos>
< > serviceBehaviors
<behavior name="SampleTrackingSample.SampleWFBehavior">
<etwTracking profileName=" HealthMonitoring_Tracking_Profile" />
</behavior>
< > /serviceBehaviors
</behaviors>
. . .
Figura 54: Configuración del seguimiento de servicios
Hay muchas mejoras nuevas en el hospedaje y la configuración de los servicios WCF que puede aprovechar en los flujos de trabajo, como los servicios ".svc-less", o los servicios que no necesitan una extensión de archivo, enlaces predeterminados y puntos de conexión predeterminados solo para nombrar algunos. Para obtener más información sobre estas y otras características de WCF4, consulte el documento complementario sobre WCF que se encuentra en la sección Recursos adicionales.
Además de las mejoras de hospedaje, Windows Workflow Foundation aprovecha otras características de WCF; algunos directamente y otros indirectamente. Por ejemplo, la correlación de mensajes es ahora una característica clave en la creación de servicios que permite relacionar mensajes con una instancia de flujo de trabajo determinada en función de los datos del mensaje, como un número de pedido o un identificador de empleado. La correlación se puede configurar en las diversas actividades de mensajería para la solicitud y la respuesta y admite la correlación de varios valores en el mensaje. De nuevo, puede encontrar más detalles sobre estas nuevas características en el documento complementario sobre WCF4.
Conclusión
La nueva tecnología de Windows Workflow Foundation que se incluye con .NET Framework 4 representa una mejora importante en el rendimiento y la productividad del desarrollador y se da cuenta de un objetivo de desarrollo de aplicaciones declarativos para la lógica de negocios. El marco proporciona las herramientas para que las unidades cortas de trabajo complicado se simplificaran y para crear servicios complejos de larga duración con mensajes correlacionados, estado persistente y visibilidad enriquecida de la aplicación a través del seguimiento.
Acerca del autor
Matt es miembro del personal técnico de Pluralsight, donde se centra en las tecnologías de sistemas conectados (WCF, WF, BizTalk, AppFabric y la Plataforma de servicios de Azure). Matt también es consultor independiente especializado en el diseño y desarrollo de aplicaciones de Microsoft .NET. Como escritor, Matt ha contribuido a varias revistas y revistas, como MSDN Magazine, donde actualmente crea el contenido de flujo de trabajo para la columna Foundations. Matt comparte regularmente su amor por la tecnología hablando en conferencias locales, regionales e internacionales como Tech Ed. Microsoft ha reconocido a Matt como MVP por sus contribuciones de la comunidad en torno a la tecnología de sistemas conectados. Póngase en contacto con Matt a través de su blog: http://www.pluralsight.com/community/blogs/matt/default.aspx.