Integração entre o Windows Workflow Foundation e o Windows Communication Foundation

Intergen

Publicado em: 19 de fevereiro de 2007

Aplica-se a:

  • Windows Workflow Foundation

  • Windows Communication Foundation

  • Microsoft Visual Studio 2005

Resumo: Este artigo oferece uma visão geral de como os fluxos de trabalho criados pelo Windows Workflow Foundation (WF) podem ser hospedados em serviços criados pelo Windows Communication Foundation (WCF). O artigo também descreve como podemos usar alguns dos recursos abrangentes fornecidos pelo WCF para facilitar retornos de chamada de evento do cliente utilizando um canal duplex. Este artigo também contém links para páginas em inglês. (15 páginas impressas.)

Conteúdo

Nesta página

Introdução
Exemplo de relatório de despesas
Lista de verificação da integração
Definindo contratos de serviço
Hospedando o tempo de execução do fluxo de trabalho
Configurando a implantação
Consumindo o serviço
Configurando canais duplex
Conclusão
Para obter mais informações
Agradecimentos

Clique aqui para baixar o "Windows Workflow Foundation Sample Integrating WF and WCF".

Introdução

Com a disponibilidade do Windows Workflow Foundation (WF), a Microsoft está introduzindo recursos de fluxo de trabalho na plataforma do desenvolvedor .NET. Esses recursos permitem aos desenvolvedores criar fluxos de trabalho para atender a vários cenários, de fluxos de trabalho seqüenciais e simples a fluxos de trabalho baseados em máquina de estado complexo com interações humanas sofisticadas.

Ao mesmo tempo, há a intenção de promover recursos comerciais a serem expostos por meio de pontos de extremidade de serviço encapsulado, permitindo a reutilização e a composição de funções e processos empresariais que dão origem às arquiteturas SOA. O Windows Communication Foundation (WCF) foi disponibilizado para ajudar a fornecer aos desenvolvedores recursos para desenvolverem facilmente sistemas conectados, oferecendo uma API de desenvolvedor consistente, um tempo de execução de hospedagem robusto e uma solução flexível controlada por configuração para ajudar no desenvolvimento.

No final deste documento, há uma lista de recursos adicionais que podem ser usados para conhecer melhor o WF e o WCF.

Exemplo de relatório de despesas

O exemplo de código deste artigo tem como base o exemplo de fluxo de trabalho de relatório de despesas que modela um processo empresarial padrão de envio e aprovação de uma solicitação de reembolso de despesas de um funcionário. O exemplo original foi atualizado para demonstrar como você pode tirar proveito do WCF e do .NET 3.0 Framework para hospedar esse cenário de maneira mais eficiente.

Quando o primeiro exemplo de relatório de despesas foi lançado, ele utilizou o .NET Remoting para oferecer comunicação entre os aplicativos cliente e o aplicativo host que continha a instância de tempo de execução do fluxo de trabalho.

Nós reformulamos a implementação do relatório de despesas para fazer a comunicação entre clientes e serviço usando o WCF. A solução também foi estruturada logicamente para afastar as várias preocupações a ela relacionadas.

Figura 1. A estrutura de nossa solução reformulada

Bb266709.Bb266709_intgrwfwcf01(pt-br,MSDN.10).gif

 

É importante compreender como as mensagens são usadas no contexto do processo empresarial, para que você possa incorporá-las ao seu projeto. No ciclo de vida do relatório de despesas, há vários pontos de interação. Vamos examiná-los rapidamente:

  • Existem três partes no processo: um cliente, um gerente e o sistema host do relatório de despesas.

  • O processo começa com um cliente enviando uma nova solicitação de reembolso de despesas.

  • Usamos uma diretiva de regras para determinar se a solicitação de reembolso de despesas pode ser aprovada automaticamente.

  • Se a solicitação de reembolso de despesas não for aprovada automaticamente, precisamos de um gerente para aprovar o relatório. O gerente precisaria conferir um novo relatório para aprovar ou ser notificado disso.

  • Se o gerente não der a aprovação dentro de um prazo flexível, o processo recusará automaticamente a solicitação.

  • Após a análise da solicitação de reembolso de despesas, o cliente e o gerente devem ser informados do resultado.

Com o WF, podemos modelar esse processo usando as atividades padrão fornecidas com a estrutura. Podemos usar a DelayActivity para gerenciar um evento disparado depois de um período de tempo e podemos usar o mecanismo Rules e PolicyActivity para gerenciar um conjunto flexível de regras solicitando um resultado.

Como esse processo é manual, precisamos interagir com os usuários finais e gerar essa interação com o fluxo de trabalho. O WF oferece um modelo de programação abrangente para permitir a comunicação entre um host e um fluxo de trabalho, fornecendo serviços locais, a HandleExternalEventActivity e a CallExternalMethodActivity.

