¡Q tranx niños! Desde mi humilde opinión de los principales beneficios
que ofrece el motor de ejecución de Windows Workflow Foundation es que posee la
capacidad de proveer servicios que tienen como objetivo el simplificar las
tareas comunes a los cuales se enfrenta un desarrollador de flujos de trabajo.
El primero de los servicios que
cubriremos en nuestro estudio ese servicio denominado "Tracking Services" que puede ser utilizados para una gran cantidad
de objetivos.
Podemos definir a Tracking Services como el servicio que
proporciona automáticamente el rastreo y/o seguimiento de la ejecución de
cualquier instancia de un flujo de trabajo. Esta característica es
particularmente importante debido a que en los flujos de trabajo muchas veces
nos interesa saber qué actividades fueron ejecutadas y en general el camino que se siguió el motor
de ejecución para completar determinado
flujo de trabajo.
El panorama en el cual a nosotros
podemos hacer uso de esta característica es realmente amplio: Por ejemplo:
Cuando utilizamos a WorkFlow Foundation como un proveedor de servicios de
orquestación en un panorama de Enterprise Service Bus dentro de una
arquitectura orientada servicios es
sumamente importante conocer los
servicios que fueron invocados desde la orquestación y los usuarios que
utilizaron el sistema, en este escenario podemos utilizar los Tracking
services para monitorear la actividad que se está generando el
momento de ejecutar los flujos de trabajo involucrados en orquestación de los
servicios, en otras palabras la utilización de estos servicios será
efectivamente en los escenarios de auditoría de monitoreo de flujos de trabajo.
Para poder acceder a los
servicios que ofrece Tracking services
hacemos uso del espacio de nombres System.Workflow.Runtime.Tracking que contiene las clases que nos permitirán generar ya
sea a nuestra propia solución de monitoreo y seguimiento de los flujos de trabajo o bien podemos
utilizar soluciones que ya vienen listas para usarse como el SQL tracking
service.
Antes de continuar los
adentraremos un poco al arquitectura de Tracking
Services.
Básicamente existen tres
componentes principales dentro del arquitectura de los Tracking services y estos son:
1.
Tracking Profiles (perfiles de seguimiento)
Los perfiles de seguimiento representan
la manera con la cuál es posible identificar los orígenes y las fuentes de los eventos
que deseamos capturar dentro del monitoreo del flujo de trabajo. Estos perfiles
son indispensables al momento de generar nuestra propia solución de seguimiento.
Existen tres tipos de eventos o sucesos a los cuales nos podemos suscribir para
monitorearlos
a) Workflow Events (Eventos
del flujo de trabajo):
Estos eventos surgen a nivel de instancia y son
equivalentes a los eventos a los cuales nos suscribimos al momento de invocar
un flujo de trabajo. Como ejemplo de estos flujos tenemos los eventos: Created,
Terminated, Suspended etc.
b) Activity Events
(eventos de las actividades)
Estos eventos son generados por las actividades que están
siendo ejecutadas dentro de la instancia del flujo de trabajo.
c) User Events
(Eventos de usuario):
Existen ocasiones en las cuales es
necesario obtener información adicional sobre lo que está sucediendo en un
determinado flujo de trabajo y que no necesariamente corresponde a la situación
de la instancia del mismo flujo de trabajo o de alguna de sus actividades sino
que es necesario generar un suceso personalizado para capturar determinado
comportamiento del flujo de trabajo.
Para cada uno de los eventos
descritos previamente existe una clase específica que guarda información sobre
los mismos. Nos referimos a las clases WorkflowTrackingRecord ActivityTrackingRecord
y UserTrackingRecord
respectivamente
2. Tracking Runtime (Motor de
ejecución de los servicios de seguimiento)
El motor de ejecución de los servicios de seguimiento de
los flujos de trabajo se encarga de iniciarlo servicios de monitoreo que han
sido declarados antes de la invocación de la instancia del flujo de trabajo que
deseamos monitorear para hacer esto utiliza la información encontrada dentro de
los perfiles de seguimiento.
3. Channels
(Canales)
Los canales de
monitoreo se usan para enviar los registros asociados a la instancia del flujo
de trabajo cuando se encuentra un tracking point que es la clase genérica que
recibe información de lo que está sucediendo dentro del flujo de trabajo.
Crearemos a continuación la implementación básica de un
servicio de seguimiento personalizado.
1) Iniciaremos con
una forma a la que llamaremos FormaInicio,
en la cual colocaremos dos etiquetas, una de las cuales usaremos como el
receptor de la información que generan las actividades del flujo de trabajo, y
otra como los mensajes que genera el servicio de seguimiento (Tracking Service)
que generaremos en este ejemplo.
![]()

