Hospedando um serviço WCF em uma Worker Role do Windows Azure

Olá pessoal, hoje vou voltar a falar sobre WCF com vocês, mas com a novidade que eu quero mostrar é a possibilidade de hospedar os serviços no Windows Azure, que é a plataforma de Cloud Computing da Microsoft.

Podemos contar no Windows Azure com os serviços de computação, com possibilidade de utilizarmos estes serviços para web (Web Role) e processamento background (Worker Role). Em uma web role, o WCF pode ser hospedado de maneira similar ao IIS, utilizando arquivos .svc. Já em uma worker role, que pode ser entendida para o equivalente à um Windows Service, precisamos gerenciar o ciclo de vida do WCF nela. Este post fala exatamente sobre WCF com Worker Roles.

Vou iniciar a solução com dois projetos existentes: uma ClassLibrary com a definição do contrato para um serviço de “Echo” (projeto WcfDefinition) e um cliente para o serviço (WcfClientApplication). O contrato do serviço está abaixo:

 using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;

namespace WcfDefinition
{
    [ServiceContract]
    public interface IEchoService
    {
        [OperationContract]
        string Echo(string text);
    }
}

A aplicação cliente é uma aplicação WPF que possui uma caixa de texto, um botão e uma lista para mostrar o retorno de cada chamada ao serviço, um print está abaixo:

 

image

O seu arquivo de configuração:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<bindings>
<netTcpBinding>
<binding>
<security mode="None"/>
</binding>
</netTcpBinding>
</bindings>
<client>
<endpoint address="net.tcp://localhost:8080/EchoService"
                binding="netTcpBinding"
                contract="WcfDefinition.IEchoService"
                name="WcfDefinition.IEchoService" />
</client>
</system.serviceModel>
</configuration>

 

E o código fonte:

 public partial class MainWindow : Window
{
    private ObservableCollection<string> _results = new ObservableCollection<string>();

    public MainWindow()
    {
        InitializeComponent();
    }

    private void SendButton_Click(object sender, RoutedEventArgs e)
    {
        ChannelFactory<WcfDefinition.IEchoService> channel = 
            new ChannelFactory<WcfDefinition.IEchoService>("WcfDefinition.IEchoService");
        WcfDefinition.IEchoService proxy = channel.CreateChannel();
        _results.Add(proxy.Echo(ContentTextBox.Text));
    }

    private void Window_Loaded(object sender, RoutedEventArgs e)
    {
        ResultListBox.ItemsSource = _results;
    }
}

Até aí tudo normal, não apresentei nenhuma novidade para o WCF. Agora vou incluir o Worker Role para pode hospedar o nosso serviço, é só adicionar um projeto do tipo Windows Azure Cloud Service:

image

E depois adicionar uma Worker Role:

image

Na Worker Role preciso implementar o serviço de Echo:

 using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.ServiceModel;
using WcfDefinition;

namespace WcfServiceWorkerRole
{
    [ServiceBehavior(AddressFilterMode = AddressFilterMode.Any)]
    class EchoService : IEchoService
    {
        public string Echo(string text)
        {
            Trace.WriteLine(string.Format("[EchoService.Echo]: {0}", text), "Information");
            return text;
        }
    }
}

Notem o uso do AddressFilterMode, isso é feito porque o serviço na verdade não é acessado diretamente pelos clientes. As chamadas dos clientes para o serviço passam por um roteamente interno no Windows Azure, se nada fosse utilizado o serviço iria simplesmente recusar a mensagem e retornar um erro.

O arquivo de configuração, exibido parcialmente, da Worker Role está abaixo, notem que ele não traz o endereço do serviço.

   <system.serviceModel>
    <bindings>
      <netTcpBinding>
        <binding>
          <security mode="None"/>
        </binding>
      </netTcpBinding>
    </bindings>
    <services>
      <service name="WcfServiceWorkerRole.EchoService">
        <endpoint address="EchoService" 
            binding="netTcpBinding"
            contract="WcfDefinition.IEchoService" />
      </service>
    </services>
  </system.serviceModel>

Para que o serviço possa ser exposto em algum endereço, primeiro precisamos avisar o Windows Azure que isso irá acontecer e isso é feito através do arquivo ServiceDefinition.csdef, em formato xml, onde configuro um endereço de endpoint:

 <?xml version="1.0" encoding="utf-8"?>
<ServiceDefinition name="WcfWorkerRole" xmlns="https://schemas.microsoft.com/ServiceHosting/2008/10/ServiceDefinition">
  <WorkerRole name="WcfServiceWorkerRole">
    <ConfigurationSettings>
      <Setting name="DiagnosticsConnectionString" />
    </ConfigurationSettings>
    <Endpoints>
      <InputEndpoint name="EchoService" protocol="tcp" port="8080" />
    </Endpoints>
  </WorkerRole>
</ServiceDefinition>

Isso também pode ser feito pela tela de configuração da Worker Role:

image

Depois no método OnStart da Worker Role, configuro o ServiceHost:

 public override bool OnStart()
{
    // Set the maximum number of concurrent connections 
    ServicePointManager.DefaultConnectionLimit = 12;

    DiagnosticMonitor.Start("DiagnosticsConnectionString");

    // For information on handling configuration changes
    // see the MSDN topic at https://go.microsoft.com/fwlink/?LinkId=166357.
    RoleEnvironment.Changing += RoleEnvironmentChanging;

    CreateServiceHost();

    return base.OnStart();
}

private void CreateServiceHost()
{
    RoleInstanceEndpoint externalEndpoint = 
        RoleEnvironment.CurrentRoleInstance.InstanceEndpoints["EchoService"];
    string baseAddress = string.Format("net.tcp://{0}/", 
        externalEndpoint.IPEndpoint);

    Trace.WriteLine(string.Format("[WorkerRole.CreateServiceHost]: Base Address {0}", baseAddress), "Information");

    ServiceHost = new ServiceHost(typeof(EchoService), new Uri(baseAddress));
}

Primeiro é necessário buscar a configuração do endereço para o Windows Azure (RoleInstanceEndpoint externalEndpoint = RoleEnvironment.CurrentRoleInstance.InstanceEndpoints["EchoService"];), em seguida é necessário configurar o serviço para utilizá-lo, o que é feito na linha seguinte (string baseAddress = string.Format("net.tcp://{0}/", externalEndpoint.IPEndpoint);).

O último passo é colocar o serviço no ar, através de um ServiceHost.Open() , feito no método Run da Worker Role:

 public override void Run()
{
    // This is a sample worker implementation. Replace with your logic.
    Trace.WriteLine("WcfServiceWorkerRole entry point called", "Information");

    ServiceHost.Open();

    while (true)
    {
        Thread.Sleep(10000);
        Trace.WriteLine("Working", "Information");
    }
}

O código fonte pode ser encontrado aqui.