Como esse conceito é importante na criação de fluxos de trabalho interativos, vamos descrever rapidamente como ele foi introduzido no WF.

Para que as interações sejam modeladas no WF, devemos criar um contrato que exponha alguns eventos e métodos. Esse contrato será compreendido pelo fluxo de trabalho e pelo processo do host. O contrato/interface que criamos deve ser marcado com o atributo [ExternalDataExchange()], que o identifica como criado para a troca de dados do fluxo de trabalho. Em nosso exemplo, usamos a interface IExpenseLocalService em nosso fluxo de trabalho.

Em seguida, inscrevemos uma classe (conhecida como Local Service) que implementa essa interface com o tempo de execução do fluxo de trabalho. As atividades do fluxo de trabalho podem registrar-se para obter eventos ou consumir métodos definidos no tipo de interface e serão conectadas ao Local Service que registramos. É usado um padrão denominado inversão de controle que remove a ligação forte entre o fluxo de trabalho e o tipo concreto do Local Service. Em nosso exemplo, a classe ExpenseLocalService implementa nosso contrato IExpenseLocalService.

Quando o fluxo de trabalho é executado pela primeira vez, ele pode receber um conjunto inicial de dados para operar. Depois de alcançar um ponto no qual precise de interação externa, podemos gerar um evento que possa ser ligado a uma HandleExternalEventActivity no fluxo de trabalho. Essa interface usa um tipo de interface e um evento como argumentos, e o fluxo de trabalho será ativado quando o evento for gerado, permitindo a continuação da execução.

Se o fluxo de trabalho tiver de fazer um retorno de chamada para um Local Service, poderá fazer isso usando uma CallExternalMethodActivity e fornecendo os nomes da interface e do método como argumentos.

Com essas atividades, podemos executar a comunicação bidirecional no processo do host com um fluxo de trabalho em execução; e, com o uso do padrão de inversão de controle do WF, você fica livre da forte ligação entre fluxos de trabalho e seus serviços locais.

No entanto, além do processo do host, devemos permitir interações controladas por outros sistemas ou até por seres humanos. Podemos alcançar esse nível de interação distribuindo nossas operações interativas pelos serviços que podem, por sua vez, ser chamados por outros serviços ou por aplicativos controlados pelo usuário. O WCF é a estrutura na qual podemos criar esse recurso de mensagens de uma maneira flexível.

Os principais benefícios de nosso cenário de integração com WCF são que:

  • Podemos desassociar nossa implementação de serviço do código do serviço de mensagens.

  • Há bem menos código e complexidade para conectar nossos sistemas.

  • Temos flexibilidade na implantação.

  • Podemos usar retornos de chamada diretos do host para os clientes para fornecer atualizações de informações mais rápidas e com menos sobrecarga.

Lista de verificação da integração

Para concluir uma integração entre o WF e o WCF, devemos expor uma interface de serviço que fornecerá alguns pontos de interface aos consumidores, onde esses poderão iniciar um fluxo de trabalho ou interagir com um em funcionamento. O serviço deve ser modelado usando os pontos nos quais o processo empresarial interage com entidades externas, como as pessoas envolvidas no processo.

Figura 2. Pontos de interação no cenário de relatório de despesas

Bb266709.Bb266709_intgrwfwcf02(pt-br,MSDN.10).gif

Para conseguir isso, devemos:

  • Definir contratos de serviço.

  • Implementar operações de serviço que criarão novos fluxos de trabalho (ou interagirão com os existentes) por meio de eventos.

  • Hospedar uma instância de tempo de execução do fluxo de trabalho dentro de um host de serviço.

Além de apenas hospedar nosso fluxo de trabalho, também podemos usar os canais duplex do WCF para gerar eventos a partir do fluxo de trabalho para os clientes consumidores. Isso é benéfico no relatório de despesas, pois a solução conta com os clientes monitorando o serviço em busca de atualizações de dados regulares. Em vez disso, eles podem ser notificados diretamente pelo serviço.

Para isso, devemos:

  • Definir um contrato de retorno de chamada.

  • Usar uma ligação que oferecerá suporte a um canal duplex.

Definindo contratos de serviço

O Windows Communication Foundation (WCF) exigiu que um contrato formal declarasse abstratamente as definições dos recursos de um serviço e a troca de dados. Isso é definido no código por meio da declaração de uma interface.

