Tag Archives: Workflow Foundation

Agregar actividades en tiempo de ejecución a un flujo de trabajo (desde el mismo flujo de trabajo) y validar dichos cambios

0
Filed under .NET Development, Windows Workflow Foundation (WF)

Cuando aplicamos cambios en tiempo de ejecución a un flujo de trabajo, puede que estos cambios contengan errores. Para demostrar esto vamos a crear un nuevo proyecto de tipo Sequential Workflow Console Application al que llamaremos WorkflowSample3.

Arrastramos sobre el diseñador visual de flujos de trabajo una instancia de la actividad CodeActivity y la soltamos en la superficie de diseño. Hacemos clic derecho sobre codeActivity1 y seleccionamos la opción de menú "Generate Handlers".

Igual al artículo anterior ("Agregar actividades en tiempo de ejecución a un flujo de trabajo"), vamos a proceder a crear una instancia de la clase WorkflowChanges y agregar una actividad.

En la vista de código para Workflow1.cs, buscamos el método codeActivity1_ExecuteCode y ponemos lo siguiente:

        private void codeActivity1_ExecuteCode(object sender, EventArgs e)
        {
            WorkflowChanges lWorkflowChanges = new WorkflowChanges(this);

            CodeActivity lCodeActivity = new CodeActivity();

            lWorkflowChanges.TransientWorkflow.Activities.Add(lCodeActivity);

            this.ApplyWorkflowChanges(lWorkflowChanges);
        }

Como verán, al constructor de WorkflowChanges le pasamos "this" ya que la actividad raíz es el mismo flujo de trabajo en el que estamos codificando (Workflow1 hereda de SequentialWorkflowActivity). Luego, instanciamos y agregamos a las actividades del flujo de trabajo una instancia de la clase CodeActivity. Por último, llamamos al método ApplyWorkflowChanges (también de Workflow1).

Si ejecutamos la aplicación así como está, obtendremos el siguiente error:

[Haga clic en la imagen para ampliar]


Esto se debe a que en este caso, CodeActivity requiere que manejemos el evento ExecuteCode (e intencionalmente no lo hicimos).

Modificamos el código del método codeActivity1_ExecuteCode dejándolo de la siguiente manera:

        private void codeActivity1_ExecuteCode(object sender, EventArgs e)
        {
            WorkflowChanges lWorkflowChanges = new WorkflowChanges(this);

            CodeActivity lCodeActivity = new CodeActivity();

            lWorkflowChanges.TransientWorkflow.Activities.Add(lCodeActivity);

            ValidationErrorCollection lValidationErrorCollection =
                lWorkflowChanges.Validate();
            if (lValidationErrorCollection.Count > 0)
            {
                foreach (ValidationError lValidationError in lValidationErrorCollection)
                {
                    Console.WriteLine(lValidationError.ErrorText);
                }
            }
            else
            {
                this.ApplyWorkflowChanges(lWorkflowChanges);
            }
        }

Si ejecutamos la aplicación, vamos a ver que no tira excepción pero nos deja el siguiente mensaje:

[Haga clic en la imagen para ampliar]


El método Validate de WorkflowChanges valida los cambios que proponemos a través de la propiedad TransientWorkflow y devuelve una colección de tipo ValidationErrorCollection.

Agregar actividades en tiempo de ejecución a un flujo de trabajo

0
Filed under .NET Development, Windows Workflow Foundation (WF)

Abrimos Visual Studio y creamos un nuevo proyecto de tipo Sequential Workflow Console Application al que llamaremos WorkflowSample2. Por defecto, Visual Studio nos deja en el diseñador visual de flujos de trabajo para Workflow1.cs. Si no es así, vamos al diseñador para Workflow1.cs. Agregamos las siguientes actividades:

- Tipo: CodeActivity – Nombre: codeActivity1 – Descripción: La vamos a utilizar para escribir la cadena "Actividad 1" a la salida de la consola.
- Tipo: SuspendActivity – Nombre: suspendActivity1 – Descripción: SuspendActivity suspende la ejecución de un flujo de trabajo. WorkflowRuntime levantará el evento WorkflowSuspended avisando que la instancia del flujo de trabajo ha entrado en estado de suspensión, lo que nos va a permitir hacer modificaciones al flujo de trabajo y luego resumir la ejecución.
- Tipo: SequenceActivity – Nombre: sequenceActivity1 – Descripción: SequenceActivity es una actividad compuesta que ejecuta actividades hijas en el orden en que fueron agregadas. En este caso, vamos a utilizarla como "placeholder" para agregar la nueva actividad.
- Tipo: CodeActivity – Nombre: codeActivity2 – Descripción: La vamos a utilizar para escribir la cadena "Actividad 3" a la salida de la consola.

[Haga clic en la imagen para ampliar]


Hacemos clic derecho sobre codeActivity1 y seleccionamos la opción de menú "Generate Handlers" (que nos crea un nuevo manejador para el evento ExecuteCode). Volvemos al diseñador y repetimos el mismo paso pero para la actividad codeActivity2.

[Haga clic en la imagen para ampliar]


Nos vamos a la vista de código y en los métodos generados (codeActivity1_ExecuteCode y codeActivity2_ExecuteCode) ponemos lo siguiente:

        private void codeActivity1_ExecuteCode(object sender, EventArgs e)
        {
            Console.WriteLine("Actividad 1");
        }

        private void codeActivity2_ExecuteCode(object sender, EventArgs e)
        {
            Console.WriteLine("Actividad 3");
        }

Hecho esto, agregamos una nueva clase al proyecto que llamaremos "EscribirMensajeActivity". Cambiamos la declaración de la clase para que sea pública y la hacemos heredar de Activity (System.Workflow.ComponentModel.Activity).

Activity es la clase base para todas las actividades. El método principal en el que ponemos la lógica de ejecución es Execute y es llamado por el runtime de flujos de trabajo.

Sobrescribimos entonces el método Execute y ponemos lo siguiente:

        protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
        {
            Console.WriteLine(Mensaje);
            return ActivityExecutionStatus.Closed;
        }

Tenemos que crear un campo de tipo string llamado _Mensaje que va a servir como contenedor para la cadena que se va a escribir a la salida de la consola. Luego, creamos la propiedad para el campo _Mensaje.

        private string _Mensaje;

        public string Mensaje
        {
            get { return _Mensaje; }
            set { _Mensaje = value; }
        }

