Gracias a la disponibilidad de Windows Workflow Foundation (WF), Microsoft está introduciendo funciones de flujo de trabajo en la plataforma de desarrollador de .NET. Estas capacidades permiten que los desarrolladores creen flujos de trabajo que se ajusten a un amplio número de situaciones, desde sencillos flujos de trabajo secuenciales hasta complejos flujos de trabajo basados en equipos con sofisticadas interacciones humanas. Al mismo tiempo, se está imponiendo una tendencia que promueve la exposición de las funciones empresariales a través de extremos de servicio encapsulados, que permiten reutilizar y componer las funciones y los procesos empresariales, lo que provoca el ascenso de las arquitecturas orientadas a servicios. Windows Communication Foundation (WCF) está disponible ya para proporcionar a los desarrolladores funciones que permitan desarrollar sistemas conectados de forma sencilla, mediante una API de desarrollador consistente, un tiempo de ejecución de alojamiento sólido y una solución flexible controlada por configuración para asistir en la implementación.
Muestra de informes de gastos
La muestra de código de este artículo está basada en la muestra del flujo de trabajo de informes de gastos que funciona como modelo del proceso empresarial estándar relacionado con el envío y la aprobación de una reclamación de gastos por parte del empleado. La muestra original se ha actualizado para demostrar cómo se puede aprovechar de WCF y .NET 3.0 Framework para alojar este supuesto de forma más eficaz.
Cuando se publicó la primera muestra de informes de gastos, se utilizó .NET Remoting para ofrecer comunicación entre las aplicaciones cliente y la aplicación host que contenía la instancia de tiempo de ejecución del flujo de trabajo.
Hemos refactorizado la implementación de los informes de gastos para realizar la comunicación entre clientes y servicio mediante WCF. La solución también se ha estructurado lógicamente para apartar las distintas preocupaciones dentro de ésta.

Figura 1. Estructura de nuestra solución refactorizada
Es importante entender cómo se utilizan los mensajes dentro del contexto del proceso empresarial, de forma que pueda incorporarlos a su diseño. En el ciclo de vida de los informes de gastos, existen varios puntos de interacción. Revisémoslos brevemente:
| • | Existen tres partes en el proceso: Un cliente, un director y el sistema de host de los informes de gastos. |
| • | El proceso se inicia cuando un cliente envía una nueva reclamación de gastos. |
| • | Utilizamos una directiva de reglas para determinar si la reclamación de gastos se puede aprobar automáticamente. |
| • | Si la reclamación de gastos no se ha aprobado automáticamente, necesitamos un director para que apruebe el informe. El director tiene que comprobar la aprobación de un informe nuevo o recibir una notificación al respecto. |
| • | Si el director no lo aprueba dentro de un margen de demora flexible, el proceso rechazará automáticamente la reclamación. |
| • | Después de revisar una reclamación, el cliente y el director se deben actualizar en función del resultado. |
Mediante WF, podemos modelar este proceso a través de las actividades estándar que se ofrecen con el marco. Podemos utilizar DelayActivity para administrar un desencadenamiento de eventos después de que haya transcurrido un período de tiempo y podemos utilizar el motor de reglas y PolicyActivity para administrar un conjunto flexible de reglas que se interroguen en busca de un resultado.
Dado que se trata de un proceso orientado al usuario, debemos interactuar con los usuarios finales y volver a desencadenar la interacción en el flujo de trabajo. WF ofrece un modelo de programación completo que permite la comunicación entre un host y un flujo de trabajo a través de los servicios locales, HandleExternalEventActivity y CallExternalMethodActivity.
Dado que esto supone un concepto importante en la creación de flujos de trabajo interactivos, analicemos rápidamente cómo se ha diseñado esto en WF.
Para modelar interacciones en WF, debemos diseñar un contrato que exponga un número de eventos y métodos. Tanto el proceso de host como el de flujo de trabajo comprenderán este contrato. El contrato/La interfaz que hemos creado se deben marcar con el atributo [ExternalDataExchange()], que los identifica como diseñados para el intercambio de datos de flujo de trabajo. En nuestra muestra, utilizamos la interfaz IExpenseLocalService con nuestro flujo de trabajo.
A continuación, suscribimos una clase (conocida como Servicio local) que implementa dicha interfaz con el tiempo de ejecución del flujo de trabajo. Las actividades de flujo de trabajo pueden registrarse para eventos o consumir métodos definidos en el tipo de interfaz, y se transmitirán al servicio local que hemos registrado. Esto utiliza un patrón denominado Inversión de control que elimina el estrecho acoplamiento entre el flujo de trabajo y el tipo concreto de servicio local. En nuestro ejemplo, la clase ExpenseLocalService implementa nuestro contrato IExpenseLocalService.
Si el flujo de trabajo se ejecuta primero, se puede ofrecer una bolsa inicial de datos en la que trabajar. Después de que el flujo de trabajo alcance un punto en el que necesite interacción externa, podemos desencadenar un evento que se pueda enlazar a HandleExternalEventActivity en el flujo de trabajo. Esta actividad toma el tipo de interfaz y el evento como argumentos. El flujo de trabajo se activará cuando se desencadene el evento, lo que permite que continúe la ejecución.
Si el flujo de trabajo debe volver a llamar a un servicio local, puede hacerlo mediante CallExternalMethodActivity y proporcionando la interfaz y el nombre de método como argumentos.
Gracias a estas actividades, podemos establecer una comunicación bidireccional dentro del proceso de host con un flujo de trabajo en ejecución y, a través del uso de la Inversión de control dentro de WF, se le protege del estrecho acoplamiento entre los flujos de trabajo y los servicios locales.
Sin embargo, y aunque son más extensas que el proceso de host, debemos permitir interacciones controladas por otros sistemas o incluso por otros usuarios. Podemos lograr este nivel de interacción distribuyendo nuestras operaciones interactivas a través de servicios a los que pueden, a su vez, llamar otros servicios o aplicaciones controladas por usuario. WCF es el marco en el que podemos crear esta función de mensajería de una manera flexible.
Las ventajas clave de nuestro supuesto de integración con WCF son las siguientes:
| • | Podemos desacoplar nuestra implementación de servicio a partir del código de instalación de mensajería. |
| • | Hay mucho menos código y menos complejidad a la hora de conectar nuestros sistemas. |
| • | Tenemos flexibilidad de implementación. |
| • | Podemos utilizar las devoluciones de llamada directas desde el host a los clientes, para ofrecer actualizaciones de información más rápidas y con menos sobrecarga. |
Lista de comprobación de integración
Para completar una integración de WF y WCF, debemos exponer una interfaz de servicio que ofrezca varios puntos de interfaz a los consumidores, donde puedan iniciar o interactuar con un flujo de trabajo en ejecución. El servicio se debe modelar en torno a los puntos en los que el proceso empresarial interactúa con entidades externas, como usuarios implicados en el proceso.