Ao criar um serviço de negócios, você geralmente usa um padrão de colaboração do tipo solicitação/resposta. Ao usar esse padrão, você deve ater-se aos três aspectos do contrato oferecido, que são:

  • As operações publicadas. São os recursos que o serviço publica para seus consumidores. São os métodos da interface.

  • As mensagens que encapsulam os dados estruturados de cada solicitação e resposta. São os argumentos e os tipos de retorno de cada método. Na terminologia do WCF, são geralmente contratos de mensagem ou, em cenários mais simples, contratos de dados

  • Definições de dados das principais entidades comerciais que podem ser trocadas pelo serviço. Farão parte das mensagens. Na terminologia do WCF, serão nossos contratos de dados.

Um contrato de serviço é definido com o uso de uma marcação baseada em atributo que estabelece os contratos que expõem operações e, em seguida, as operações específicas que serão publicadas remotamente.

Cada contrato de serviço é marcado explicitamente com o atributo [ServiceContract]. Esse atributo pode ser declarado com parâmetros que incluem:

  • Name. Controla o nome do contrato declarado no elemento <portType> do WSDL.

  • Namespace. Controla o nome do contrato declarado no elemento <portType> do WSDL.

  • SessionMode. Especifica se o contrato exige uma ligação que ofereça suporte a sessões.

  • CallbackContract. Especifica o contrato a ser usado nos retornos de chamada do cliente.

  • ProtectionLevel. Especifica se o contrato exige uma ligação que ofereça suporte à propriedade ProtectionLevel, usada para declarar requisitos de criptografia e assinaturas digitais.

Declarando as operações

O serviço é composto de várias operações publicadas. As operações são explicitamente concedidas ao contrato com a marcação do atributo [OperationContract]. Como o ServiceContract, um OperationContract possui alguns parâmetros que controlam como ele pode ser ligado a um ponto de extremidade. Eles incluem:

  • Action. Controla o nome para identificar essa operação exclusivamente. Quando as mensagens são recebidas por um ponto de extremidade, o dispatcher usa controle e ação para determinar o método a ser chamado.

  • IsOneWay. Indica que a operação usará uma mensagem de solicitação, mas não produzirá resposta. Isso é diferente de simplesmente fornecer um tipo de retorno void que ainda gerará uma mensagem de resultado.

  • ProtectionLevel. Especifica os requisitos de criptografia ou assinatura que a operação exigirá.

Veja aqui um exemplo de contrato de serviço em código.

[ServiceContract]
public interface IExpenseService
{
        [OperationContract]
        GetExpenseReportsResponse GetExpenseReports();

        [OperationContract]
        GetExpenseReportResponse GetExpenseReport(GetExpenseReportRequest 
getExpenseReportRequest);
}
		

Declarando mensagens e entidades de dados

Talvez você queira modelar suas mensagens como classes que definam a carga ou o corpo de cada mensagem que estará enviando. Isso é semelhante à forma como as mensagens foram modeladas com o uso de ferramentas, como o WS Contract First (WSCF), ao criar serviços Web usando o ASP.NET.

Por padrão, o WCF usa um mecanismo de serialização chamado DataContractSerializer para serializar e desserializar dados (ou seja, convertê-los em/de XML). Usamos o DataContractSerializer adicionando uma referência ao namespace System.Runtime.Serialization e, em seguida, marcamos nossa classe com um atributo [DataContract] e os membros a serem publicados com o [DataMember].

[DataContract]
    public class GetExpenseReportsResponse
    {
        private List<ExpenseReport> reports;

        [DataMember]
        public List<ExpenseReport> Reports
        {
            get { return reports; }
            set { reports = value; }
        }
    }
		

As entidades de dados usadas nas mensagens representam as entidades no domínio da empresa. Como os contratos de mensagem, podemos usar DataContractSerializer e atribuição para permitir explicitamente os membros que estão sendo distribuídos; ou, se pretendemos apenas modelar dados, podemos usar uma abordagem de campos públicos e marcar a classe como serializável.

No exemplo, usamos a abordagem de contrato de dados para marcação de mensagens. Em cenários reais, normalmente você lidará com esquemas mais complicados, com o uso de atributos nos esquemas e com a necessidade de usar cabeçalhos SOAP. O WCF resolve esses casos extremos oferecendo a capacidade de definir uma classe marcada com o atributo [MessageContract] que descreverá o envelope SOAP inteiro, em vez de apenas o corpo.

Para obter mais informações sobre contratos de dados e de mensagens, consulte os respectivos artigos da MSDN Library incluídos na seção Para obter mais informações, no final deste artigo.

Hospedando o tempo de execução do fluxo de trabalho

Normalmente, os serviços permitem um comportamento simultâneo no qual uma nova instância do tipo de serviço será criada e mantida durante o ciclo de vida de uma sessão. Para usar o fluxo de trabalho nessa situação, devemos criar uma instância do tempo de execução do fluxo de trabalho e mantê-la enquanto existir a instância do host de serviço, em vez de fazer isso por chamada.