Por último vamos a hacer unas modificaciones al código que Visual Studio generó en Program.cs. Nos suscribimos al evento WorkflowSuspended de workflowRuntime:

        static void Main(string[] args)
        {
            using (WorkflowRuntime workflowRuntime = new WorkflowRuntime())
            {
                AutoResetEvent waitHandle = new AutoResetEvent(false);
                workflowRuntime.WorkflowCompleted += delegate(object sender, WorkflowCompletedEventArgs e) { waitHandle.Set(); };
                workflowRuntime.WorkflowTerminated += delegate(object sender, WorkflowTerminatedEventArgs e)
                {
                    Console.WriteLine(e.Exception.Message);
                    waitHandle.Set();
                };

                workflowRuntime.WorkflowSuspended +=
                    new EventHandler<WorkflowSuspendedEventArgs>(workflowRuntime_WorkflowSuspended);

            ... 

En el método encargado de manejar el evento WorkflowSuspended (workflowRuntime_WorkflowSuspended) ponemos lo siguiente:

        static void workflowRuntime_WorkflowSuspended(object sender, WorkflowSuspendedEventArgs e)
        {
            WorkflowChanges lWorkflowChanges =
                new WorkflowChanges(e.WorkflowInstance.GetWorkflowDefinition());

            EscribirMensajeActivity lEscribirMensajeActivity = new EscribirMensajeActivity();
            lEscribirMensajeActivity.Mensaje = "Actividad 2";
            ((CompositeActivity)lWorkflowChanges.TransientWorkflow.Activities["sequenceActivity1"]).
                Activities.Add(lEscribirMensajeActivity);

            e.WorkflowInstance.ApplyWorkflowChanges(lWorkflowChanges);
            e.WorkflowInstance.Resume();
        }

Cuando queremos hacer cambios a un flujo de trabajo, los tenemos que hacer utilizando la clase WorkflowChanges que toma como parámetro la actividad raíz. En este caso la actividad raíz para la instancia del flujo de trabajo que queremos modificar la sacamos con el método GetWorkflowDefinition de WorkflowInstance que lo tenemos en "e" (WorkflowSuspendedEventArgs). Después, creamos una nueva instancia de nuestra actividad (EscribirMensajeActivity) y establecemos la propiedad Mensaje.

La propiedad TransientWorkflow de WorkflowChanges devuelve una copia exacta de la estructura del flujo de trabajo que vamos a utilizar para ubicar la actividad sequenceActivity1 que convertiremos a CompositeActivity para poder agregar la actividad hija.

Una vez agregada la actividad hija, llamamos al método ApplyWorkflowChanges de WorkflowInstance pasando la instancia de WorkflowChanges. Finalmente, llamamos al método Resume para continuar con la ejecución del flujo de trabajo.

Compilamos y ejecutamos. Deberíamos ver lo siguiente:

[Haga clic en la imagen para ampliar]

WorkflowRuntime + WorkflowInstance

0
Filed under .NET Development, Windows Workflow Foundation (WF)

Como dije en la introducción, uno de los trabajos que WorkflowRuntime tiene es recibir y dar notificaciones. Recibe notificaciones desde las instancias de flujos de trabajo (WorkflowInstance) que está ejecutando. Nos provee con eventos que podemos utilizar para procesar esas notificaciones que recibió y también para procesar notificaciones que él mismo emite. Podemos separar los eventos que WorkflowRuntime expone en dos partes:

Eventos propios de WorkflowRuntime:
Started – El motor de flujos de trabajo inició. Podemos iniciar el motor de flujos de trabajo llamando al método StartRuntime de WorkflowRuntime o iniciando una instancia de un flujo de trabajo. Cuando llamamos al método Start de WorkflowInstance, si el motor de flujos de trabajo no está en ejecución, automáticamente la instancia lo inicia. Además, se inician los servicios y se establece la propiedad IsStarted (de WorkflowRuntime) en True.
Stopped – El motor de flujos de trabajo se detuvo. Esto pasa si llamamos al método StopRuntime de WorkflowRuntime. Cuando llamamos a StopRuntime, el motor detiene todos los servicios que tiene asociados.

Eventos que WorkflowRuntime recibe para después notificarlos:
WorkflowAborted – Notifica cuando una instancia de un flujo de trabajo ha sido abortada. Abortamos una instancia en ejecución de un flujo de trabajo llamando al método Abort de WorkflowInstance.
WorkflowCompleted – Notifica cuando una instancia de un flujo de trabajo ha completado de forma normal (eso es, no se llamó a Abort ni se propagó una excepción).
WorkflowCreated – Notifica que se ha creado una instancia de un flujo de trabajo.
WorkflowIdled – Notifica que la instancia de un flujo de trabajo ha entrado en estado de ausencia. Este evento es muy importante. A parte de avisar que una instancia ha entrado en ausencia, si está habilitado el servicio de persistencia aquí podemos llamar a los métodos Unload y TryUnload de WorkflowInstance para persistir la instancia del flujo de trabajo. También es aquí en donde podemos aplicar cambios dinámicos al flujo de trabajo desde fuera de él (por ejemplo, desde la aplicación que lo aloja). Cuando un flujo de trabajo entra en estado de ausencia, va a salir de ese estado por su cuenta, no tenemos que hacer nada. Entra en estado de ausencia con actividades como DelayActivity y HandleExternalEventActivity.
WorkflowLoaded – Notifica que la instancia de un flujo de trabajo que estaba persistida se ha cargado en memoria.
WorkflowPersisted – Notifica que la instancia de un flujo de trabajo ha sido persistida.
WorkflowResumed – Notifica que la ejecución de la instancia de un flujo de trabajo continúa luego de haber estado suspendida. Resumimos la ejecución llamando al método Resume de WorkflowInstance.
WorkflowStarted – Notifica que comenzó la ejecución de una instancia de un flujo de trabajo. Para correr una instancia llamamos al método Start de WorkflowInstance.
WorkflowSuspended – Notifica que se ha suspendido la ejecución de una instancia de un flujo de trabajo. Suspendemos la ejecución llamando al método Suspend de WorkflowInstance. El motor de flujos de trabajo también es capaz de suspender la ejecución por ejemplo cuando tiene que aplicar un cambio. Al suspender la instancia luego podemos reanudarla. No interviene la persistencia ya que no se considera a la instancia como ausente. Podemos utilizar la actividad SuspendActivity (cuando de antemano sabemos que precisamos modificar la instancia del flujo de trabajo) para suspender la ejecución, aplicar un cambio y luego reanudar la ejecución.
WorkflowTerminated – Notifica que la instancia de un flujo de trabajo ha terminado de forma anormal. La aplicación que aloja la instancia del flujo de trabajo pudo haber llamado al método Terminate de WorkflowInstance, puede que haya una actividad TerminateActivity, o se propagó una excepción.
WorkflowUnloaded – Notifica que se descargó la instancia de un flujo de trabajo.

Windows Workflow Foundation – Introducción

0
Filed under .NET Development, Windows Workflow Foundation (WF)

Pero todo esto ¿para qué? ¿Qué es Windows Workflow Foundation? ¿Un flujo de trabajo secuencial? ¿Actividades? ¿WorkflowRuntime?

Como lo dice su nombre, Windows Workflow Foundation es una tecnología que nos permite trabajar con flujos de trabajo. La realidad es que podemos hacer cualquier cosa con WF.

Por ejemplo: tenemos un requisito para la aplicación que estamos construyendo que dice que tenemos que obtener un grupo de personas que está almacenado en una base de datos. Para cada una de esas personas pertenecientes al grupo, enviar un correo electrónico y esperar por la respuesta de cada persona para continuar. Si todos respondieron que están de acuerdo, procedemos a imprimir una factura.

Normalmente crearíamos métodos, clases para obtener las personas, podríamos crear un servicio Web que reciba el identificador de la persona y si quiere aceptar o rechazar. Una vez que tengamos todas las respuestas, analizamos y si las respuestas fueron todas que sí (que están de acuerdo) entonces tendríamos otro método o como sea que imprima la factura.

Todo este procesamiento secuencial a grandes rasgos lo podríamos modelar con WF teniendo una actividad ReplicatorActivity que se encargue de enviar un correo a cada persona. Es más, podríamos crear una actividad personalizada de tipo compuesta que se encargue de esa parte. Siguiente, podríamos exponer nuestro flujo de trabajo como servicio Web utilizando WebServiceInputActivity a la espera de las respuestas de las personas y luego otra actividad personalizada que imprima la factura en el caso que las respuestas sean de aceptación.

Todo esto lo podríamos hacer a mano, pero también utilizando WF. WF tiene la ventaja que corre cada instancia de un flujo de trabajo en un hilo independiente al de nuestra aplicación principal. Además, tiene servicios que nos permiten descargar instancias de un flujo de trabajo cuando están en estado de ausencia (en el ejemplo que di anteriormente, el flujo de trabajo podría entrar en ausencia cuando se encuentra esperando a que se llame al método Web de aceptación o rechazo) y dado un evento externo levantar esas instancias que estaban persistidas (deshidratar y rehidratar) y ponerlas en ejecución nuevamente manteniendo el mismo estado que tenían antes de ser descargadas.

Los servicios y las actividades built-in que vienen con WF facilitan nuestro trabajo.

Utilizamos flujos de trabajo en todo momento. Declaramos dichos flujos de trabajo utilizando un modelo intuitivo que cualquiera puede entender. Por un lado tenemos reglas de negocio y por otro el código implementación que creamos para satisfacer dichas reglas. Hay una clara separación entre el modelo visual y la implementación.

Existen dos tipos de flujos de trabajo que podemos crear: secuenciales y máquina de estados. Los secuenciales ejecutan nuestras actividades en forma de secuencia, primero una, después otra y así sucesivamente. Por el contrario, tenemos los flujos de trabajo de máquina de estados (en otro artículo profundizaremos en ellos). Están diseñados especialmente cuando se requiere de mucha interacción con eventos externos. Los secuenciales tienen como actividad padre a SequentialWorkflowActivity.

SequentialWorkflowActivity es una actividad compuesta. Compuesta quiere decir que acepta actividades hijas. Las actividades compuestas heredan de CompositeActivity (System.Workflow.ComponentModel.CompositeActivity).

Las actividades son bloques de construcción que utilizamos para definir nuestros flujos de trabajo. Es la unidad básica de trabajo.

La definición de un flujo de trabajo es lo que construimos, las actividades que arrastramos, las propiedades que establecemos, etc. La instancia de un flujo de trabajo es lo que está en ejecución (o se va a ejecutar). WorkflowRuntime crea dichas instancias.

WorkflowRuntime es lo que utilizamos para iniciar el motor de flujos de trabajo, agregar los servicios (como el de persistencia por ejemplo), crear instancias de flujos de trabajo, recibir notificaciones desde las instancias de un flujo de trabajo en ejecución y desde el mismo motor. Podemos iniciar el motor sólo una vez por AppDomain.

Creando un simple flujo de trabajo

0
Filed under .NET Development, Windows Workflow Foundation (WF)

Voy a construir un simple flujo de trabajo de tipo secuencial en donde explicaré algunas de las actividades built-in que vienen con WF. Como entorno de desarrollo voy a utilizar Visual Studio 2008.

El flujo de trabajo va a recibir como parámetro una lista de nombres que previamente debemos pasar de la siguiente manera: "WorkflowSample1 Daniel Eduardo Jorge Manuel Santiago" al ejecutar la aplicación. Para cada nombre, vamos a escribir un saludo. La idea es mostrar cómo pasar parámetros a un flujo de trabajo desde la aplicación que lo aloja y además entender para qué sirven las actividades CodeActivity y ReplicatorActivity.

CodeActivity: especificamos un método al que después CodeActivity llamará. Se intenta que el código del método que hayamos especificado sea liviano ya que CodeActivity bloquea la ejecución de la instancia del flujo de trabajo hasta que nuestro método termina de ejecutarse. Si queremos implementar lógica de negocio lo mejor es crear una actividad personalizada.

ReplicatorActivity: dada una colección de objetos que implemente IList, crea y ejecuta sólo una actividad hija tantas veces como objetos haya en la colección. Es similar a la sentencia for each. Tenemos eventos como ChildInitialized que se dispara después que una actividad hija es inicializada y ChildCompleted después que ReplicatorActivity termina de correr la instancia de una actividad hija.

Para aclarar todo esto, abrimos Visual Studio y creamos un nuevo proyecto de tipo Sequential Workflow Console Application al que llamaremos WorkflowSample1. Por defecto, Visual Studio nos deja en el diseñador visual de flujos de trabajo para Workflow1.cs. Si no es así, vamos al diseñador para Workflow1.cs.

Lo primero es arrastrar desde el Toolbox una instancia de la actividad Replicator y colocarla en la superficie de diseño (en donde dice "Drop Activities to create a Sequential Workflow"). Inmediatamente vamos a ver un signo de exclamación que nos indica que la actividad Replicator debe contener una actividad. Todas estas pistas visuales que el diseñador muestra las actividades las proveen creando una clase que derive de la clase ActivityValidator y algunas otras cosas más que en otro artículo escribiré.

[Haga clic en la imagen para ampliar]


Luego, dentro de la actividad Replicator (replicatorActivity1) que acabamos de agregar, agregamos una instancia de la actividad Code, quedando de la siguiente manera.

[Haga clic en la imagen para ampliar]


Ahora podemos observar que replicatorActivity1 no tiene más errores pero sí los tiene codeActivity1, y es que no hemos especificado el método que codeActivity1 tiene que llamar cuando comience su ejecución. Por ahora lo dejamos así.

Vamos a concentrarnos en replicatorActivity1. La aplicación que aloja el flujo de trabajo va a pasar la colección de nombres a una propiedad declarada a nivel de flujo de trabajo a la que replicatorActivity1 estará atada. Entonces para crear esa propiedad y a la vez decirle a replicatorActivity1 que se ate a esa propiedad, vamos a buscar la propiedad InitialChildData de replicatorActivity1 y vamos a hacer clic en el botón "…" que aparece cuando nos paramos sobre InitialChildData en el explorador de propiedades. Una vez hecho esto, nos va a aparecer un cuadro de diálogo llamado "Bind ‘InitialChildData’ to an activity’s property" y va a estar seleccionada la solapa "Bind to an existing member", pero como nosotros no tenemos el miembro (propiedad) ya creado, nos vamos a la solapa "Bind to a new member" que nos va a pedir un nombre de miembro y si queremos crearlo como campo (variable) o propiedad. Vamos a poner de nombre "Nombres" y dejamos seleccionado "Create Property" para crear el nuevo miembro como propiedad.

[Haga clic en la imagen para ampliar]


Bien. Con esto lo que conseguimos es que replicatorActivity1 pueda recorrer cada elemento que haya dentro de la propiedad Nombres que si nos fijamos Visual Studio fue suficientemente inteligente como para darse cuenta que el tipo de dato que da soporte a la propiedad tiene que ser de tipo ‘System.Collections.IList’.

[Haga clic en la imagen para ampliar]


Ahora, nos vamos a las propiedades de replicatorActivity1, seleccionamos para que se muestren los eventos y hacemos doble clic sobre ChildInitialized para crear un manejador de evento. En el manejador de evento que se acaba de crear, vamos a agregar el siguiente código.

        private void replicatorActivity1_ChildInitialized(object sender, ReplicatorChildEventArgs e)
        {
            _Nombre = e.InstanceData.ToString();
        }

Tenemos que recordar que ChildInitialized es disparado por cada elemento que haya en la colección Nombres. Luego que se dispare ChildInitialized, se va a ejecutar la actividad codeActivity1 que va a tener que mostrar el saludo para el nombre actual sobre el que replicatorActivity1 está parado, es por eso que en el método replicatorActivity1_ChildInitialized le asignamos a una variable llamada "_Nombre" que aún no hemos creado, el valor de la propiedad InstanceData que trae ReplicatorChildEventArgs (que nos da el elemento sobre el que replicatorActivity1 está parado). Declaramos la variable "_Nombre" de la siguiente manera:

        private string _Nombre; 

Nos vamos al diseñador visual de flujos de trabajo y para los eventos de codeActivity1 (que tiene uno solo) hacemos doble clic sobre ExecuteCode para crear un manejador de evento. Una vez creado el manejador, lo dejamos de la siguiente manera:

        private void codeActivity1_ExecuteCode(object sender, EventArgs e)
        {
            Console.WriteLine(string.Format("Hola {0}. Buenos días.", _Nombre));
        }

Una vez que el método codeActivity1_ExecuteCode terminó de escribir la cadena a la salida de la consola, la actividad va a cerrar (terminar) y replicatorActivity1 seguirá con el próximo elemento en la lista y volverá a crear una nueva instancia de codeActivity1.

Por último, nos queda pasar los nombres desde la aplicación que aloja al flujo de trabajo a la instancia del flujo de trabajo. Nos vamos al código de Program.cs en donde vamos a ver que Visual Studio agregó código como para iniciar el runtime de flujos de trabajo e iniciar una instancia de Workflow1 (nuestro flujo de trabajo). Le vamos a hacer una modificación. La línea:

        WorkflowInstance instance = workflowRuntime.CreateWorkflow(typeof(WorkflowSample1.Workflow1)); 

La vamos a remplazar por las siguientes:

            Dictionary<string, object> lParametros = new Dictionary<string, object>();
            lParametros.Add("Nombres", args);
            WorkflowInstance instance =
                workflowRuntime.CreateWorkflow(typeof(WorkflowSample1.Workflow1), lParametros); 

El método CreateWorkflow de WorkflowRuntime tiene una sobrecarga que acepta un tipo que va a ser el tipo de flujo de trabajo que vamos a querer ejecutar, y un diccionario del tipo Dictionary<string, object> que va a tener como clave el nombre de la propiedad que intentamos establecer y como valor el valor que queremos establecer para esa propiedad. En este caso para la propiedad "Nombres" que se encuentra definida en el flujo de trabajo vamos a establecer el valor de "args" que son los argumentos que vienen cuando ejecutamos la aplicación consola.

Compilamos y ejecutamos de la siguiente manera: "WorkflowSample1 Daniel Eduardo Jorge Manuel Santiago". Si todo va bien, deberíamos ver lo siguiente:

[Haga clic en la imagen para ampliar]