2) Añadiremos a continuación a la solución un flujo de
trabajo secuencial y agregaremos una actividad del tipo codeActivity
![]()

3) En el código de codeActivity1 simplemente escribiremos en
la etiqueta un mensaje con el siguiente código
C#
|
private
void codeActivity1_ExecuteCode(object sender, EventArgs
e)
{
Programa.FormaInicio.lblMensaje.Text += "MENSAJE DESDE LA CODEACTIVITY1\n";
}
}
|
|
4) Posteriormente vamos a invocar la ejecución del flujo de
trabajo en el manejador de evento del clic del botón de la forma y generaremos
la suscripción al evento WorkFlowCompleted del flujo de trabajo con el código
que aparece a continuación
C#
|
private void
button1_Click(object sender, EventArgs e)
{
WorkflowRuntime
MotorWF = new WorkflowRuntime();
Type
Tipo = typeof(Workflow1);
WorkflowInstance
InstanciaWF = MotorWF.CreateWorkflow(Tipo);
InstanciaWF.Start();
MotorWF.WorkflowCompleted +=
new
EventHandler<WorkflowCompletedEventArgs>(MotorWF_WorkflowCompleted);
}
void
MotorWF_WorkflowCompleted(object sender, WorkflowCompletedEventArgs e)
{
MessageBox.Show("¡El
flujo de trabajo fue finalizado!");
}
}
|
|
Al momento de ejecutar el código previamente mostrado en la
etiqueta que recibe la información generada desde flujo de trabajo deberá
aparecer el mensaje "MENSAJE DESDE LA
CODEACTIVITY1"
5) Hemos llegado al momento en el
cuál propiamente iniciaremos el desarrollo del servicio de tracking. Añadiremos
una nueva clase a nuestro proyecto que denominaremos ServicioSeguimiento. Esta clase deberá heredar de la
clase TrackingService que es la clase
abstracta base que provee la interfase entre un servicio de seguimiento y el motor de ejecución de los
servicios de seguimiento que mencionamos anteriormente en los conceptos de
arquitectura de este capítulo.
Para generar exitosamente una
clase que pueda ser consumida por el motor de seguimiento es necesario sobreescribir
(hacer override) los siguientes miembros de la clase base TrackingService.
-
GetProfile(Guid),
-
GetProfile(Type,
Version)
-
TryGetProfile
-
GetTrackingChannel
-
TryReloadProfile.
El motor de ejecución de los
servicios de seguimiento solicita un objeto del tipo TrackingChannel para cada una de las instancias que
tienen un TrackingProfile asignado y usa este TrackingChannel para mandar los
registros asociados a dar una instancia de flujo de trabajo. Dentro de la definición del TrackingProfile se debe de especificar
cuáles serán los eventos que serán monitoreados y cuya información es recibida
por un TrackingRecord que como vimos
anteriormente, puede tener como clases concretas los tipos: ActivityTrackingRecord, UserTrackingRecord o WorkflowTrackingRecord.
Posteriormente la infraestructura
de WF manda a llamar el método TryReloadProfile para verificar si se debe recargar el Profile.
Este proceso permite a un cliente o a un servicio cambiar un profile de
seguimiento cambiar dinámicamente.
C#
|
public class
ServicioSeguimiento : TrackingService
{
protected
override bool
TryGetProfile(Type workflowType, out TrackingProfile
profile)
{
//El objetivo de
este método es que el motor de seguimiento
//dependiendo del tipo de WF el servicio puede utilizar
diferentes tracking
//profiles. Aunque es necesario hacer el override en
este ejemplo utilizaremos
//para que el ejemplo compile en esta muestra usaremos
//el mismo profile para cualquier WF
profile = ObtenerProfile();
return
true;
}
protected
override TrackingProfile
GetProfile(Guid workflowInstanceId)
{
// No será implementado para este ejemplo
throw new
NotImplementedException("No implementado");
}
|
|
|
protected
override TrackingProfile
GetProfile(Type workflowType, Version profileVersionId)
{
return
ObtenerProfile();
}
protected
override bool
TryReloadProfile(Type workflowType, Guid workflowInstanceId, out TrackingProfile
profile)
{
// En este caso siempre regresamos falso indicando que
no hay nuevos profiles
profile = null;
return
false;
}
protected
override TrackingChannel
GetTrackingChannel(TrackingParameters
parametros)
{
//El motor de WF llama este método para obtener el
canal para la instancia de monitoreo
return new
CanaldeSeguimiento(parametros);
}
private
TrackingProfile ObtenerProfile()
{
//
Creamos un Tracking Profile
TrackingProfile
profile = new TrackingProfile();
profile.Version
= new Version("3.0.0");
// En este
caso monitorearemos específicamente actividades, no otro tipo de eventos
ActivityTrackPoint PuntoSeguimiento = new ActivityTrackPoint();
ActivityTrackingLocation
LocacionActividad = new ActivityTrackingLocation(typeof(Activity));
LocacionActividad.MatchDerivedTypes
= true;
//Con este código se registran todos los posibles
estados
IEnumerable<ActivityExecutionStatus>
estados = Enum.GetValues(typeof(ActivityExecutionStatus))
as IEnumerable<ActivityExecutionStatus>;
foreach
(ActivityExecutionStatus estado in estados)
{
LocacionActividad.ExecutionStatusEvents.Add(estado);
}
PuntoSeguimiento.MatchingLocations.Add(LocacionActividad);
profile.ActivityTrackPoints.Add(PuntoSeguimiento);
return
profile;
}
}
|
|
Como podemos ver en el código
presentado anteriormente el método GetTrackingChannel
devuelve un objeto del tipo TrackingChannel donde
en este caso específicamente estaremos enviando a la forma la información que
ha sido monitoreada automáticamente al momento de ser ejecutado el flujo de
trabajo.
6) A continuación añadiremos a
nuestro programa una nueva clase denominada CanaldeSeguimiento que heredará de TrackingChannel cuyo objetivo será manejar
para persistirlos. En nuestro ejemplo simplemente se envía la información
recopilada a la etiqueta que se encuentra en la forma.
De esta clase es necesario sobre
escribir los métodos heredados desde la clase abstracta Send y InstanceCompletedOrTerminated para lograr un código
como el siguiente:
|
public class
CanaldeSeguimiento : TrackingChannel
{
private
TrackingParameters Parametros = null;
protected CanaldeSeguimiento()
{
}
public
CanaldeSeguimiento(TrackingParameters
parametros)
{
this.Parametros = parametros;
}
//Este
es el método que se ejecuta para realizar el registro de la actividad
generada por el WF
protected override void Send(TrackingRecord
Registro)
{
ActivityTrackingRecord
RegistroActividad = (ActivityTrackingRecord)Registro;
Programa.FormaInicio.lblSeguimiento.Text+="Hora: " +
RegistroActividad.EventDateTime.ToString();
Programa.FormaInicio.lblSeguimiento.Text
+= "Fecha: " +
RegistroActividad.QualifiedName.ToString();
Programa.FormaInicio.lblSeguimiento.Text += "Tipo:
" + RegistroActividad.ActivityType;
Programa.FormaInicio.lblSeguimiento.Text += "Estado: " +
RegistroActividad.ExecutionStatus.ToString();
}
//Se
llama cuando se termina de ejecutar la instancia
protected override void InstanceCompletedOrTerminated()
{
MessageBox.Show("Se terminó la instancia");
}
}
|
|
Hemos descrito entonces los pasos
básicos para generar un servicio de seguimiento personalizado para cualquier
instancia de un flujo de trabajo, que monitorea la ejecución de actividades y
en este caso muestra la información a la etiqueta definida en nuestra forma principal
oct.
Lo único que falta para que
nuestro Tracking Service
personalizado funcione es agregar el servicio al momento de que se invocan las
islas de los flujos de trabajo es decir, en el
debemos de modificar nuestra invocación con la siguiente línea de código
|
private void
button1_Click(object sender, EventArgs e)
{
WorkflowRuntime
MotorWF = new WorkflowRuntime();
//Añadimos
el servicio que acabamos de crear
MotorWF.AddService(new ServicioSeguimiento());
Type Tipo = typeof(Workflow1);
WorkflowInstance
InstanciaWF = MotorWF.CreateWorkflow(Tipo);
InstanciaWF.Start();
MotorWF.WorkflowCompleted +=
new
EventHandler<WorkflowCompletedEventArgs>(MotorWF_WorkflowCompleted);
}
void
MotorWF_WorkflowCompleted(object sender, WorkflowCompletedEventArgs e)
{
MessageBox.Show("¡El
flujo de trabajo fue finalizado!");
}
}
|
|
Al momento de ejecutar el código anterior y
oprimir el botón de invocación del flujo de trabajo podemos observar cómo el
motor de ejecución de Workflow Foundation ejecuta los diferentes métodos
definidos en las clases ServicioSeguimiento y CanaldeSeguimiento y muestra en
la forma el resultado del seguimiento del flujo de trabajo
![]()