A abordagem recomendada para isso é usar uma classe de extensão que será ativada com a criação do host de serviço. Essa extensão criará e manterá uma instância global do tempo de execução do fluxo de trabalho, permitindo que ela seja acessada por cada instância de serviço independente.

Para implementar uma extensão no ServiceHost, crie uma classe que implemente IExtension<ServiceHostBase>. Na solução, você encontrará um exemplo disso na classe WfWcfExtension que reside no projeto do código WcfExtensions.

Temos que implementar dois métodos: Attach, que será chamado quando a extensão for anexada ao objeto pai, e Detach, que será chamado quando o objeto pai estiver sendo descarregado.

O método Attach, aqui mostrado, cria uma nova instância do WorkflowRuntime e cria uma instância dela com os serviços necessários. Nós vamos armazená-la em um campo particular local chamado workflowRuntime.

void IExtension<ServiceHostBase>.Attach(ServiceHostBase owner)
{
   workflowRuntime = new WorkflowRuntime(workflowServicesConfig);
   ExternalDataExchangeService exSvc = new ExternalDataExchangeService();
   workflowRuntime.AddService(exSvc);
   workflowRuntime.StartRuntime();
}
		

Como você pode ver, nossa inicialização do tempo de execução do fluxo de trabalho também envolve a adição de instâncias de serviço ao tempo de execução, antes de iniciá-lo. Ao criar soluções, geralmente é recomendado adicionar qualquer serviço antes de iniciar o tempo de execução. No entanto, se houver uma preocupação em relação à ligação, talvez seja mais adequado usar uma abordagem de ligação tardia.

Em nosso exemplo, como parte do método SetUpWorkflowEnvironment na classe ExpenseService, adicionamos uma instância de ExpenseLocalService ao ExternalDataExchangeService depois de o WorkflowRuntime ter sido iniciado.

O método Detach mostrado aqui encerra o tempo de execução chamando StopRuntime.

void IExtension<ServiceHostBase>.Detach(ServiceHostBase owner)
{
   workflowRuntime.StopRuntime();
}
		

À medida que WorkflowRuntime for criado e inicializado como parte da inicialização do host de serviço, qualquer fluxo de trabalho existente poderá continuar antes das chamadas de serviço serem feitas. Quando o host de serviço é encerrado, o tempo de execução do fluxo de trabalho é fechado corretamente.

Observação: é recomendado usar persistência de fluxo de trabalho (por exemplo, SqlWorkflowPersistenceService) ao hospedar fluxos de trabalho e modelar fluxos de execução longa, que é o padrão. Isso fornecerá um mecanismo de persistência de estado para sobreviver a qualquer reinicialização de aplicativo ou processo.

Criando operações de serviço

Para criar uma classe que contenha comportamento de serviço, você deve implementar uma ou mais interfaces que definam um contrato de serviço.

public class ExpenseService :
        IExpenseService,
        IExpenseServiceClient,
        IExpenseServiceManager

		

Para se integrarem ao nosso fluxo de trabalho, nossos métodos de serviço não conterão lógica comercial mas, em vez disso, conterão código para controlar ou gerar eventos em um fluxo de trabalho em execução que encapsulará o processo empresarial.

Para operações que lidem com fluxo de trabalho, iniciaremos um novo fluxo ou interagiremos com um que já esteja sendo executado.

Criar uma nova instância de fluxo de trabalho exige o uso de um WorkflowRuntime para criar uma nova instância do tipo de fluxo de trabalho desejado. Já criamos uma em nossa classe de extensão ServiceHost. Para obter uma referência a essa instância, devemos localizar nossa extensão personalizada usando OperationContext.

WfWcfExtension extension = 
OperationContext.Current.Host.Extensions.Find<WfWcfExtension>();
workflowRuntime = extension.WorkflowRuntime;

		

OperationContext é uma classe que fornece acesso ao contexto de execução do método de serviço. Como você viu no código anterior, ele fornece um singleton chamado Current que fornece o contexto para o método de serviço atual. Chamamos a propriedade Host para buscar de volta uma instância para o ServiceHost no qual estamos executando e, em seguida, localizar nossa extensão com base em seu tipo.

Assim que tivermos uma referência para nossa instância de extensão, poderemos retornar o WorkflowRuntime por meio de nossa propriedade pública e usá-lo para criar uma nova instância de 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);


		

No código anterior, criamos uma nova instância de fluxo de trabalho com base em um tipo predefinido. Embora seja possível fazer isso com a criação direta de uma instância de tipo, isso demonstra que podemos ter a flexibilidade de criar um fluxo de trabalho com base em uma regra dinâmica em tempo de execução, em vez de por meio de uma ligação com imposição de declaração de tipo.