Figura 2. Puntos de interacción en el supuesto de informes de gastos
Para lograr esto, debemos:
| • | Definir contratos de servicio. |
| • | Implemente operaciones de servicio que creen nuevos flujos de trabajo (o interactúen con flujos de trabajo existentes) a través de eventos. |
| • | Aloje una instancia de tiempo de ejecución del flujo de trabajo dentro de un host de servicios. |
Además de simplemente recibir nuestro flujo de trabajo, también podemos usar los canales dúplex de WCF para desencadenar eventos fuera del flujo de trabajo y devolverlos a los clientes de consumo. En el caso de los informes de gastos, esto supone una ventaja, ya que la solución depende de que los clientes sondeen el servicio en busca de actualizaciones de datos regulares. En vez de eso, pueden recibir la notificación directamente desde el servicio.
Para ello, debemos:
| • | Definir un contrato de devolución de llamada. |
| • | Utilizar un enlace que admita un canal dúplex. |
Definición de contratos de servicio
Windows Communication Foundation (WCF) requería que se declarara un contrato formal para definir de manera abstracta las funciones de un servicio y el intercambio de datos. Esto se define en código mediante la declaración de una interfaz.
Cuándo diseñe un servicio empresarial, probablemente utilizará un patrón de colaboración de solicitud y respuesta. Al utilizar este patrón, debe incluir estos tres aspectos en el contrato que se ofrece:
| • | Las operaciones que se están publicando. Éstas son las funciones que publica el servicio para sus consumidores. Éstos son los métodos de la interfaz. |
| • | Mensajes que encapsulan los datos estructurados de cada solicitud y respuesta. Éstos son los argumentos y los tipos de valor devuelto de cada método. En la terminología de WCF, éstos son contratos de mensaje típicos o, en situaciones más sencillas, serán contratos de datos. |
| • | Las definiciones de datos de las entidades empresariales principales que se pueden intercambiar a través del servicio. Éstos forman parte de los mensajes. En la terminología de WCF, éstos serán nuestros contratos de datos. |
Un contrato de servicio se define mediante el uso de un marcado basado en atributos que defina los contratos que exponen operaciones y, a continuación, las operaciones específicas que se están publicando a través de la red.
Cada contrato de servicio se marca explícitamente con el atributo [ServiceContract]. Este atributo se puede declarar con parámetros entre los que se incluyan:
| • | Name. Controla el nombre del contrato declarado en el elemento <portType> de WSDL |
| • | Namespace. Controla el nombre del contrato declarado en el elemento <portType> de WSDL |
| • | SessionMode. Especifica si el contrato requiere un enlace que admita sesiones |
| • | CallbackContract. Especifica el contrato que se va a utilizar para las devoluciones de llamada de clientes |
| • | ProtectionLevel. Especifica si el contrato requiere un enlace que admita la propiedad ProtectionLevel, que se utiliza para declarar los requisitos de cifrado y las firmas digitales |
Declaración de operaciones
Por tanto, el servicio está compuesto de un número de operaciones publicadas. Las operaciones explícitamente suscritas en el contrato que se está marcando con el atributo [OperationContract]. Al igual que ServiceContract, un OperationContract tiene varios parámetros que controlan cómo se puede enlazar con un extremo. Éstos incluyen:
| • | Action. Controla el nombre que identifica esta operación de forma única. Cuando se reciben mensajes en un extremo, el distribuidor utiliza el control y la acción para determinar el método al que se va a llamar. |
| • | IsOneWay. Indica que la operación tomará un mensaje de solicitud, pero no genera respuesta. Esto es diferente al hecho de simplemente devolver un tipo de valor void que aún generará un mensaje de resultado. |
| • | ProtectionLevel. Especifica los requisitos de cifrado o firma que requerirá la operación. |
Aquí se incluye un ejemplo del aspecto de un contrato de servicio en el código.
[ServiceContract]
public interface IExpenseService
{
[OperationContract]
GetExpenseReportsResponse GetExpenseReports();
[OperationContract]
GetExpenseReportResponse GetExpenseReport(GetExpenseReportRequest
getExpenseReportRequest);
}
Declaración de entidades de datos y mensajes
Probablemente deseará modelar sus mensajes como clases que definan la carga útil o el cuerpo de cada uno de los mensajes que envíe. Esto se parece a la forma en que se modelan los mensajes mediante herramientas, como WS Contract First (WSCF), al crear servicios Web con ASP.NET.
WCF utiliza de forma predeterminada un motor de serialización denominado DataContractSerializer para serializar y deserializar datos (es decir, para convertirlo a y desde XML). Usamos DataContractSerializer agregando una referencia al espacio de nombres System.Runtime.Serialization y, a continuación, marcamos nuestra clase con el atributo [DataContract] y los miembros que se van a publicar con el atributo [DataMember].
[DataContract]
public class GetExpenseReportsResponse
{
private List<ExpenseReport> reports;
[DataMember]
public List<ExpenseReport> Reports
{
get { return reports; }
set { reports = value; }
}
}
Las entidades de datos que se utilizan dentro de los mensajes representan las entidades incluidas dentro del dominio empresarial. Al igual que los contratos de mensajes, podemos usar DataContractSerializer y los atributos para suscribir explícitamente miembros que se están distribuyendo; o, si sólo vamos a modelar datos, podemos usar un enfoque de campos públicos y marcar la clase como serializable.
En la muestra, hemos utilizado el enfoque de contrato de datos para marcar la mensajería. En situaciones reales, a menudo tendrá que tratar con esquemas más complicados, el uso de atributos en los esquemas y los requisitos para utilizar encabezados de SOAP. WCF se encarga de estos casos extremos gracias a la capacidad de definición de clases marcadas con el atributo [MessageContract], que describe todo el sobre de SOAP, en vez de sólo el cuerpo.
Alojamiento del tiempo de ejecución del flujo de trabajo
Los servicios suelen tener en cuenta cualquier comportamiento simultáneo en el que se cree una instancia nueva del tipo de servicio y se mantenga durante el ciclo de vida de una sesión. Para usar el flujo de trabajo en esta situación, debemos crear una instancia de tiempo de ejecución del flujo de trabajo y mantenerla durante el ciclo de vida de la instancia de host de servicios, en vez de hacerlo por llamada.
El enfoque recomendado es utilizar una clase de extensión que se activará tras la creación del host de servicios. Esta extensión creará y mantendrá una instancia global del tiempo de ejecución del flujo de trabajo, permitiendo el acceso por cada una de las instancias de servicio independientes.
Para implementar una extensión en ServiceHost, cree una clase que implemente IExtension<ServiceHostBase.> En la solución, puede encontrar un ejemplo de esto en la clase WfWcfExtension que reside en el proyecto de código WcfExtensions.
Es necesario implementar dos métodos: Attach, que se denominará como la extensión adjunta a su objeto principal y Detach, que se denominará como el objeto principal que se está descargando.
El método Attach, que se muestra aquí, crea una instancia nueva de WorkflowRuntime y la instancia con los servicios necesarios. Almacenaremos esto en un campo privado local denominado workflowRuntime.
void IExtension<ServiceHostBase>.Attach(ServiceHostBase owner)
{
workflowRuntime = new WorkflowRuntime(workflowServicesConfig);
ExternalDataExchangeService exSvc = new ExternalDataExchangeService();
workflowRuntime.AddService(exSvc);
workflowRuntime.StartRuntime();
}
Como puede ver, nuestra inicialización del tiempo de ejecución del flujo de trabajo implica también la adición de instancias de servicio al tiempo de ejecución, antes de iniciarlo. Al crear sus soluciones, generalmente recomendamos agregar cualquier servicio antes de iniciar el tiempo de ejecución. Sin embargo, si el acoplamiento es una preocupación, podría ser más sensato utilizar un enfoque de enlace en tiempo de ejecución.
En nuestro ejemplo, como parte del método SetUpWorkflowEnvironment de la clase ExpenseService, agregamos una instancia de ExpenseLocalService en ExternalDataExchangeService después de que se haya iniciado WorkflowRuntime.
El método Detach que se muestra aquí desconecta el tiempo de ejecución llamando a StopRuntime.
void IExtension<ServiceHostBase>.Detach(ServiceHostBase owner)
{
workflowRuntime.StopRuntime();
}
Dado que WorkflowRuntime se crea e inicializa como parte del inicio del host de servicios, cualquier flujo de trabajo existente podrá continuar antes de realizar las llamadas de servicio. Si termina el host de servicios termina, el tiempo de ejecución del flujo de trabajo se desconectará limpiamente.
Nota:
Recomendamos que utilice la persistencia de flujo de trabajo (por ejemplo, SqlWorkflowPersistenceService) al alojar flujos de trabajo y modelar flujos de trabajo de ejecución prolongada, que es la norma habitual. Esto le ofrecerá un mecanismo de persistencia de estado para sobrevivir a cualquier reinicio de la aplicación o de procesos.
Creación de operaciones de servicio
Para crear una clase que contenga el comportamiento de servicio, debe implementar una o más interfaces que definan un contrato de servicio.
public class ExpenseService :
IExpenseService,
IExpenseServiceClient,
IExpenseServiceManager
Para integrar con el flujo de trabajo, nuestros métodos de servicio no contendrán lógica empresarial, sino que en su lugar contendrán código para controlar o desencadenar eventos en un flujo de trabajo en ejecución que encapsule el proceso empresarial.
Para las operaciones que tratan con el flujo de trabajo, iniciaremos un nuevo flujo de trabajo o interactuaremos con un flujo de trabajo ya en ejecución.
La creación de una instancia de flujo de trabajo nueva requiere que utilicemos WorkflowRuntime para crear una instancia nueva del tipo de flujo de trabajo deseado. Ya hemos creado una en nuestra clase de extensión ServiceHost. Para obtener una referencia a esta instancia, debemos encontrar nuestra extensión personalizada con OperationContext.
WfWcfExtension extension = OperationContext.Current.Host.Extensions.Find<WfWcfExtension>(); workflowRuntime = extension.WorkflowRuntime;
OperationContext es una clase que nos ofrece acceso al contexto de ejecución del método de servicio. Como puede ver en el código anterior, ofrece un valor Singleton denominado Current que nos facilita el contexto del método de servicio actual. Llamamos a la propiedad Host para capturar de nuevo una instancia en el ServiceHost que se está ejecutando y, a continuación, encontramos nuestra extensión en función de su tipo.
Después de que tengamos una referencia a nuestra instancia de extensión, podemos volver a WorkflowRuntime a través de nuestra propiedad pública y utilizarla para crear una instancia nueva de nuestro SequentialWorkflow.
Guid workflowInstanceId =
submitExpenseReportRequest.Report.ExpenseReportId;
Assembly asm = Assembly.Load("ExpenseWorkflows");
Type workflowType = asm.GetType("ExpenseWorkflows.SequentialWorkflow");
WorkflowInstance workflowInstance =
workflowRuntime.CreateWorkflow(workflowType, null, workflowInstanceId);
workflowInstance.Start();
expenseLocalService.RaiseExpenseReportSubmittedEvent(
workflowInstanceId, submitExpenseReportRequest.Report);
En el código anterior, creamos una instancia de flujo de trabajo nueva basada en un tipo predefinido. Si bien esto se podía lograr a través de la instancia de tipo directo, esto demuestra que en realidad podríamos tener flexibilidad para crear un flujo de trabajo basado en una regla dinámica en el tiempo de ejecución, en vez de hacerlo a través de un enlace con tipos más sólidos.
La última línea señala el inicio del flujo de trabajo mediante el desencadenamiento de un evento manejado por el primer HandleExternalEventActivity del flujo de trabajo. Esto se desencadena a través de una instancia de la clase ExpenseLocalService. En la muestra, ExpenseLocalService se utiliza para interactuar con el flujo de trabajo a través del inicio de nuevos flujos de trabajo o el desencadenamiento de eventos en flujos de trabajo existentes. Utilizamos esta clase como mecanismo de encapsulación de nuestro proceso empresarial. Internamente, esto se implementa con WF.