Como pudimos observar los ejemplo
anterior podemos hacer un seguimiento muy específico de todas las situaciones
que están ocurriendo durante la ejecución de nuestro flujo de trabajo para
llevar a una auditoría muy a detalle de lo que sucede en el WF; sin embargo no
siempre es necesario programar lo que hemos visto previamente sino que WF
incluye una solución ya lista para usarse basada en SQL Server que se denomina
SQLTracking y que hace lo mismo que vimos anteriormente pero su persistencia es
evidentemente el servidor de base de datos SQL Server y nosotros lucharemos que
programar absolutamente nada porque todo ya está hecho. Cabe señalar que ésta
implementación utiliza la misma técnica y usa como clase de bases las mismas
clases que utilizamos para nuestro tracking service personalizado.
Para poder utilizar esta
funcionalidad es necesario tener instalada correctamente una instancia de SQL
Server 2005 (Express o cualquier versión comercial) y ejecutar unos scripts que
se distribuyen con la instalación del .NET Framework 3.0 y que se encuentran en
C:\WINDOWS\Microsoft.NET\Framework\v3.0\Windows Workflow Foundation\SQL\EN
Estos scripts son:
- Tracking_Schema.sql Crea la estructura de SQLTracking en la BDD.
-
Logic_Schema.sql, Crea la lógica de negocio de SQLTracking
en la BDD.
Modificando nuestro ejemplo
previo agregaremos ahora el servicio SqlTrackingService que como podemos ver contiene
un constructor sobrecargado en el cual le pasaremos como parámetro la cadena de
conexión que indica la instancia y el nombre de la base de datos de SQL Server.
|
private void
button1_Click(object sender, EventArgs e)
{
WorkflowRuntime
MotorWF = new WorkflowRuntime();
MotorWF.AddService(new ServicioSeguimiento());
//Agregamos servicio SQLTrackingService;
MotorWF.AddService(new
SqlTrackingService(@"Data
Source=.\sqlexpress;" +
"Initial Catalog=Tracking;Integrated Security=sspi"));
Type
Tipo = typeof(Workflow1);
WorkflowInstance
InstanciaWF = MotorWF.CreateWorkflow(Tipo);
InstanciaWF.Start();
MotorWF.WorkflowCompleted +=
new
EventHandler<WorkflowCompletedEventArgs>(MotorWF_WorkflowCompleted);
}
|
|
Podemos ahora
ir al Management Studio de SQL Server y ejecutar el siguiente query que nos
mostrará la efectividad del SqlTrackingService:
T-SQL
|
|
|
SELECT
TrackingWorkflowEvent.Description as Evento,
WorkflowInstanceEvent.EventDateTime as HoraInicial,
WorkflowInstance.WorkflowInstanceId as InstanciaWF,
Type.TypeFullName as NombreWF
FROM
WorkflowInstanceEvent
INNER JOIN
TrackingWorkflowEvent ON
WorkflowInstanceEvent.TrackingWorkflowEventId
= TrackingWorkflowEvent.TrackingWorkflowEventId AND
WorkflowInstanceEvent.TrackingWorkflowEventId
= TrackingWorkflowEvent.TrackingWorkflowEventId
INNER JOIN
WorkflowInstance ON
WorkflowInstance.WorkflowInstanceInternalId=WorkflowInstanceEvent.WorkflowInstanceInternalId
INNER JOIN
Type ON
Type.TypeId = WorkflowInstance.WorkflowTypeId
|
|
El query
anterior nos mostrará el siguiente resultado