A última linha indica o início do fluxo de trabalho gerando um evento processado pela primeira HandleExternalEventActivity do fluxo. Geramos isso por meio de uma instância da classe ExpenseLocalService. No exemplo, a classe ExpenseLocalService é usada para interagir com o fluxo de trabalho iniciando novos fluxos ou gerando eventos para os existentes. Usamos essa classe como um mecanismo de encapsulamento de nosso processo empresarial. Internamente, estamos fazendo essa implementação com o uso do WF.

Figura 3. Nosso fluxo de trabalho começa com HandleExternalEventActivity.

Bb266709.Bb266709_intgrwfwcf03(pt-br,MSDN.10).gif

 

O outro tipo de situação com a qual vamos lidar é retornar a chamada para um fluxo de trabalho existente e gerar eventos. Devemos gerar um evento para o mecanismo de fluxo de trabalho que faça com que o fluxo existente o receba e continue processamento esse evento.

Um exemplo de onde isso ocorrerá no fluxo de relatório de despesas é quando a aprovação do gerente é necessária. O fluxo de trabalho chamará o método externo para RequestManagerApproval que gerará um alerta para o gerente informando-o de que deve aprovar ou rejeitar um novo relatório de despesas.

O fluxo de trabalho contém uma classe ListenActivity que será bloqueada até que um dos eventos possíveis tenha ocorrido. Nesse caso, recebemos um evento indicando que um gerente analisou o relatório ou o tempo se esgota com base em DelayActivity.

O outro tipo de situação com a qual vamos lidar é retornar a chamada para um fluxo de trabalho existente e gerar eventos. Devemos gerar um evento para o mecanismo de fluxo de trabalho que faça com que o fluxo existente o receba e continue processamento esse evento.

Um exemplo de onde isso ocorrerá no fluxo de relatório de despesas é quando a aprovação do gerente é necessária. O fluxo de trabalho chamará o método externo para RequestManagerApproval que gerará um alerta para o gerente informando-o de que deve aprovar ou rejeitar um novo relatório de despesas.

O fluxo de trabalho contém uma classe ListenActivity que será bloqueada até que um dos eventos possíveis tenha ocorrido. Nesse caso, recebemos um evento indicando que um gerente analisou o relatório ou o tempo se esgota com base em DelayActivity.

Figura 4. O fluxo de atividades personalizado de aprovação do gerente

Bb266709.Bb266709_intgrwfwcf04(pt-br,MSDN.10).gif

 

Guid workflowInstanceId = 
submitReviewedExpenseReportRequest.Report.ExpenseReportId;

ExpenseReportReviewedEventArgs e =
   new ExpenseReportReviewedEventArgs(workflowInstanceId, report, review);

if (ExpenseReportReviewed != null)
{
   ExpenseReportReviewed(null, e);
}

	

Quando um gerente analisa um relatório usando ManagerApplication, é criada uma chamada de serviço para o host que está chamando o método SubmitReviewedExpenseReport que gera um evento ExpenseReportReviewed.

Ao gerar um evento para uma classe HandleExternalEventActivity no fluxo de trabalho, você deverá conhecer o GUID da instância do fluxo com o qual estamos lidando, para que o evento possa ser encaminhado.

Cada evento é gerado com EventArgs, o que nos permite passar dados de volta para o fluxo de trabalho usando o modelo de eventos. Nesse caso, podemos transmitir detalhes do estado atual do relatório e os dados que nos fornecem contexto sobre a atividade de análise.

No fluxo de trabalho, os eventos são conectados automaticamente a ele pelas propriedades de uma HandleExternalEventActivity.

Figura 5. Estamos conectando HandleExternalEventActivity à interface IExpenseLocalService.

Bb266709.Bb266709_intgrwfwcf05(pt-br,MSDN.10).gif

 

Você especifica o tipo de interface que deve ser marcado com o atributo [ExternalDataExchange] e, em seguida, o evento nessa interface no qual HandleExternalEventActivity se inscreverá.

Os argumentos do evento devem ser derivados da classe ExternalDataEventArgs. No mínimo, isso significa que cada evento conterá o contexto, como InstanceId, do fluxo de trabalho. O tempo de execução do fluxo de trabalho gerencia, então, o encaminhamento do evento para a instância correta do fluxo para continuá-lo. Se estiver usando um serviço de persistência, o tempo de execução gerenciará também a hidratação e a reidratação de qualquer estado em execução do fluxo de trabalho enquanto durar sua execução.

Hospedando o serviço

Para hospedar um serviço do WCF, devemos estar executando dentro do contêiner ServiceHost.