Figura 3. Nuestro flujo de trabajo se inicia con HandleExternalEventActivity.
El otro tipo de situación con la que trataremos es llamar de nuevo a un flujo de trabajo existente y desencadenar eventos. Debemos desencadenar un evento en el motor de flujo de trabajo que provoque que nuestro flujo de trabajo existente reciba el evento y continúe el procesamiento.
Un ejemplo de dónde se producirá esto dentro del flujo de informes de gastos se produce cuando se necesita al Director de aprobación. El flujo de trabajo llama al método externo para RequestManagerApproval que desencadenará una alerta para el director acerca de que deben aprobar o rechazar un nuevo informe de gastos.
El flujo de trabajo contiene ListenActivity, que se bloqueará hasta que se haya producido uno de los eventos posibles. En este caso, recibimos un evento que indica que un director ha revisado el informe o que ha transcurrido nuestro tiempo de espera en función de DelayActivity.

Figura 4. Flujo de actividad personalizado de ManagerApproval
Guid workflowInstanceId =
submitReviewedExpenseReportRequest.Report.ExpenseReportId;
ExpenseReportReviewedEventArgs e =
new ExpenseReportReviewedEventArgs(workflowInstanceId, report, review);
if (ExpenseReportReviewed != null)
{
ExpenseReportReviewed(null, e);
}
Cuando un director revisa un informe con ManagerApplication, se vuelve a realizar una llamada de servicio al host solicitando el método SubmitReviewedExpenseReport que desencadena el evento ExpenseReportReviewed.
Al desencadenar un evento HandleExternalEventActivity en el flujo de trabajo, debe conocer el GUID de la instancia del flujo de trabajo con que tratamos, para que el evento se pueda dirigir.
Cada evento se desencadena con EventArgs, que permite volver a transferir datos al flujo de trabajo a través del modelo de eventos. En este caso, podemos transferir detalles del estado actual del informe y los datos que nos dan contexto acerca de la actividad de revisión.
En el flujo de trabajo, los eventos se transmiten automáticamente al flujo de trabajo a través de las propiedades en HandleExternalEventActivity.