Persistencia
Existe otro
concepto muy importante dentro de los flujos de trabajo y que también es provisto
mediante los servicios de ejecución del motor de flujos de trabajo de Workflow
Foundation es la persistencia. Este concepto se le denomina persistencia y es
utilizada siempre que se requiere recordar el estado de una determinada
instancia de un flujo de trabajo para posteriormente ocuparla en otro momento
del tiempo. Este tipo de flujos de trabajo son muy utilizados en los flujos
humanos es decir aquellos en los cuales intervienen diferentes miembros una
organización y cada uno completa una parte de las actividades específicas
dentro del flujo de trabajo pero no lo hacen todos al mismo tiempo sino que
cada quien interviene con sus actividades del flujo en momentos discontinuos.
El motor de flujos de trabajo sobre una solución extremadamente simple y fácil
de utilizar para implementar este tipo de flujo de trabajo también denominados
flujos de trabajo de ejecución larga (Long
running workflows)
Los servicios
de persistencia de Workflow Foundation se encargan de guardar el estado de
cualquier instancia de los flujos de trabajo en lo hacen de una manera
transparente automática al momento de que ocurre algún evento que haga que el
flujo de trabajo entre en un estado de suspensión es decir toda la información
referente al flujo se serializa en un medio persistente (de ahí el nombre)
Modificaremos
ahora el proyecto previamente usado para verificar el funcionamiento de los
servicios de persistencia de WF.
1) Lo primero
que haremos será modificar nuestro flujo de trabajo agregando una actividad del
tipo SuspendActivity y otra CodeActivity
después de la primera, cuyo código será: Programa.FormaInicio.lblMensaje.Text += "MENSAJE DESDE LA CODEACTIVITY2\n";
![]()