Para ver como é possível hospedar usando o WCF, vamos primeiro compreender as alternativas disponíveis:

  • Nos processos padrão do Windows, uma instância de ServiceHost pode ser criada e aberta manualmente.

  • Ao hospedarmos um ponto de extremidade da Web (serviço Web) por meio do IIS (Serviços de Informações da Internet) 6.0 da Microsoft, usamos um HttpHandler personalizado fornecido sob o namespace System.ServiceModel.

  • Ao hospedarmos no IIS 7, podemos usar o Windows Activation Service (WAS) para hospedar nossos pontos de extremidade.

Normalmente, você optará por hospedar usando os Serviços de Informações da Internet se estiver criando serviços Web. Se você estiver criando um ponto de extremidade de uma única instância que funcione como um daemon, optará por hospedar usando um serviço do Windows.

Em nosso exemplo, estamos hospedando a instância principal do serviço em um aplicativo de console do Windows, semelhante a como você hospedaria um serviço do Windows.

Para implantar um serviço, devemos criar uma instância da classe ServiceHost e abrir seus pontos de extremidade para cada tipo de serviço a ser publicado. O ServiceHost usa alguns argumentos como parte de seu construtor; no entanto, o argumento principal é um Type ou uma instância de uma classe que implementa um ServiceContract.

  • Utilize um Type quando desejar usar criação de instância PerCall ou PerSession.

  • Utilize uma única instância ao usar a criação de instância Single.

Para obter mais detalhes sobre criação de instância e concomitância no WCF, consulte o artigo sobre sessões, criação de instâncias e concomitância na MSDN Library.

Depois de um host ser estabelecido, ele analisará a configuração disponível (leia mais sobre isso na seção Configurando a implantação a seguir) e a mesclará com qualquer configuração adicionada explicitamente a fim de determinar os pontos de extremidade disponíveis e abri-los para publicação. À medida que as chamadas são recebidas dos clientes, as solicitações são processadas em novos threads de trabalho em segundo plano e encaminhadas para as operações de serviço apropriadas, conforme direcionadas pelo nome e pela ação de contrato SOAP na mensagem.

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();
}

	

Ao configurar um ServiceHost, você deve fazer isso antes de abrir os pontos de extremidade para conexões. Para isso, como mostrado anteriormente, basta interagir com o objeto host antes de chamar .Open(). É recomendado usar um escopo para já ter o ServiceHost determinado e chamar explicitamente Close() no final desse escopo para encerrar sem problemas quaisquer conexões e pontos de extremidade ativos.

Configurando a implantação

O WCF oferece um mecanismo que separa as preocupações com a implantação da implementação, permitindo configurar os pontos de extremidade por meio da configuração de XML. Esse mecanismo oferece aos administradores a capacidade de modificar a diretiva de um serviço sem precisar redesenvolver o código.

Cada serviço é publicado em um ou mais pontos de extremidade. Um ponto de extremidade é simplesmente um ponto de conexão endereçável no qual os clientes podem consumir o serviço. No WCF, cada ponto de extremidade é declarado com três atributos que foram popularizados como os ABCs do WCF.

São eles Address (Endereço), Binding (Ligação) e Contract (Contrato).

Address: o local endereçável exclusivo do ponto de extremidade. Geralmente, trata-se de um URI que fornece o endereço absoluto no qual o serviço está escutando solicitações, como: http://meuhost/meuserviço ou net.tcp://meuhost:400/meuserviço

Binding: a diretiva que determina o protocolo de comunicação entre o serviço e seus consumidores. Binding especifica aspectos como o tipo de transporte usado, a forma de codificação das mensagens e a maneira de serialização dos dados. O WCF é fornecido com várias ligações predefinidas que oferecem suporte aos cenários mais comuns.

Contract: as operações e os dados sendo publicados, conforme definimos por uma interface no código.

Para configurar nosso serviço, devemos declarar a configuração que o declara e configurar quaisquer pontos de extremidade para o serviço. Como um serviço pode estar implementando qualquer número de contratos, isso também afetará o número de pontos de extremidade que será necessário publicar.

Um exemplo de configuração é mostrado aqui.

<services>
   <service name="ExpenseServices.ExpenseService">
      <endpoint
         address="https://localhost:8081/ExpenseService/Manager"
         binding="wsHttpBinding"
         contract="ExpenseContracts.IExpenseServiceManager" />
<endpoint
         address="https://localhost:8081/ExpenseService/Client"
         binding="wsDualHttpBinding"
         contract="ExpenseContracts.IExpenseServiceClient" />
   </service>
</services>
		

Nesse exemplo de configuração, estamos declarando a configuração do serviço do tipo ExpenseServices.ExpenseService. Isso permite ao tempo de execução localizar a configuração quando criamos uma nova instância de ServiceHost com base nesse tipo. Para obter mais detalhes, consulte WCF Bindings na MSDN Library.

Consumindo o serviço