Figura 5. Estamos transmitiendo HandleExternalEventActivity a la interfaz de IExpenseLocalService.
Especifique el tipo de interfaz que se debe marcar con el atributo [ExternalDataExchange] y, a continuación, el evento de esa interfaz al que se va a suscribir HandleExternalEventActivity.
Los argumentos del evento se deben derivar desde la clase ExternalDataEventArgs. Como mínimo, esto significa que cada evento contendrá el contexto, al igual que el valor InstanceId del flujo de trabajo. El tiempo de ejecución del flujo de trabajo administra el enrutamiento del evento a la instancia de flujo de trabajo correcta con el fin de continuarlo. Si utilizamos un servicio de persistencia, el tiempo de ejecución también administrará la hidratación y la rehidratación de cualquier estado de ejecución del flujo de trabajo durante el transcurso de su ejecución.
Alojamiento del servicio
Para alojar un servicio de WCF, debemos ejecutarlo dentro del contenedor ServiceHost.
Para revisar cómo se puede lograr el alojamiento con WCF, permítanos recordar las alternativas que tenemos disponibles:
| • | Para procesos estándar de Windows, se puede crear y abrir manualmente una instancia de ServiceHost. |
| • | Al alojar un extremo web (servicio web) a través de Microsoft Internet Information Services (IIS) 6.0, usamos un HttpHandler personalizado facilitado bajo el espacio de nombres System.ServiceModel. |
| • | Al alojar bajo IIS 7, podemos utilizar Windows Activation Service (WAS) para alojar nuestros extremos. |
Generalmente, si está creando servicios web, le conviene elegir el alojamiento mediante Internet Information Services. Si está creando un extremo de instancia única que funcione como demonio, generalmente le conviene elegir el alojamiento a través de un servicio de Windows.
En nuestro ejemplo, estamos alojando la instancia de servicio principal dentro de una aplicación de consola de Windows de forma parecida a cómo se alojaría un servicio de Windows.
Para implementar un servicio, debemos crear una instancia de la clase ServiceHost y abrir sus extremos para cada tipo de servicio que queramos publicar. ServiceHost toma varios argumentos como parte de su constructor; sin embargo, el argumento principal es un argumento Type o una instancia de una clase que implementa ServiceContract.
| • | Utilice Type cuando desee utilizar PerCall o instancias de PerSession. |
| • | Utilice una instancia única cuando utilice instancias de Single. |
Después de haber establecido un host, analizará sintácticamente cualquier configuración disponible y la fusionará con cualquier configuración agregada explícitamente, con el fin de determinar los extremos disponibles y abrirlos para publicar. A medida que se reciben llamadas de clientes, las solicitudes se procesan en nuevos subprocesos de trabajo en segundo plano y se dirigen a las operaciones de servicio apropiadas, según les indica el nombre de contrato de SOAP y la acción del mensaje.
using (ServiceHost serviceHost = new ServiceHost(new ExpenseService()))
{
WfWcfExtension wfWcfExtension =
new WfWcfExtension("WorkflowRuntimeConfig");
serviceHost.Extensions.Add(wfWcfExtension);
serviceHost.Open();
// block the process at this point, for example Console.ReadLine();
serviceHost.Close();
}
Al configurar ServiceHost, debe hacerlo así antes de abrir los extremos para las conexiones. Puede hacerlo así, como le hemos mostrado anteriormente, interactuando con el objeto de host antes de llamar a .Open(). Es recomendable que utilice un ámbito de uso para que ServiceHost esté dispuesto antes del uso y que llame explícitamente a Close() al final de ese ámbito para desconectar limpiamente cualquier extremo o conexión activos.
Configuración de implementación
WCF ofrece un mecanismo para separar las preocupaciones de implementación de la implementación en sí mediante la configuración de los extremos a través de la configuración XML. Esto ofrece a los administradores la capacidad de modificar la directiva de un servicio sin tener que perfeccionar el código.
Cada servicio se publica en uno o más extremos. Un extremo es simplemente un punto de conexión dirigible en el que los clientes pueden consumir el servicio. En WCF, cada extremo se declara con tres atributos que se han popularizado como los ABC de WCF.
Son Address, Binding y Contract.
Address: La ubicación dirigible única de este extremo. Generalmente, será un URI que le proporciona la dirección absoluta en la que el servicio está escuchando solicitudes; por ejemplo: http://myhost/myservice o net.tcp://myhost:400/myservice
Binding: La directiva que dicta el protocolo para la comunicación entre el servicio y sus consumidores. Binding especifica aspectos tales como el tipo del transporte que se está utilizando, la forma en que se están codificando los mensajes y la forma en que se serializan los datos. WCF incluye varios valores Binding “de fábrica” que admiten la mayoría de las situaciones comunes.
Contract: Las operaciones y los datos que se van a publicar según hayamos definido a través de una interfaz en el código.
Para configurar nuestro servicio, debemos declarar una configuración que declare nuestro servicio y configurar cualquier número de extremos para el servicio. Dado que un servicio podría implementar cualquier número de contratos, esto afectará también al número de extremos que tenga que publicar.
A continuación le mostramos una configuración de ejemplo.
<services>
<service name="ExpenseServices.ExpenseService">
<endpoint
address="http://localhost:8081/ExpenseService/Manager"
binding="wsHttpBinding"
contract="ExpenseContracts.IExpenseServiceManager" />
<endpoint
address="http://localhost:8081/ExpenseService/Client"
binding="wsDualHttpBinding"
contract="ExpenseContracts.IExpenseServiceClient" />
</service>
</services>
En este ejemplo de configuración, declaramos una configuración para el servicio de tipo ExpenseServices ExpenseService. Esto permite que el tiempo de ejecución encuentre la configuración cuando creamos una instancia de un nuevo ServiceHost basado en este tipo.
Consumo de servicios
El consumo de servicios a través de WCF se realiza con la clase ChannelFactory.ChannelFactory utiliza el patrón de fábrica para ofrecernos instancias proxy de un contrato de servicio que se conecta con el extremo especificado en la configuración. Podemos configurar la fábrica con información de tiempo de ejecución, como credenciales de seguridad y certificados para cifrado de mensajes, o para determinar la información del extremo dinámicamente.
private IExpenseServiceManager CreateChannelExpenseServiceManager()
{
ChannelFactory<IExpenseServiceManager> factory = new
ChannelFactory<IExpenseServiceManager>("ExpenseServiceManager");
IExpenseServiceManager proxy = factory.CreateChannel();
return proxy;
}
Como puede ver, inicialmente creamos una instancia de la fábrica, que utiliza un argumento genérico para que el contrato de servicio nos permita construir una fábrica más precisa que devolverá sólo instancias del contrato deseado. También especificamos un argumento que determine la configuración que se utilizará para el extremo. En este caso, estamos utilizando una configuración de extremo denominada ExpenseServiceManager, que se refiere a la configuración que tenemos en nuestro archivo de configuración de la aplicación.
<system.serviceModel>
<client>
<endpoint name="ExpenseServiceManager"
address="http://localhost:8081/ExpenseService/Manager"
binding="wsHttpBinding"
contract="ExpenseContracts.IExpenseServiceManager" />
</client>
</system.serviceModel>
Puede comprobar que la definición del extremo coincide exactamente con la definición que se declara en la configuración del host. Generalmente, la única vez que tendrá una configuración diferente será cuando la dirección del cliente sea diferente a la del servidor debido a que se está implementando una configuración de red o un comportamiento personalizado.
Si ha instalado Windows SDK, podrá disponer de una herramienta (svcutil) con el fin de automatizar la creación de una configuración de extremos y clases proxy, que podrá integrar en su solución. El servicio de destino debe publicar una descripción de sus metadatos a través de WSDL o WS-Metadataexchange para poder utilizar esta herramienta
Configuración de canales dúplex
Hasta ahora, hemos supuesto que nuestro flujo de comunicación utilizará un patrón de colaboración de solicitud y respuesta, donde los mensajes los envía un consumidor y los responde un servicio. WCF admite varios flujos de mensajes alternativos, como la comunicación dúplex de un solo sentido (”Fire and Forget”) o bidireccional. Si tratamos con un flujo de mensajes en el que cualquier parte puede iniciar una conversación, tendremos que utilizar un dúplex o un canal bidireccional. Los canales dúplex pueden resultar muy efectivos para sistemas conectados de forma más sólida en los que se puedan enviar datos desde cualquier dirección. Un ejemplo de dónde le podría resultar esto útil es a la hora proporcionar devoluciones de llamada desde eventos.
Implementación de devoluciones de llamada de clientes
Las devoluciones de llamada de clientes se implementan en WCF a través de un concepto denominado CallbackContracts. Para el contrato que publiquemos, podemos nombrar un segundo contrato para definir las operaciones que los clientes publicarán y cuyas llamadas se puedan devolver a través de la ejecución de código en el servicio.
Para declarar CallbackContract, especifique el tipo de interfaz como parte del contrato de servicio desde el que se estará devolviendo la llamada.
[ServiceContract(CallbackContract = typeof(IExpenseServiceClientCallback))]
También es necesario usar un enlace que admita canales dúplex, como netTcpBinding o wsDualHttpBinding. El dúplex sobre TCP se logra mediante una conexión bidireccional que se establece y mantiene en todo el intercambio de mensajes. Sobre HTTP, esto se logra mediante una devolución de llamada a una escucha de cliente. Dado que puede que el cliente no sea consciente de su ruta de regreso o que desee definir esto de forma más sólida a través de una configuración, podemos utilizar una configuración de enlace personalizada para declarar una dirección clientBaseAddress alternativa.
<endpoint binding="wsDualHttpBinding"
bindingConfiguration="AlternativeClientCallback"/>
<bindings>
<wsDualHttpBinding>
<binding name="AlternativeClientCallback"
clientBaseAddress="http://localhost:8082/ExpenseService/ClientCallback"/>
</wsDualHttpBinding>
</bindings>
Implementación de devoluciones de llamada en el cliente
Implementar un contrato de devolución de llamada es exactamente lo mismo que implementar un contrato de servicio. Debemos proporcionar una implementación de la interfaz que hemos definido.
class CallbackHandler : IExpenseServiceClientCallback
{
public void ExpenseReportReviewed(
ExpenseReportReviewedRequest expenseReportReviewedRequest)
{
// We implement client logic to respond to the callback here.
}
}
Para permitir que el host tenga una instancia de nuestra clase CallbackHandler en la que devolver la llamada, debemos configurar nuestro canal de cliente de tal manera que sea consciente de la naturaleza dúplex de la conexión.
Antes de nada, como hemos descrito anteriormente, utilizaremos un enlace que admita canales dúplex. En segundo lugar, cuando inicialicemos nuestra conexión con el extremo de servicio, utilizaremos una versión subclasificada de ChannelFactory, denominada DuplexChannelFactory, que creará automáticamente una conexión dúplex para el servicio.
private IExpenseServiceClient CreateChannelExpenseServiceClient()
{
InstanceContext context = new InstanceContext(new CallbackHandler());
DuplexChannelFactory<IExpenseServiceClient> factory =
new DuplexChannelFactory<IExpenseServiceClient>(context,
"ExpenseServiceClient");
IExpenseServiceClient proxy = factory.CreateChannel();
return proxy;
}
La diferencia clave al usar DuplexChannelFactory es que inicializamos una instancia de nuestra clase CallbackHandler y la transferimos al constructor para que la fábrica inicialice un contexto que se utilice en las devoluciones de llamada.
Implementación de devoluciones de llamada desde el host
Desde la perspectiva del host, podemos obtener una referencia para devolver la llamada a nuestro cliente a través del canal de devolución de llamada que se define en nuestro contrato IExpenseServiceClient.
[ServiceContract(CallbackContract = typeof(IExpenseServiceClientCallback))] public interface IExpenseServiceClient : IExpenseService
El atributo CallbackContract declara la interfaz que define el contrato para las devoluciones de llamada realizadas desde el host.
Para realizar la devolución de llamada, obtenemos una referencia al contrato de devolución de llamada llamando a OperationContext.Current.GetCallbackChannel, como se muestra aquí.
IExpenseServiceClientCallback callback =
OperationContext.Current.GetCallbackChannel
<IExpenseServiceClientCallback>();
callback.ExpenseReportReviewed(new
ExpenseReportReviewedRequest(e.Report));
Después de que tengamos una referencia a nuestro canal de devolución de llamada, podemos llamarlo con normalidad.
A continuación nuestro amigo Jeremy Boyd nos da algunas instrucciones que debemos recordar al diseñar los servicios:
| • | Debe incluir flujos de trabajo de larga ejecución a través del uso de un servicio de persistencia. |
| • | Una operación de servicio puede interactuar a través de un flujo de trabajo en ejecución, a través del desencadenamiento de eventos. Los flujos de trabajo se deben diseñar para desencadenar eventos cuando se exija atención y responder a eventos cuando se esté interactuando externamente (por ejemplo, desde un servicio o un usuario externos). |
| • | Los flujos de trabajo se ejecutarán asincrónicamente con respecto a la llamada de servicio; por tanto, realice un diseño apropiado cuando piense en la devolución de datos desde el servicio o en qué estado se podrían encontrar los datos en ese momento. Si desea utilizar un enfoque sincrónico, puede utilizar la clase ManualWorkflowSchedulerService para permitir una programación manual de la ejecución de flujos de trabajo. |
Fuente: http://www.microsoft.com/spanish/msdn/articulos/
Publicado por: Jeremy Boyd, consultor experto técnico de Intergen, proveedor de soluciones de Nueva Zelanda y Partner Gold Certified de Microsoft, así como Director regional de MSDN en la comunidad de Nueva Zelanda.
Escrito en Tecnologia Microsoft .Net