2)
Modificaremos también la forma principal del proyecto (frmInicio) y le
agregaremos una caja de texto, así como un botón adicional, de tal manera que
quede parecida a la siguiente:
![]()

3)
Modificaremos ahora la invocación del flujo de trabajo en el primer botón para
añadir el servicio de SqlWorkflowPersistenceService
que se encuentra dentro del espacio de nombres
System.Workflow.Runtime.Hosting, así mismo agregaremos un manejador de eventos
para que el host se suscriba al evento que generará la SuspendActivity y que
notificará al host de dicha suspensión tal modo que la inicialización del motor del flujo
de trabajo deberá parecerse a la siguiente:
|
private void
button1_Click(object sender, EventArgs e)
{
WorkflowRuntime
MotorWF = new WorkflowRuntime();
MotorWF.AddService(new ServicioSeguimiento());
//Agregamos
servicio SQLTrackingService;
MotorWF.AddService(new
SqlTrackingService(@"Data Source=.\sqlexpress;" +
"Initial
Catalog=WF;Integrated Security=sspi"));
//Agregamos
servicio de persistencia
MotorWF.AddService(new
SqlWorkflowPersistenceService (@"Data Source=.\sqlexpress;"
+
"Initial Catalog=WF;Integrated
Security=sspi"));
Type
Tipo = typeof(Workflow1);
WorkflowInstance
InstanciaWF = MotorWF.CreateWorkflow(Tipo);
MotorWF.WorkflowCompleted +=
new
EventHandler<WorkflowCompletedEventArgs>(MotorWF_WorkflowCompleted);
//Nos
suscribimos a la suspensión del flujo de trabajo por la SuspendActivity
// que añadimos al WF.
MotorWF.WorkflowSuspended +=
new EventHandler<WorkflowSuspendedEventArgs>(MotorWF_WorkflowSuspended);
InstanciaWF.Start();
}
|
|
4) En el manejador de de eventos
determinado por la función MotorWF_WorkflowSuspended agregaremos el siguiente
código para obtener el id de instancia del WF que usaremos posteriormente para
solicitar al motor de WF que reactive el flujo de trabajo persistido. Haremos
esto mediante el siguiente código.
C#
|
void MotorWF_WorkflowSuspended(object sender, WorkflowSuspendedEventArgs
e)
{
e.WorkflowInstance.Unload();
MessageBox.Show("WF Persistido!!!");
//Agregamos
el id de instancia a caja de texto para hacer la reactivacion
this.textBox1.Text =
e.WorkflowInstance.InstanceId.ToString();
}
|
|
5)Para
finalizar, al nuevo botón que acabamos de agregar le añadiremos el siguiente
código que invoca a los servicios de persistencia para reactivar el flujo de
trabajo previamente persistido (debido a que fue suspendido y descargado en el
paso previo) utilizando como su identificador el id de la instancia que se
encuentra en la caja de texto.
C#
|
private void
button2_Click(object sender, EventArgs e)
{
WorkflowRuntime
MotorWF = new WorkflowRuntime();
MotorWF.AddService(new
SqlWorkflowPersistenceService(@"Data Source=.\sqlexpress;" +
"Initial
Catalog=WF;Integrated Security=sspi"));
MotorWF.StartRuntime();
MotorWF.WorkflowCompleted +=
new
EventHandler<WorkflowCompletedEventArgs>(MotorWF_WorkflowCompleted);
WorkflowInstance
InstanciaWF = MotorWF.GetWorkflow(new Guid(this.textBox1.Text));
InstanciaWF.Resume();
}
|
|
6) Al ejecutar
el flujo de trabajo podemos observar que la instancia es suspendida después de
que se ejecuta la primera actividad de código, y posteriormente la caja de
texto adquiere el GUID que representa la instancia del WF.
![]()

7) Al oprimir el botón con el
texto Reactivar WF se reactivará el flujo de trabajo persistido mediante
SQLPersistenceServices y el motor de ejecución de WF completará el proceso de ejecución del flujo de trabajo.
Bueno pues ojalá les haya latido el artículo. El código fuente completo de este artículo está aqui
No se despeguen de este blog porque vienen cosas interesantes.
Antes de irme a dormir, algunas cosas:
a) Estamos organizando algo muy padre para juntarnos y platicar sobre tecnología aquí en la Ciudad de México. Muy pronto más detalles.
b) Ultimamente he tenido el gusto de recibir bastante correspondencia de ustedes, mis queridisimos lectores (jajaja chale ke gay) el caso es que me da mucho gusto que me escriban :) pero les recomiendo que lo hagan en los foros de este espacio para que las dudas y las respuestas puedan estar disponibles para todos, de todos modos les seguiré contestando pero si son dudas netamente técnicas (como la mayoría de los mails que he recibido estos dias) les pediría que lo pongan acá en los foros, ah ahora que me acuerdo hay gente que me comenta que no recibe el mail de confirmación cuando se registran. El buen Victor Alameda seguramente pronto verificará que onda con el sistema.
Un abrazo, coman sano, con y sin albur.
Maic.