January 2008 - Posts
Publicando Flujos de Trabajo de Workflow Foundation como Servicios Web
Una de las características más novedosas y útiles que podemos encontrar al desarrollar flujos de trabajo con Workflow Foundation es su capacidad de exponer nuestros flujos como servicios Web y usarlos en cualquier tipo aplicaciones. En este capítulo veremos qué se requiere para realizar esta funcionalidad y también veremos cómo invocar nuestros flujos de trabajo como servicios Web desde una aplicación de consola.
Para iniciar, abramos Visual Studio .NET si es que no está ejecutándose ya y creemos un nuevo proyecto de tipo consola haciendo clic en la opción New Project y seleccionando la plantilla Console Application de la categoría Windows. A esta aplicación le asignaremos el nombre AplicacionCliente y nos servirá para invocar el web Service que expondrá la funcionalidad del flujo de trabajo. La siguiente figura muestra la caja de diálogo New Project al crear nuestro proyecto:
A la solución agregaremos un nuevo proyecto de tipo Sequential Workflow Library haciendo clic derecho sobre la solución en Solution Explorer y seleccionando la opción Add->New Project. En la caja de diálogo seleccionemos la plantilla Sequential Workflow Library de la categoría Workflow y asignemos "WorkflowEjemplo" como nombre. La siguiente figura muestra la creación de este proyecto:
Ahora bien, necesitamos definir una interface la cual nos servirá para establecer el conjunto de métodos que nuestro servicio Web expondrá. Para realizar esto hagamos clic derecho sobre el proyecto WorkflowEjemplo en Solution Explorer y seleccionemos la opción Add->New Item del menú. Al hacer esto Visual Studio .NET despliega la caja de diálogo Add New Item con una serie de plantillas a escoger y una caja de texto para asignar el nombre del nuevo elemento. Seleccionemos la plantilla Interface del panel derecho y asignemos "IEjemplo.cs" como nombre de la nueva interfaz. La siguiente figura muestra la caja de diálogo Add New Item al agregar el nuevo elemento:
Al hacer clic en Ok Visual Studio .NET nos muestra el diseñador de código de la interfaz agregada. Asignemos el siguiente código en IEjemplo.cs:
public interface IEjemplo
{ string RegresaSignoZodiacal(DateTime fechaNacimiento);
}
Una vez definida la interfaz para nuestro servicio Web, regresemos al diseñador del flujo de trabajo y arrastremos y coloquemos una actividad de tipo WebServiceInput, la cual indica al flujo de trabajo que obtendrá una solicitud externa a través de una aplicación, es decir, que será invocado como un servicio Web. La actividad WebServiceInput debe ser configurada, siendo su propiedad InterfaceType su propiedad más importante. Esta propiedad indica el tipo de interfaz relacionada con esta actividad y para este ejemplo usaremos la interfaz IEjemplo creada en los párrafos anteriores. Para asignar la interfaz IEjemplo a la propiedad InterfaceType hagamos clic en el botón con tres puntos "…" en la ventana de propiedades de Visual Studio .NET. Al hacer clic sobre ese botón se despliega la siguiente caja de diálogo:
En esta caja de diálogo seleccionemos la interfaz WorkflowEjemplo.IEjemplo (la cual es la única interfaz en nuestro proyecto) y hagamos clic en el botón Ok.
La siguiente propiedad que debemos configurar es MethodName, la cuál como podemos deducir será RegresaSignoZodiacal. Al seleccionar este método de la lista desplegable automáticamente la ventana de propiedades se actualiza para mostrarnos fechaNacimiento en la lista. Esta nueva propiedad la debemos mapear a un campo o propiedad en nuestro flujo de trabajo para tener acceso al dato pasado como parámetro cuando se esté invocando este servicio Web. Seleccionemos la propiedad fechaNacimiento y hagamos clic en la opción "Bind Property 'fechaNacimiento'" en el panel de opciones de la parte inferior de la ventana de propiedades y en la caja de diálogo generemos un nuevo miembro de tipo campo llamado fechaNacimiento tal y como lo muestra la siguiente figura:
La última propiedad que configuraremos de la actividad WebServiceInput será IsActivating la cual indica si esta actividad activará el flujo de trabajo.
Agreguemos ahora una actividad de tipo IfElse a nuestro diseñador, y como expresión condicional en la rama izquierda asignemos un Code Condition llamado EsFechaValida. Este método validará que la fecha que asigne el usuario como parámetro no sea mayor que la fecha actual. El código entonces para este método será el siguiente:
private void EsFechaValida(object sender, ConditionalEventArgs e)
{ e.Result = fechaNacimiento <= DateTime.Now.Date ? true : false;
}
Como paso siguiente agreguemos una actividad de tipo Code en la rama izquierda de la actividad IfElse y una actividad de tipo WebServiceOutput. A la actividad Code le asignaremos en su propiedad ExecuteCode el método CalculaSigno, y a la actividad WebServiceOutput le asignaremos en su propiedad InputActivityName el nombre de nuestra actividad WebServiceInput (webServiceInputActivity1). Al hacer lo anterior la ventana de propiedades se actualiza para mostrarnos una nueva propiedad llamada ReturnValue. Esta propiedad la mapearemos a un nuevo miembro de tipo field llamado resultado.
En la rama derecha de la actividad IfElse arrastraremos y colocaremos una actividad de tipo WebServiceFault con su propiedad InputActivityName mapeada a webServiceInputActivity1 y su propiedad fault a un nuevo miembro de tipo field llamado fault.
La siguiente figura muestra todo el flujo de trabajo configurado e implementado:
El siguiente código muestra el método CalculaSigno el cuál contiene una implementación muy sencilla para calcular el signo zodiacal según la fecha especificada en el parámetro:
private void CalculaSigno(object sender, EventArgs e)
{ switch (fechaNacimiento.Month)
{ case 1:
resultado = fechaNacimiento.Day <= 21 ? "Capricornio" : "Acuario";
break;
case 2:
resultado = fechaNacimiento.Day <= 19 ? "Acuario" : "Piscis";
break;
//Otros meses…
}
}
Visual Studio .NET nos brinda una manera muy sencilla de exponer nuestros flujos de trabajo como servicios Web: simplemente hagamos clic derecho sobre el proyecto del flujo de trabajo y seleccionemos la opción "Publish as Web Service". Al hacer esto Visual Studio .NET creará automáticamente un proyecto Web en nuestra solución el cual contiene el servicio Web que expone la funcionalidad del flujo de trabajo.
Finalmente, en la aplicación de consola agreguemos la referencia Web apuntando al proyecto que se creó en el párrafo anterior y en el método Main() escribamos el siguiente código:
static void Main(string[] args)
{ Console.WriteLine("Escriba su fecha de nacimiento: ");
DateTime fechaNacimiento = Convert.ToDateTime(Console.ReadLine());
localhost.Workflow1_WebService ws = new AplicacionCliente.localhost.Workflow1_WebService();
Console.WriteLine(ws.RegresaSignoZodiacal(fechaNacimiento));
}
Al ejecutar nuestra aplicación y escribir una fecha válida (que sea igual o anterior a la fecha actual), nos mostrará el signo zodiacal relacionado con la fecha especificada tal y como lo muestra la siguiente figura:

Consumiendo Web Services en Workflow Foundation
Hasta este momento hemos visto y explicado una gran cantidad de funcionalidad de Workflow Foundation implementada en varias actividades que vienen incluidas, y en este capítulo explicaremos una más: la actividad InvokeWebService la cual nos permite realizar la invocación a un servicio Web XML y de esa manera extender la funcionalidad de nuestros flujos de trabajo.
Si bien está fuera de alcance en este capítulo explicar a detalle el funcionamiento de los servicios Web, debemos recordar que son componentes implementados en servidores Web y que exponen una serie de funcionalidades que podemos utilizar y reutilizar en nuestras aplicaciones que desarrollemos.
Para demostrar lo fácil que es consumir servicios Web desde nuestros flujos de trabajo en Workflow Foundation, iniciemos abriendo Visual Studio .NET y creemos una nueva aplicación de consola de flujo de trabajo secuencial. Esto lo logramos seleccionado la plantilla "Sequential Workflow Console Application" en la ventana de New Project de Visual Studio .NET. A esta solución le pondremos el nombre de ConsumiendoWebServices tal y como lo muestra la siguiente figura:
A nuestro proyecto le agregaremos un nuevo sitio Web de tipo ASP.NET Web Service y le pondremos el nombre WSPrueba. Este será el sitio en donde desarrollaremos un servicio Web de prueba para demostrar la invocación desde un flujo de trabajo. Cuando creamos un proyecto de este tipo automáticamente Visual Studio .NET agrega un servicio Web llamado Service.asmx y su archivo de code-behind relacionado Service.asmx.cs. Nuestro servicio Web tendrá un método público que regresará el día de la semana actual, para esto necesitamos implementar el siguiente código:
1: [WebMethod]
2: public string RegresaDiaDeLaSemana()
3: { 4: return DateTime.Now.DayOfWeek.ToString();
5: }
Recordemos que aquellos métodos que deseamos invocar a través del servicio Web deberán estar decorados con el atributo WebMethod y tener el modificador de acceso public
Ahora bien, en el diseñador de nuestro flujo de trabajo agreguemos una actividad de tipo InvokeWebService. Al hacer esto Visual Studio .NET automáticamente nos presenta la caja de diálogo Add Web Reference –la misma que se muestra al agregar una referencia Web en cualquier tipo de proyecto-. El objetivo de esta caja de diálogo es indicar cuál es el servicio Web que deseamos invocar por medio de la actividada InvokeWebService. En nuestro caso seleccionaremos "Web Services in this solution" ya que esta opción buscará los servicios Web implementados en la misma solución. Una vez seleccionada esta opción, la caja de diálogo nos muestra una lista con los servicios Web encontrados y posteriormente seleccionaremos el servicio llamado "Service" el cual es el que creamos en los párrafos anteriores. Al hacer clic en este servicio, la caja de diálogo nos muestra su detalle completo. La siguiente figura muestra la caja de diálogo desplegando el detalle de Service.asmx:
Hagamos clic en el botón Add Reference. Como es esperado, Visual Studio .NET agrega al proyecto de nuestro flujo de trabajo la referencia Web correspondiente al servicio seleccionado. Además de lo anterior la actividad InvokeWebService es mostrada en el diseñador. No obstante, a este momento aún no hemos configurado dicha actividad, lo cual requiere establecer la propiedad MethodName que indica el método del servicio Web que se va a ejecutar. En la ventana de propiedades MethodName se muestra como una lista desplegable, la cual al hacer clic sobre ella muestra la lista de todos los métodos disponibles en el servicio. En nuestro caso seleccionaremos RegresaDiaDeLaSemana. Al seleccionar dicho método la ventana de propiedades es actualizada mostrando una nueva propiedad llamada (ReturnValue). Esta propiedad nos permite mapear el valor de retorno del método a alguna propiedad o campo dentro de nuestro flujo de trabajo; esto lo podemos lograr al hacer clic en el botón de tres puntos "…" en la propiedad (ReturnValue) y configurar el mapeo, en nuestro caso, a un campo llamado "resultado". El mismo mecanismo de asignación es usado para cuando el método Web requiere parámetros. La siguiente figura muestra la actividad InvokeWebService configurada:
Muy bien, ahora para probar la invocación del servicio Web agreguemos una actividad de tipo Code al diseñador del flujo de trabajo. A esta actividad le relacionaremos el método Consulta en su propiedad ExecuteCode tal y como lo muestra la siguiente figura:
El método Consulta tendrá el siguiente código, el cual muestra el resultado de la invocación de nuestro servicio Web:
1: private void Consulta(object sender, EventArgs e)
2: { 3: Console.WriteLine(resultado);
4: }
Hemos finalizado. Para probar nuestro trabajo ejecutemos nuestra aplicación haciendo clic en Ctrl-F5 o haciendo clic en el menú Debug->Start Without Debugging. Dependiendo del día en el que se esté ejecutando el código, el resultado nos mostrará el día correcto de la semana tal y como lo podemos corroborar en la siguiente figura:

Transacciones en Workflow Foundation
Muy frecuentemente en nuestras aplicaciones necesitamos incorporar algún tipo de mecanismo que nos asegure que los datos que estemos usando hayan sido grabados de manera correcta y consistente. Bienvenidos a las Transacciones, las cuáles -en el contexto de WF- las podemos utilizar como medio para asegurarnos que efectivamente la información haya sido actualizada tal y como lo esperamos y en todo caso de alguna falla la información no quede incompleta o inconsistente.
Pero ¿qué es una Transacción?
Una transacción la podemos definir como una unidad de trabajo en donde se ejecutan exitosamente todas las tareas que se incluyen en la transacción, o no se ejecuta ninguna.
En WF podemos incorporar en nuestros flujos de trabajo dos tipos de transacciones: Transacciones de tipo 2PC (Two Phase Commit) y Transacciones Compensables. En este capítulo explicaremos y mostraremos el uso de cada una de ellas para nuestras aplicaciones.
Transacciones Two Pase Commit
Este tipo de transacción es aquella que está coordinada a través de un administrador el cual su objetivo es determinar si las acciones y datos relacionados con la transacción se deben aplicar en un momento específico. Ese momento lo determina el administrador tomando en cuenta si todos los recursos relacionados en la transacción "votan" para que la transacción continúe o se deshaga en su totalidad.
Las transacciones de esta categoría deben tener las propiedades ACID, las cuales son por sus siglas en inglés: Atomicity, Consistency, Isolation y Durability.
La prueba del "Ácido" para las transacciones
Como describimos en el párrafo anterior, las transacciones de tipo Two Phase Commit deben tener ciertas propiedades para que su comportamiento sea el esperado y funcionen correctamente. Estas propiedades las explicaremos a continuación:
Atomicidad (Atomicity). Esta propiedad indica que todas las operaciones relacionadas con la transacción se han ejecutado en su totalidad o no se han ejecutado, es decir, esta es la propiedad que nos asegura que un proceso de actualización de datos no puede quedar a medias provocando que nuestros datos queden en un estado incorrecto. Un ejemplo en donde podemos apreciar esta propiedad es cuando realizamos un retiro de dinero en un cajero automático: solicitamos cierta cantidad y esa cantidad nos es dada en efectivo. Ahora bien, imaginemos el caso en el que la transacción no fuera atómica y que ocurriese alguna falla en la transacción, digamos que el cajero automático no tenga dinero suficiente para nuestro retiro. Terminaríamos con un saldo menor e incorrecto en nuestra cuenta de ahorros pero con nada de dinero en efectivo en las manos. O tal vez el pago de un servicio en línea por medio de una tarjeta de crédito. En fin, ejemplos podríamos decir muchos. Gracias a la propiedad de atomicidad en las transacciones nos aseguramos que en algún caso de error en los ejemplos anteriores los datos relacionados no se vean afectados.
La Atomicidad nos asegura que todas las operaciones en una transacción se realizan todas o no se realiza ninguna.
Consistencia (Consistency). Esta propiedad indica que los datos envueltos en la transacción se deben mantener consistentes. Si tomamos como ejemplo una base de datos relacional esto indica que la transacción no debe romper ninguna restricción que haya sido declarada. Tal es el caso de una transacción que requiere insertar un registro en dos tablas con una relación padre-hijo por ejemplo las tablas Cliente y Factura. Si la transacción inserta exitosamente un registro en la tabla Factura pero falla al insertar el registro correspondiente en la tabla Cliente, la integridad de la base de datos se vería afectada y la transacción –gracias a la propiedad de Consistencia- es echada para atrás.
La Consistencia nos asegura que la integridad de los datos se mantendrá una vez realizada la transacción y que no se romperá ninguna restricción.
Aislamiento (Isolation). Esta propiedad nos permite que las operaciones en una transacción no afecten a otras. Existen diferentes tipos de nivel de aislamiento para las transacciones que podemos utilizar según el contexto y las necesidades propias de la aplicación y/o flujo de trabajo que estemos desarrollando.
La propiedad de Aislamiento nos asegura que las operaciones de una transacción no pueden afectar a otras.
Durabilidad (Durability). Esta propiedad nos asegura que una vez realizada la transacción los datos modificados o agregados quedarán guardados de una manera no-volátil y estarán disponibles aún si ocurriese una falla en el sistema. Esto es cierto aún si la falla ocurriera 1 milisegundo después de haber sido realizada la transacción.
La Durabilidad nos asegura que una vez realizada la transacción los datos quedarán guardados aunque falle el sistema.
Ejemplo
Veamos ahora cómo implementar transacciones de tipo Two Phase Commit en nuestros flujos de trabajo utilizando Workflow Foundation. Cabe mencionar que el siguiente ejemplo supone la existencia de la base de datos Northwind implementada en SQL Server Express de manera local y supone también que se ha configurado el servicio SqlWorkflowPersistenceService usando una base de datos también local llamada WorkflowStore usando los scripts que se incluyen con el .NET Framework 3.0 presentes en [Carpeta de Windows]\Microsoft.NET\Framework\v3.0\Windows Workflow Foundation\SQL\EN. Para mayor información acerca de SqlWorkflowPersistenceService consulta el capítulo "Persistencia" en esta obra.
Con Visual Studio .NET abierto, hagamos un nuevo proyecto de tipo Sequential Workflow Console Application. Con el diseñador de WF en la pantalla arrastremos y coloquemos una actividad de tipo TransactionScope tal y como lo muestra la siguiente figura:
La actividad TransactionScope es una actividad compuesta, es decir, que puede contener una o más actividades dentro de ella. Todas las actividades que definamos dentro de la actividad TransactionScope participarán en la misma transacción. Una de las propiedades más importantes de esta actividad es TransactionOptions.IsolationLevel la cual indica el nivel de aislamiento que esta transacción tendrá al ejecutar todas sus operaciones siendo su valor predeterminado Serializable (el nivel de aislamiento más restrictivo). A continuación en la tabla 1 se enlistan los diferentes niveles de aislamiento disponibles:
| Nombre | Descripción |
| Serializable | Los datos no aplicados pueden ser leídos pero no modificados, y no se pueden insertar datos durante la transacción |
| RepeatableRead | Los datos no aplicados pueden ser leídos pero no modificados. Se pueden insertar datos durante la transacción |
| ReadCommitted | Los datos no aplicados no pueden ser leídos durante la transacción pero sí pueden ser modificados |
| ReadUncommitted | Los datos no aplicados pueden ser leídos y modificados |
| Snapshot | Los datos no aplicados pueden ser leídos. Antes de modificar los datos verifica si otra transacción ha modificado los datos después de que se leyeron los datos, si es así arroja una excepción |
| Chaos | Los cambios pendientes de transacciones que tengan un nivel de aislamiento superior no pueden ser reemplazados |
| Unspecified | Otro. Sin embargo este nivel no puede ser asignado de manera manual |
Tabla 1. Niveles de aislamiento para las transacciones
No podemos colocar una actividad TransactionScope dentro de otra
Para continuar con el ejemplo usaremos una actividad de tipo Code y la colocaremos dentro de la actividad transactionScopeActivity1. Es en esta actividad en donde declararemos el código que deseamos que participe en la transacción así que asignemos un método llamado "Transaccion" en la propiedad ExecuteCode de la actividad codeActivity1. En este método definamos el siguiente código:
private void Transaccion(object sender, EventArgs e)
{
using (SqlConnection conn = new SqlConnection())
{
conn.ConnectionString = @"Data Source=.\sqlexpress;Initial Catalog=Northwind;Integrated Security=Yes";
SqlCommand cmd = new SqlCommand("INSERT INTO Region VALUES (@RegionID, @Description)", conn);
cmd.Parameters.AddWithValue("@RegionID", this.RegionID);
cmd.Parameters.AddWithValue("@Description", this.RegionID.ToString());
conn.Open();
cmd.ExecuteNonQuery();
}
}
Asimismo, en nuestra clase del flujo de trabajo definamos una propiedad pública de tipo int llamada RegionID. Esta propiedad nos servirá como mecanismo para enviar parámetros a nuestro flujo de trabajo específicamente al comando de SQL que vamos a ejecutar.
Ya que vamos a demostrar el poder y comportamiento de las transacciones es buena idea agregar un Fault Handler a nuestro flujo de trabajo para que capture cualquier tipo de excepción, es por eso que vamos a usar la clase System.Exception. En el fault handler agregaremos una actividad de tipo Code la cual ejecutará el método ErrorHandler para mostrar un mensaje amigable al usuario. La siguiente figura muestra el diseñador de Workflow Foundation en la vista de Fault Handlers.
El código del método ErrorHandler será el siguiente:
private void ErrorHandler(object sender, EventArgs e)
{ Console.WriteLine("Ha ocurrido un error. La transacción será echada para atrás.");}
Una vez hecho lo anterior, asignemos un método manejador para el evento Completed de nuestro flujo de trabajo. Esto lo podemos lograr haciendo clic en el botón de eventos en la ventana de propiedades del flujo de trabajo tal y como lo muestra la siguiente figura:
El nombre del método será WorkflowCompleto y tendrá el siguiente código:
private void WorkflowCompleto(object sender, EventArgs e)
{ Console.WriteLine("El workflow ha completado");}
Por último debemos modificar el código que inicia la instancia del flujo de trabajo en la aplicación de consola. Para esto modifiquemos el código incluido en Program.cs para que tenga el siguiente código antes de invocar instance.Start().
Console.Write("Escriba la nueva región a insertar: ");
int regionID = int.Parse(Console.ReadLine());
Dictionary<string, object> parametros = new Dictionary<string, object>();
parametros.Add("RegionID", regionID);
//Agregamos el servicio al runtime
workflowRuntime.AddService(new SqlWorkflowPersistenceService(
@"Data Source=.\sqlexpress;Initial Catalog=WorkflowStore;Integrated Security=Yes"));
WorkflowInstance instance = workflowRuntime.CreateWorkflow(typeof(WorkflowConsoleApplication1.Workflow1), parametros);
Muy bien! Hemos preparado nuestro flujo de trabajo para que agregue una nueva región a la table Region de la base de datos Northwind dentro de una transacción. Probemos nuestro proyecto ejecutándolo con Ctrl + F5 y pasemos la región número 5 como parámetro. En este caso la operación de inserción es exitosa y nuestro flujo de trabajo nos despliega el siguiente mensaje en la consola:
Finalmente debemos comentar que en caso de alguna falla la actividad TransactionScope se encargará de echar para atrás todos los cambios dejando los datos en un estado consistente.
Transacciones Compensables
Este tipo de transacciones nos permiten definir una serie de operaciones a ejecutar cuando la ejecución de la transacción falla, es decir, la compensan. A diferencia de las transacciones de tipo Two Phase Commit en donde las operaciones se ejecutan o no se ejecutan, en una transacción compensable las operaciones sí son ejecutadas y en todo caso de una falla esas operaciones no son echadas para atrás sino que se ejecutan algunas otras acciones que complementan o "compensan" esa falla. Para dar un ejemplo de esto podemos mencionar el caso cuando pagamos un excedente de algún servicio, en donde la operación compensatoria es emitir una nota de crédito o cupón para hacer uso de ese beneficio posteriormente. Cabe mencionar que este tipo de transacciones son útiles cuando ya no podemos echar para atrás los cambios producidos por la misma transacción o por otra.
Ejemplo
Para demostrar este tipo de transacciones retomemos el proyecto de ejemplo anterior, sin embargo en vez de usar una actividad de tipo TransactionScope utilizaremos la actividad CompensatableTransactionScope la cual nos permite definir una serie de actividades a ejecutar como parte de esa transacción tal y como ocurre con su contraparte explicada en la sección anterior.
Asimismo, para esta demostración provocaremos un error a propósito después de ejecutar la transacción que da de alta regiones en la base de datos Northwind. La actividad que arrojaremos será de tipo System.Exception. La siguiente figura muestra el diseñador de Workflow Foundation con los cambios necesarios:
Debemos notar que el tipo de transacción que ejecuta la actividad codeActivity1 ahora es de tipo CompensateTransactionScope y que agregamos una actividad de tipo Throw después de la transacción para provocar el error mencionado.
Para indicar la serie de actividades compensatorias a ejecutar debemos definirlas en la vista de la actividad compensationHandlerActivity1. Esto lo podemos lograr haciendo clic la opción apropiada del menú desplegable de la transacción compensatableTransactionScopeActivity1 tal y como lo muestra la siguiente figura:
En la actividad compensationHandlerActivity1 añadiremos una actividad de tipo Code. Esta actividad nos servirá para ejemplificar la compensación de una transacción cuando esta falla. A esta actividad le relacionaremos el método Compensa y en este método solamente agregaremos el siguiente sencillo código:
private void Compensa(object sender, EventArgs e)
{ Console.WriteLine("Compensando...");}
Finalmente, en la vista de Fault Handlers agregaremos una actividad de tipo Compensate, la cual indica por medio de su propiedad TargetActivityName la actividad que se quiere compensar, en nuestro caso será la actividad compensatableTransactionScopeActivity1. Esta actividad nos servirá para indicar de manera manual que queremos realizar la compensación correspondiente. La figura 7 muestra el diseñador de Workflow Foundation con la vista de Fault Handlers:
Resumen
En este capítulo vimos los dos tipos de transacciones que podemos incorporar en nuestros flujos de trabajo. Asimismo dimos ejemplos y escenarios en donde pueden ser implementados y la manera sencilla y amigable que disponemos en Workflow Foundation para hacer uso de ellos.
Este artículo fue creado para el curso Desarrollador .NET de la revista Users. http://desarrollador.redusers.com/
Políticas en Workflow Foundation
Como ya hemos visto en capítulos anteriores, una regla es un conjunto de expresiones condicionales que nos permiten evaluar ciertos valores para determinar un resultado verdadero o falso. En este capítulo profundizaremos un poco más acerca de las reglas y su relación que guardan con los Conjuntos de Reglas o RuleSet, usado por la actividad Policy.
La actividad Policy y los Conjuntos de Reglas
La actividad llamada Policy nos permite en un flujo de trabajo evaluar un RuleSet, el cuál es una colección de reglas relacionadas o no entre sí.
El Conjunto de Reglas o RuleSet es una colección de reglas relacionadas o no entre sí. Nos referimos a que dos o más reglas están relacionadas entre sí cuando ellas leen o actualizan el mismo campo o propiedad de alguna clase.
Veamos ahora en la práctica cómo podemos utilizar esta actividad dentro de nuestros flujos de trabajo en Workflow Foundation.
Para comenzar, abramos Visual Studio .NET si es que no está actualmente abierto. Posteriormente crearemos un nuevo proyecto llamado PolicyWF de tipo Sequential Workflow Console Application tal y como lo muestra la siguiente figura:
Ya que deseamos mostrar la funcionalidad de la actividad Policy, junto con la evaluación de su RuleSet relacionado, necesitamos escribir un poco de funcionalidad para nuestro flujo de trabajo. Sin embargo, esta es una tarea muy sencilla: vamos a crear cuatro variables públicas de tipo int llamadas a, b, c y d. Estas variables (comúnmente denominados "campos") le permitirán al RuleSet evaluar sus reglas y su posible re-evaluación. Muy bien, manos a la obra. Hagamos clic en el botón "View Code" de la ventana Solution Explorer de Visual Studio.NET para desplegar la vista de código de nuestro flujo de trabajo. Posteriormente escribamos el código para crear esas variables a nivel de clase:
int a;
int b;
int c;
int d;
Ahora, necesitamos asignarle un valor a cada variable para que las reglas en el RuleSet que vamos a crear lean esos valores. Esto lo podemos hacer inmediatamente después de la invocación al método InitializeComponent() en el constructor de la clase:
a = 10;
b = 1;
c = 5;
d = 1;
El siguiente paso es seleccionar la actividad Policy del Toolbox y arrastrarla al diseñador de Workflow Foundation. Cuando la actividad es colocada en el diseñador, automáticamente Visual Studio .NET le asigna un nombre el cual en nuestro caso eso policyActivity1. Asimismo esta actividad muestra un símbolo de admiración en color rojo, esto significa que la actividad aun no ha sido configurada (tal y como sucede al configurar una actividad de tipo IfElseActivity como vimos en los capítulos anteriores).
Como podemos apreciar en la ventana de propiedades, la actividad policyActivity1 tiene una propiedad llamada RuleSetReference, la cual indica el nombre del RuleSet que deseamos asociar a esa actividad. Ya que en este caso acabamos de crear un proyecto nuevo no tenemos un RuleSet definido. Esto lo podemos hacer en la caja de diálogo llamada "Select Rule Set", la misma que es desplegada al hacer clic en el botón con tres puntos "…" en la propiedad RuleSetReference de policyActivity1 tal y como lo muestra la siguiente figura:
Esta caja de diálogo nos permite crear, modificar o eliminar los Conjuntos de Reglas que hayamos definido dentro de nuestro proyecto. Ya que no tenemos nada definido aún, es buena idea hacer clic en el botón "New…" para poder crear un nuevo RuleSet. Al hacer clic en este botón Visual Studio .NET despliega la caja de diálogo llamada "Rule Set Editor" la cual nos permite establecer las reglas que componen ese RuleSet, su configuración de encadenamiento o Chaining (el mismo que explicaremos más adelante), su prioridad, su comportamiento de re-evaluación, la expresión condicional, las acciones a ejecutar si esa expresión es verdadera (THEN) y las acciones a ejecutar si esa expresión resulta ser falsa (ELSE). Podemos apreciar esta caja de diálogo en la siguiente figura:
Es tiempo de crear la primera regla de nuestro RuleSet. Hagamos clic en el botón "Add Rule". Al hacer clic en este botón, es creada una nueva regla con nombre predeterminado "Rule1". A esta regla le asignaremos una prioridad con valor 100, dejaremos el resto de sus propiedades con el valor por defecto y la condición la expresaremos como "Si a es igual a 20…", es decir:
Ahora bien, escribamos la expresión para cuando esa condición sea verdadera. Queremos que en el caso que a sea 20, entonces d sea 50: esto lo haremos en la caja de texto para THEN:
Hemos completado la primera regla de nuestro RuleSet. La regla completa la podemos ver en la siguiente figura:
La prioridad de las reglas permite al motor de reglas de Workflow Foundation determinar qué reglas se evalúan primero y cuáles después. En un RuleSet, las reglas serán ejecutadas en el orden del valor de prioridad que tengan asignadas, siendo la regla que tenga el valor más alto la que se evaluará primero y así consecutivamente.
Continuemos ahora agregando una segunda regla a nuestro RuleSet, haciendo clic en el botón "Add Rule" de la barra de botones para crear una regla llamada Rule2. La expresión condicional para esta regla será "Si b es igual a 1, a será 20 de lo contrario d será 90", de ahí que la condición será:
El bloque THEN:
Y la expresión ELSE:
A esta regla le asignaremos la prioridad con un valor de 99. Agreguemos otra regla llamada Rule3 a nuestro RuleSet con prioridad 98 y con la expresión condicional: "Si d es 50 entonces c es 100 de lo contrario c es 60". La siguiente figura muestra las reglas Rule2 y Rule3 terminadas:
Muy bien, es hora de poner en marcha nuestro RuleSet y comprobar su funcionamiento, para esto agregaremos una actividad de tipo Code después de la actividad policyActivity1 en nuestro diseñador. Le asignaremos como valor "DisplayResults" en la propiedad ExecuteCode en la ventana de propiedades de esta nueva actividad (codeActivity1). La siguiente figura muestra el diseñador de Workflow Foundation incluyendo esta nueva actividad:
Después de teclear <Enter> Visual Studio .NET nos mostrará el código fuente de nuestro flujo de trabajo y el cuerpo del método DisplayResults(). Completemos el código:
private void DisplayResults(object sender, EventArgs e)
{ Console.WriteLine("a:{0} b:{1} c:{2} d:{3}", a, b, c, d);}
Este código mostrará en la consola el valor de cada una de los campos que creamos.
Ejecutemos el código y veamos el resultado:
¿Por qué los resultados son así? Es muy importante hacer un alto y hacer notar la Dependencia que tienen las reglas Rule1 y Rule2 y Rule 3 entre sí.
Podemos decir que, existe Dependencia entre reglas cuando una regla modifica un campo o propiedad que otra regla está leyendo como parte de su expresión condicional, tal y como sucede en Rule1 y Rule2 en donde Rule1 está evaluando el valor de a y asimismo Rule2 está modificando el valor de a.
Asimismo, Rule3 tiene dependencia tanto con Rule1 y Rule2:
Con Rule1 porque modifica el valor de d cuando su expresión condicional es verdadera (THEN) .
Con Rule2 porque esta modifica el valor de d cuando su expresión es falsa (ELSE).
En este caso específico, el motor de reglas de Workflow Foundation usa el concepto de encadenamiento de reglas o Chaining, para determinar si una regla se re-evalúa como resultado de la modificación de un campo o propiedad que otra regla haya modificado.
Chaining es el proceso en donde el motor de reglas de Workflow Foundation re-evalúa las reglas dependientes entre sí, encadenando efectivamente una con otra hasta que todas las reglas de un RuleSet son evaluadas. Hay tres tipos de Chaining: Implícito, Basado en Atributos y Explícito.
Tipos de Control de Chaining
A continuación se explican cada uno de los tipos de Chaining que el motor de reglas de Workflow Foundation implementa:
Implícito (default)
En este tipo de Chaining el motor de reglas de Workflow Foundation es el responsable de asegurarse que las reglas se evalúen y re-evalúen según sus dependencias entre sí. Este es el mecanismo de Chaining por defecto ya que es el más sencillo de usar para nosotros los desarrolladores ya que el motor de reglas se encarga de todo el proceso de encadenamiento.
Basado en Atributos
Este tipo de Chaining utiliza atributos especiales con los que podemos decorar nuestros métodos para determinar si un campo o propiedad será modificado después de ejecutar dicho método. Este mecanismo es usado para escenarios de Chaining más sofisticados y complejos. Los atributos usados en este tipo de Chaining son los siguientes:
- RuleRead. Le indica al motor de reglas que un campo o propiedad está siendo leído.
- RuleWrite. Le indica al motor de reglas que un campo o propiedad está siendo modificado.
- RuleInvoke. Le indica al motor de reglas que el método especificado será invocado, el cuál muy probablemente esté a su vez decorado con ReadWrite. Este atributo es usado cuando un método ejecuta otro.
Explícito
Este tipo de Chaining hace uso de la cláusula Update, la cual le indica de inmediato al motor de reglas que un campo o propiedad ha sido modificado. Prácticamente este mismo comportamiento lo obtenemos con el Chaining implícito o por medio del atributo ReadWrite, sin embargo este mecanismo es necesario cuando estamos invocando un método o función que no hayamos escrito nosotros en nuestras reglas. Por ejemplo:
IF…
THEN
this.Utilerias.ActualizaValor(this.d)
Update(this.d)
Ejemplo
Modifiquemos nuestro proyecto agregando un método llamado Modifica_A() que reciba como parámetro un entero. A este método lo decoraremos con el atributo RuleWrite("a") para indicar al motor de reglas que este método está actualizando el valor del campo a. El código es el siguiente:
[RuleWrite("a")]void Modifica_A(int valor)
{ this.a = 20;
}
Asimismo, modifiquemos la acción THEN de Rule2 para invocar el método Modifica_A() enviando como parámetro el valor 20 en vez de hacer la modificación directamente en el código de la acción:
El resultado será el mismo. Ahora quitemos el atributo [RuleWrite("a")] del método Modifica_A(). Ejecuta de nuevo el proyecto. ¿Qué sucede?
Tipos de Comportamiento de Chaining
Tenemos la posibilidad de modificar el comportamiento de Chaining para nuestro RuleSet, ya que existen tres tipos de comportamientos los cuales están listados en la siguiente tabla:
|
Tipo de Comportamiento |
Descripción |
|
Encadenamiento Completo (Full Chaining) |
Este es el comportamiento por defecto |
|
Solo Actualización Explícita (Explicit Update Only) |
Como su nombre lo indica este comportamiento desactiva todos los atributos y el Chaining implícito y únicamente usará las cláusulas Update para llevar a cabo el proceso de Chaining. |
|
Secuencial (Sequential) |
Con este comportamiento, el motor de reglas evalúa las reglas una sola vez en el orden indicado por su prioridad. |
Debemos tener especial cuidado al seleccionar tanto el tipo de control como el tipo de comportamiento de Chaining, ya que podríamos llegar a una situación de un bucle infinito en donde una Regla A modifique un valor que cause que otra Regla B se evalúe, y esta a su vez modifique un campo que Regla A utiliza.
Ejemplo de Comportamiento Secuencial
Modifiquemos el comportamiento del Chaining en nuestro RuleSet a Sequential. Esto lo podemos lograr en la caja de diálogo "Rule Set Editor" y seleccionando apropiadamente el valor de la lista desplegable. Hagamos clic en el botón OK y otra vez OK. Ejecutemos otra vez nuestro proyecto. El resultado ahora será:
Comportamiento de Re-evaluación
Existen dos tipos de comportamientos de re-evaluación para las reglas dentro de un RuleSet: Siempre (Always) o Never (Nunca).
Always es el comportamiento por defecto en donde la regla se re-evaluará tantas veces como sea necesario según la dependencia y el mecanismo de Chaining. Por el otro lado, Never como su nombre lo indica evaluará solo la primera vez la regla pero nunca se re-evaluará. Podemos hacer uso de este mecanismo para evitar bucles infinitos entre las reglas.
La cláusula Halt
Esta cláusula sirve para terminar la ejecución del RuleSet. La podemos equiparar a la función break dentro de un bucle for de Visual C# ya que nos sirve para regresar el control al código origen cuando cierto objetivo ha sido logrado dentro de nuestro RuleSet y ya no sea necesario ejecutar las demás reglas.
Resumen
En este capítulo profundizamos acerca de las reglas y vimos el uso de la actividad Policy. También se explicó el término RuleSet y vimos en la práctica cómo crean. Posteriormente hablamos del mecanismo de Chaining y sus diversos comportamientos y opciones incluídos en el motor de reglas de Workflow Foundation para resolver escenarios sencillos o más sofisticados en nuestras aplicaciones que necesiten usar este motor.
Este artículo fue creado para el curso Desarrollador .NET de la revista Users. http://desarrollador.redusers.com/
Condiciones en Workflow Foundation
En el artículo anterior mencionamos el uso de expresiones condicionales para determinar si una regla se evalúa como verdadera o falsa y el código relacionado a ese resultado se ejecuta.
En este artículo profundizaremos un poco más acerca de las expresiones condicionales, su sintaxis y su uso dentro de las reglas en Workflow Foundation.
Operadores y palabras clave
Para crear o editar una expresión condicional en Workflow Foundation tenemos la opción de utilizar una serie de operadores y palabras clave para darle el significado y el comportamiento necesitado a nuestra expresión. Por ejemplo, en la sección anterior vimos el uso del operador relacional == para determinar si el día de hoy es o no es lunes. No obstante, contamos con una diversa gamma no solo de operadores relacionales, sino también aritméticos y lógicos, así como también palabras clave que nos permiten escribir una lógica robusta para nuestras expresiones. A continuación se describen los diferentes operadores y palabras clave disponibles.
Es muy importante recordar que una expresión condicional es una expresión cuyo objetivo es decirnos simplemente: Sí o No (verdadero o falso). De esta manera podemos determinar el flujo que debe seguir la ejecución.
Operadores Relacionales
Son aquellos que relacionan dos valores para determinar su igualdad o jerarquía. La Tabla 1 nos muestra los diferentes operadores relacionales disponibles para crear expresiones condicionales en Workflow Foundation:
| Operador | Funcionalidad |
| == | Determina la igualdad entre dos valores |
| = | Determina la igualdad entre dos valores |
| > | Determina si un valor es mayor a otro |
| >= | Determina si un valor es mayor o igual a otro |
| < | Determina si un valor es menor a otro |
| <= | Determina si un valor es menor o igual a otro |
Tabla 1. Operadores Relacionales
Operadores Aritméticos
Son aquellos que nos permiten ejecutar cálculos matemáticos entre el valor puesto a la izquierda del operador y el de la derecha. La Tabla 2 nos muestra los diferentes operadores aritméticos disponibles para crear expresiones condicionales en Workflow Foundation:
| Operador | Funcionalidad |
| + | Suma |
| - | Resta |
| * | Multiplicación |
| / | División |
| MOD | Módulo |
Tabla 2. Operadores Aritméticos
Operadores Lógicos
Son aquellos que nos permiten evaluar si una condición se cumple o no. La Tabla 3 nos muestra los diferentes operadores lógicos disponibles para crear expresiones condicionales en Workflow Foundation:
| Operador | Funcionalidad |
| AND ó && | "Y" lógico |
| OR ó || | "O" lógico |
| NOT ó ! | Negación |
| & | "Y" Bitwise |
| | | "O" Bitwise |
Tabla 3. Operadores Lógicos
Palabras Clave
Existen diversas palabras clave que podemos usar en la creación de expresiones condicionales, permitiéndonos controlar el comportamiento y el flujo de nuestra expresión. La Tabla 4 nos muestra las diferentes palabras clave disponibles para crear expresiones condicionales en Workflow Foundation:
| Palabra clave | Funcionalidad |
| IF | Evalúa una expresión para determinar si el código relacionado se ejecuta o no |
| THEN | Ejecuta el código relacionado si la expresión IF es verdadera |
| ELSE | Ejecuta el código relacionado si la expresión IF es falsa |
| HALT | Finaliza el procesamiento de la Regla |
| Update | Informa al motor de Reglas que una propiedad o un campo ha sido modificado, para poder establecer la dependencia entre diferentes reglas y su probable re-evaluación. |
Tabla 4. Palabras Clave
Una expresión condicional puede usar uno o varios operadores y palabras clave.
Ejemplo
En el capítulo anterior vimos cómo crear una regla cuya expresión condicional evalúa si el día de hoy es lunes. Vamos a retomar el mismo proyecto y flujo de trabajo y modificaremos la expresión condicional para evaluar además si el mes actual es noviembre o diciembre, y si el año es 2007 o superior. Para esto lo que necesitamos hacer es ejecutar Visual Studio .NET si es que no está ejecutándose y abrir el proyecto que creamos en el capítulo anterior. Recordemos en este momento cómo quedó dicho proyecto:
Para modificar la expresión condicional debemos hacer clic en la actividad llamada IfElseBranchActivity1, ya que como recordaremos, es esta la actividad a la cuál le definimos la regla la cuál determina el día de la semana actual. Al hacer clic sobre esta actividad la ventana de propiedades en Visual Studio .NET nos muestra las propiedades para esta actividad en específico. Hagamos clic en el nodo de la propiedad llamada Condition (mostrado como una caja con un símbolo +) para expandir su árbol de propiedades relacionadas y enseguida hagamos clic en el botón con tres puntos "…" en la propiedad Expression, para mostrar la caja de diálogo Rule Condition Editor, tal y como lo muestra la siguiente ilustración:
Como ya mencionamos con anterioridad esta caja de diálogo nos permite editar la expresión condicional relacionada con esta regla.
Ahora bien, la modificación de la expresión condicional es muy sencilla. Simplemente debemos editar su código directamente en la caja de diálogo. Ya que nuestro objetivo es evaluar además si el mes actual es noviembre o diciembre y además que el año sea 2007 o superior, el código para la expresión es la siguiente:
System.DateTime.Now.DayOfWeek ==
System.DayOfWeek.Monday && (System.DateTime.Now.Month == 11 ||
System.DateTime.Now.Month == 12) && System.DateTime.Now.Year >= 2007
En este caso estamos usando las propiedades Month y Year de la clase DateTime para determinar el número de mes y el año actuales respectivamente. Nótese también cómo estamos usando diferentes operadores lógicos y relacionales en conjunto para determinar nuestra expresión condicional.
Finalmente sería buena idea modificar los mensajes que queremos mostrar en la consola cuando la regla se evalúa verdadera o falsa. Para esto modifiquemos el código de los métodos que ejecutan las actividades codeActivity1 y codeActivity2 (EsLunes() y NoEsLunes() respectivamente):
private void EsLunes(object sender, EventArgs e)
{ Console.WriteLine("Es lunes, es noviembre o diciembre del año 2007 o superior!"); }
private void NoEsLunes(object sender, EventArgs e)
{ Console.WriteLine("No es lunes, el mes no es noviembre o diciembre o no es el año 2007 o superior."); }
Si efectivamente se cumplen todas las condiciones, es decir, si hoy es un día lunes de noviembre y diciembre del año 2007 o superior, la ejecución de este proyecto mostrará en la consola el siguiente resultado mostrado en la siguiente figura:
Resumen
En este capítulo hablamos acerca de las expresiones condicionales para las reglas en Workflow Foundation. Hablamos también de los diversos operadores y palabras clave que pueden ser usados para escribir las expresiones y modificamos la regla del flujo de trabajo del proyecto anterior, haciendo una validación más robusta y compleja. En el capítulo de Políticas continuaremos viendo las expresiones condicionales y el uso de las palabras clave.
Este artículo fue creado para el curso Desarrollador .NET de la revista Users. http://desarrollador.redusers.com/
Reglas en Workflow Foundation
En la mayoría de las aplicaciones –si no es que en todas- nosotros como programadores necesitamos implementar de un momento a otro flujos de trabajo que controlen el buen comportamiento y operación de nuestras aplicaciones. Workflow Foundation es una tecnología que nos permite definir e implementar flujos de trabajo para nuestras aplicaciones de una manera sencilla y sin necesidad de escribir cuantiosas líneas de código, ya que esta tecnología encapsula en sí misma diversas funcionalidades necesarias para la ejecución y control de dichos flujos. Tal es el caso del motor de Reglas, las cuales son un mecanismo que nos permite definir acciones a tomar según el resultado de la evaluación de las condiciones que se especifiquen al crearla. Esta característica nos permite implementar fácilmente en nuestras aplicaciones reglas reales de negocio para que posteriormente puedan ser evaluadas y que estas controlen el flujo que deba llevar la ejecución de la aplicación. Incluso, podemos utilizar el motor de Reglas en nuestras aplicaciones sin necesidad de usar un flujo de trabajo como tal.
Anteriormente para implementar reglas de negocio en nuestras aplicaciones era necesario escribir un sinnúmero de líneas de código para escribir funciones de control haciendo uso de condiciones y operadores lógicos o aritméticos que el lenguaje de programación nos ofrecía, es decir, lo hacíamos de manera imperativa. No obstante, la implementación de reglas y flujos de trabajo siempre ha sido una verdadera necesidad al crear aplicaciones, pero, ¿qué pasaba cuando nuestra aplicación crecía en número de funcionalidades o en usuarios, sin mencionar: en número de reglas de negocio? El mantenimiento y administración de las aplicaciones y de esas reglas se volvía difícil y con posibilidades de que en cualquier momento ocurriera algún tipo de falla ya que las reglas comúnmente estaban implementadas en el código de la aplicación y cualquier cambio provocaba que se recompilara alguna de las bibliotecas del programa. Ahora, Workflow Foundation nos ofrece una plataforma estándar para implementar dicha funcionalidad pero de manera declarativa.
Podemos definir como "declarativa" a la manera de crear reglas de forma desacoplada a nuestra aplicación, siguiendo la sintaxis y el esquema que define Workflow Foundation usando XAML.
Ejemplos de Reglas
Como mencionamos anteriormente, la necesidad de incorporar reglas de negocio a nuestras aplicaciones es inherente a nuestro trabajo como programadores. Como ejemplos de aplicaciones que usen reglas de negocio podemos mencionar una aplicación de tipo Call Center de alguna empresa de servicios en dónde se tienen que evaluar diversos factores antes de efectivamente dar de alta un pedido para un cliente en específico: Que el cliente no tenga adeudos, o si el cliente tiene adeudos pero si su comportamiento crediticio es bueno entonces proceder a dar de alta su pedido, la disponibilidad de la empresa para otorgar ese producto o servicio basándose en horario, posición geográfica del cliente, inventario, etc. Como podemos deducir hay una infinidad de ejemplos de aplicaciones y de reglas de negocio únicamente estando limitados a nuestra imaginación.
Creación de Reglas en Workflow Foundation
Las reglas en Workflow Foundation se utilizan en actividades con expresiones condicionales y en las políticas. Por el momento nos centraremos brevemente en la primera opción y más adelante platicaremos acerca de las políticas. Como actividades con expresiones condicionales podemos mencionar IfElse, ConditionedActivityGroup, While y Replicator.
Manos a la obra. Comencemos creando un proyecto de tipo flujo de trabajo secuencial de consola utilizando la plantilla Secuential Workflow Console Application en Visual Studio .NET. En este proyecto implementaremos la actividad IfElse como actividad inicial tal como lo muestra la figura 1.
La actividad IfElse requiere que le asignemos la condición que evaluará para determinar si es verdadera o falsa y si se ejecuta la rama izquierda o derecha respectivamente. Para hacer esto seleccionemos la actividad IfElseBranchActivity1 en el diseñador y asignemos la propiedad Condition en la ventana de propiedades (si la ventana de propiedades no es visible en este momento selecciónela del menú View->Properties Window). Si expandimos la lista desplegable de la propiedad Condition se nos presentan dos opciones: Code Condition y Declarative Rule Condition tal y como lo muestra la figura 2.
La primera opción nos permite especificar el nombre de un método que deseemos que sirva como expresión condicional. Asimismo, la segunda opción nos permite definir una regla de manera declarativa que sirva como expresión condicional. Al seleccionar la opción Declarative Rule Condition se nos presentan dos nuevas opciones en la ventana de propiedades: Condition Name y Expression. Condition Name es la propiedad por la cual accedemos al editor de reglas cuando hacemos clic en el botón marcado con "…". Al hacer clic en este botón se nos presenta la caja de diálogo Select Condition tal y como lo muestra la figura 3.
Las reglas en Workflow Foundation son editadas por medio de la caja de diálogo Rule Condition Editor presente en Visual Studio .NET. Este diseñador nos ofrece una interfaz amigable en donde podemos crear y editar diversas reglas para nuestro flujo de trabajo. Para crear una nueva regla hagamos clic en el botón "New…" en la barra de botones en la caja de diálogo. En la caja de diálogo Rule Condition Editor tenemos la posibilidad de editar la expresión condicional que se evaluará en la regla. En este ejemplo, nuestra regla validará si el día actual es lunes así que la expresión será la siguiente:
System.DateTime.Now.DayOfWeek == System.DayOfWeek.Monday
Una vez finalizada la definición de la expresión para nuestra regla hagamos clic en el botón OK para regresar a la caja de diálogo anterior (Select Condition) y ahí hagamos también clic en el botón OK.
Cuando creamos reglas usando el diseñador en Visual Studio .NET, estas son escritas en un archivo llamado [Flujo de trabajo].rules, siendo [Flujo de trabajo] el nombre del flujo de trabajo que estamos utilizando en ese momento. Si abrimos este archivo para inspeccionar su contenido podremos corroborar que efectivamente la regla está implementada declarativamente por medio de XAML. La figura 4 muestra el contenido de este archivo.
Asimismo, una vez compilado nuestro proyecto podemos observar que el archivo es compilado como un recurso en el ensamblado que contiene el flujo de trabajo tal y como lo podemos corroborar en la figura 5, la cual muestra el manifiesto de nuestra aplicación WorkflowConsoleApplication1.exe en la herramienta ILDASM.exe
Ahora bien, aún nuestra aplicación no está finalizada ya que no se ha definido el código que deseemos que se ejecute cuando la expresión de la regla se evalúe verdadera o falsa. Para corregir esto agreguemos una actividad de tipo Code en cada una de las ramificaciones de la actividad IfElse usando el diseñador en Visual Studio .NET. La figura 6 muestra nuestro flujo de trabajo con dichas actividades incorporadas.
Las actividades codeActivity1 y codeActivity2 requieren que se les asigne el método que van a ejecutar cuando estas sean llamadas cuando la regla se evalúe como verdadera y como falsa respectivamente. Para realizar esto hagamos primero clic en la actividad codeActivity1 para mostrar sus propiedades relacionadas en la ventana de propiedades de Visual Studio .NET (si la ventana de propiedades no es visible en este momento puede mostrarla haciendo clic en el menú View->Properties Window). En la propiedad ExecuteCode en la ventana de propiedades escribamos EsLunes. Al hacer [Enter] Visual Studio .NET nos muestra la vista de código fuente de nuestro flujo de trabajo con el cuerpo del método EsLunes() implementado. Para este ejemplo escribamos el siguiente fragmento de código:
Console.WriteLine("Es lunes!");
Hagamos doble clic en el archivo Workflow1.cs en la ventana de Solution Explorer para regresar al diseñador de nuestro flujo de trabajo y repitamos las mismas acciones pero ahora con codeActivity2, la única diferencia radicará en que el nombre del método para codeActivity2 será NoEsLunes; de esta manera Visual Studio .NET generará el método NoEsLunes() como parte del código fuente en Workflow1.cs. Ya que este método se ejecuta cuando la evaluación de la regla esa falsa entonces el código para el método NoEsLunes() es la siguiente:
Console.WriteLine("No es lunes");
Entonces tenemos que los dos métodos completos fueron implementados de la siguiente manera:
private void EsLunes(object sender, EventArgs e)
{ Console.WriteLine("Es lunes!");}
private void NoEsLunes(object sender, EventArgs e)
{ Console.WriteLine("No es lunes");}
Listo. Hemos diseñado una regla utilizando el diseñador incorporado a Visual Studio .NET y esa regla la hemos incorporado como expresión condicional a una actividad dentro de un flujo de trabajo. Para corroborar que todo hasta el momento funciona como debe ser podemos compilar nuestro proyecto usando la opción Build Solution en el menú Build. Una vez compilado el proyecto podemos ejecutarlo haciendo click en la opción Start Without Debugging del menú Debug o con la combinación de teclas Ctrl+F5. Si el día en el que se está ejecutando este código efectivamente es lunes la aplicación mostrará el siguiente resultado en la consola:
De lo contrario, mostrará la consola con el mensaje implementado en el método NoEsLunes() indicado anteriormente.
Resumen
En este artículo hablamos acerca del motor de Reglas incorporado en Workflow Foundation, dimos algunos ejemplos de reglas de negocio reales en aplicaciones y detallamos la manera de creación de reglas en Workflow Foundation utilizando Visual Studio .NET. En secciones posteriores veremos más acerca de las reglas y su utilización por medio de políticas.
Este artículo fue creado para el curso Desarrollador .NET de la revista Users. http://desarrollador.redusers.com/
Por medio de este post también quiero unirme a la bienvenida del buen Alfredo Ceballos a esta escuadra.
Bienvenido Alfredo!!!
Por fin lo que fue anunciado hace algunos meses por Scott Guthrie hoy es realidad: Podemos cargar los símbolos de depuración del .NET Framework en nuestras aplicaciones para poder apreciar qué es lo que se está haciendo tras las cortinas.
Los pasos completos para configurar Visual Studio .NET 2008 los pueden encontrar aquí.
Demonios! Creo que mis estudios en ASP.NET MVC pueden esperar...
Salu2!
UPDATE: Thanks Wika! You're the best!