O consumo de serviços usando o WCF é feito com o uso da classe ChannelFactory. ChannelFactory usa o padrão de fábrica para nos fornecer instâncias de proxy de um contrato de serviço que se conecte ao ponto de extremidade especificado na configuração. Podemos configurar a fábrica com informações sobre tempo de execução, como credenciais e certificados de segurança para a criptografia de mensagens, ou determinar as informações do ponto de extremidade dinamicamente.

private IExpenseServiceManager CreateChannelExpenseServiceManager()
{
   ChannelFactory<IExpenseServiceManager> factory = new
ChannelFactory<IExpenseServiceManager>("ExpenseServiceManager");
   IExpenseServiceManager proxy = factory.CreateChannel();

   return proxy;
}

		

Como você pode ver, inicialmente criamos uma instância da fábrica, que usa um argumento genérico do contrato de serviço para nos permitir criar uma fábrica mais precisa que retornará apenas instâncias do contrato desejado. Também especificamos um argumento que determina a configuração que será usada para o ponto de extremidade. Nesse caso, estamos usando uma configuração de ponto de extremidade chamada ExpenseServiceManager, que faz referência à configuração que temos em nosso arquivo de configuração de aplicativo.

<system.serviceModel>
   <client>
         <endpoint name="ExpenseServiceManager"
            address="https://localhost:8081/ExpenseService/Manager"
            binding="wsHttpBinding"
            contract="ExpenseContracts.IExpenseServiceManager" />
   </client>
</system.serviceModel>

		

Você pode ver que a definição de ponto de extremidade corresponde exatamente à definição declarada na configuração do host. Geralmente, o único momento em que você terá uma configuração diferente será quando o endereço do cliente e do servidor forem diferentes devido à configuração da rede ou quando o comportamento personalizado estiver sendo implementado.

Se você tiver instalado o SDK do Windows, encontrará uma ferramenta (svcutil) que automatiza a criação de uma configuração de classe de proxy e ponto de extremidade, que poderá integrar à solução. O serviço de destino deverá publicar uma descrição de seus metadados por WSDL ou WS-MetadataExchange para utilizar essa ferramenta.

Configurando canais duplex

Até agora, partimos do pressuposto de que nosso fluxo de comunicação usará um padrão de colaboração de resposta de solicitação, com as mensagens sendo enviadas por um consumidor e respondidas por um serviço. O WCF oferece suporte a alguns fluxos de mensagens alternativos, como comunicação unidirecional (do tipo "fire and forget", dispare e esqueça) e duplex bidirecional. Se estivermos lidando com um fluxo de mensagens no qual qualquer uma das partes possa iniciar uma conversação, teremos de usar um canal duplex ou bidirecional. Os canais duplex podem ser bastante eficientes para se obter sistemas com conexão mais forte, nos quais os dados possam ser enviados em qualquer direção. Isso pode lhe ser útil para fornecer retornos de chamada dos eventos.

Implementando retornos de chamada de cliente

Os retornos de chamada de cliente são implementados no WCF por meio de um conceito chamado CallbackContracts. Para um contrato que publicamos, podemos indicar um segundo contrato para definir operações que os clientes estarão publicando e que possam retornar a chamada pelo código em execução no serviço.

Para declarar um CallbackContract, especifique o tipo de interface como parte do contrato de serviço do qual retornaremos a chamada.

[ServiceContract(CallbackContract = 
typeof(IExpenseServiceClientCallback))]

		

Você também deve usar uma ligação que ofereça suporte a canais duplex, como netTcpBinding ou wsDualHttpBinding. A duplexação por TCP é possível por meio de uma conexão bidirecional estabelecida e mantida durante toda a troca de mensagens. Por HTTP, isso é possível graças a um retorno de chamada para um ouvinte cliente. Como o cliente pode não estar ciente de seu caminho de retorno ou talvez você queira ter isso explicitamente definido pela configuração, podemos usar uma configuração de ligação personalizada para declarar uma classe clientBaseAddress alternativa.

<endpoint binding="wsDualHttpBinding" 
bindingConfiguration="AlternativeClientCallback"/>
<bindings>
   <wsDualHttpBinding>
      <binding name="AlternativeClientCallback" 
clientBaseAddress="https://localhost:8082/ExpenseService/ClientCallback"/>
   </wsDualHttpBinding>
</bindings>
		

Implementando retorno de chamada no cliente

Implementar um contrato de retorno de chamada é exatamente o mesmo que implementar um contrato de serviço. Devemos fornecer uma implementação da interface que definimos.

class CallbackHandler : IExpenseServiceClientCallback
{
   public void ExpenseReportReviewed(
ExpenseReportReviewedRequest expenseReportReviewedRequest)
        {
            // We implement client logic to respond to the callback here.
        }
}
		

Para permitir que o host tenha uma instância de nossa classe CallbackHandler para retornar a chamada, devemos configurar o canal do cliente de forma que ele tenha conhecimento da natureza duplex da conexão.

Em primeiro lugar, como descrito anteriormente, usaremos uma ligação que ofereça suporte a canais duplex. Em segundo lugar, quando inicializarmos nossa conexão com o ponto de extremidade do serviço, usaremos uma versão de subclasse de ChannelFactory chamada DuplexChannelFactory, que criará para nós uma conexão duplex com o serviço.

private IExpenseServiceClient CreateChannelExpenseServiceClient()
{
   InstanceContext context = new InstanceContext(new CallbackHandler());

   DuplexChannelFactory<IExpenseServiceClient> factory =
new DuplexChannelFactory<IExpenseServiceClient>(context, 
"ExpenseServiceClient");
   IExpenseServiceClient proxy = factory.CreateChannel();

   return proxy;
}
		

A principal diferença ao usar uma classe DuplexChannelFactory é que inicializamos uma instância de nossa classe CallbackHandler e a passamos para o construtor da fábrica para inicializar um contexto a ser usado em retornos de chamada.

Implementando retorno de chamada no host

Do ponto de vista do host, podemos obter uma referência para retornar a chamada de nosso cliente pelo respectivo canal definido em nosso contrato IExpenseServiceClient.

[ServiceContract(CallbackContract = 
typeof(IExpenseServiceClientCallback))]
public interface IExpenseServiceClient : IExpenseService

		

O atributo CallbackContract declara a interface que define o contrato dos retornos de chamada feitos pelo host.

Para fazer o retorno de chamada, obtemos uma referência para o respectivo contrato chamando OperationContext.Current.GetCallbackChannel, como mostrado aqui.

IExpenseServiceClientCallback callback =
                   OperationContext.Current.GetCallbackChannel
<IExpenseServiceClientCallback>();
callback.ExpenseReportReviewed(new 
ExpenseReportReviewedRequest(e.Report));

		

Depois que tivermos uma referência para nosso canal de retorno de chamada, poderemos chamá-lo normalmente.

Conclusão

O Windows Workflow Foundation oferece uma estrutura geral de definição de fluxos de trabalho e um mecanismo de tempo de execução robusto que permite hospedar fluxos de trabalho em execução e interagir com eles.

O Windows Communication Foundation fornece uma estrutura geral de criação de sistemas conectados e oferece aos desenvolvedores uma API consistente e um conjunto amplo de recursos para definir o funcionamento das comunicações.

Você pode usar essas duas estruturas juntas para fornecer uma plataforma de aplicativo flexível e abrangente para a criação e a implantação de processos empresariais distribuídos em seu ambiente. O WF permite modelar e encapsular a lógica comercial e o processo empresarial, enquanto o WCF fornece a infra-estrutura de mensagens que oferece os meios de distribuir os sistemas.

Veja aqui algumas instruções a serem lembradas ao criar serviços:

  • Você deve fornecer um serviço de persistência aos fluxos de trabalho de execução longa.

  • Uma operação de serviço pode interagir por um fluxo de trabalho em execução, por meio da geração de eventos. Os fluxos de trabalho devem ser criados para gerar eventos quando exigirem atenção e para responder aos eventos quando houver interação externa (humana ou de um serviço externo).

  • Os fluxos de trabalho executarão assincronamente com a chamada de serviço; portanto, seja cuidadoso com o design se estiver pensando em retornar dados do serviço e considere o estado que os dados terão nesse momento. Para usar uma abordagem síncrona, use a classe ManualWorkflowSchedulerService para permitir o agendamento manual da execução do fluxo de trabalho.

Para obter mais informações

  1. Sessions, Instancing, and Concurrency

  2. WCF Bindings

  3. Artigo sobre como usar contratos de dados

  4. Using Message Contracts

  5. Site da comunidade do .NET Framework 3.0

  6. Fóruns do Windows Workflow Foundation

Agradecimentos

Agradecemos os colaboradores e revisores a seguir pelos seus esforços:

  • Christian Weyer, thinktecture

  • Paul Andrew, Microsoft Corporation

  • Khalid Aggag, Microsoft Corporation

  • Patrice Manac'h, Microsoft Corporation

Sobre o autor

Jeremy Boyd é consultor técnico sênior da Intergen, um provedor de soluções com sede na Nova Zelândia, Microsoft Gold Certified Partner, bem como diretor regional do MSDN na comunidade desse país. Nos últimos 12 meses, Jeremy trabalhou ativamente com clientes para ajudar a implementar soluções baseadas no WF e no WCF, bem como para ajudar outros desenvolvedores a aprender os benefícios dessas tecnologias por seu Weblog .

 

.