Il presente articolo è stato tradotto automaticamente.

Architettura di WCF

Rilevamento del bus di servizio AppFabric

Juval Lowy

Scaricare il codice di esempio

Nel mio articolo di gennaio 2010 “ ricerca un nuovo WCF con rilevamento ” ( msdn.microsoft.com/magazine/ee335779 ), presentato la funzionalità di rilevamento importanti di Windows Communication Foundation (WCF) 4. Rilevamento di WCF è fondamentalmente una tecnica basata su intranet, non esiste un modo per trasmettere informazioni attraverso Internet.

Ancora i vantaggi di indirizzi dinamici e decoupling client e servizi sull'asse indirizzo verrebbe applicata anche ai servizi che si basano sul servizio bus per ricevere chiamate client.

Fortunatamente, è possibile utilizzare associazione di inoltro eventi multicast a UDP (User Datagram Protocol) di sostituire le richieste e per individuazione e annunci. Consente di combinare i vantaggi di facile distribuzione servizi individuabili con connettività unhindered del servizio bus. In questo articolo vengono esaminati un piccolo framework scritto per supportare il rilevamento tramite il servizio bus, portandola paragonabile con il supporto incorporato per il rilevamento di WCF, con il set di classi di supporto. Funge inoltre da un esempio di implementazione di un proprio meccanismo di individuazione.

AppFabric Service Bus sfondo

Se non ha familiarità con il bus di servizi AppFabric, è possibile leggere gli articoli precedenti:

Architettura della soluzione

Per individuazione incorporato di WCF, esistono contratti standard per lo scambio di individuazione. Purtroppo, questi contratti definiti come interni. La prima consiste in un meccanismo di rilevamento personalizzato per definire contratti utilizzati per la richiesta di individuazione e callback. Il contratto IServiceBusDiscovery definito come segue:

[ServiceContract]
public interface IServiceBusDiscovery
{
  [OperationContract(IsOneWay = true)]
  void OnDiscoveryRequest(string contractName,string contractNamespace,
    Uri[] scopesToMatch,Uri replayAddress);
}

IServiceBusDiscovery singola operazione supportata dall'endpoint di individuazione. OnDiscoveryRequest consente al client scoprire gli endpoint del servizio che supportano un contratto specifico, come con WCF regolari. Il client può inoltre passare un insieme facoltativo di ambiti corrispondenti.

Servizi devono supportare l'endpoint individuazione tramite l'associazione di inoltro di eventi. Un client genera le richieste di servizi che supportano l'endpoint di individuazione, che richiedono la chiamata di servizi al client di fornito un indirizzo di risposta.

Chiamata di servizi al client utilizzando IServiceBusDiscoveryCallback, definito come:

[ServiceContract]
public interface IServiceBusDiscoveryCallback
{
  [OperationContract(IsOneWay = true)]
  void DiscoveryResponse(Uri address,string contractName,
    string contractNamespace, Uri[] scopes);
}

Il client fornisce un endpoint il cui indirizzo è il parametro replayAddress OnDiscoveryRequest IServiceBusDiscoveryCallback di supporto. L'associazione utilizzata deve essere approssimativo unicast quanto più possibile l'associazione di inoltro unidirezionale. Figura 1 è illustrata la sequenza di individuazione.

image: Discovery over the Service Bus

Figura 1 di rilevamento su Bus Service

Di Figura 1 il primo passo è un client generando un evento di richiesta di individuazione all'endpoint del rilevamento IServiceBusDiscovery di supporto. Grazie all'associazione degli eventi, l'evento viene ricevuto da tutti i servizi individuati. Se un servizio supporta il contratto richiesto, chiama nuovamente al client tramite il bus di servizi (passaggio 2 di Figura 1). Una volta che il client riceva l'endpoint del servizio (o endpoint) indirizzi, procede per chiamare il servizio come con una chiamata al servizio regolari bus (passaggio 3 in di Figura 1).

Host rilevabile

Ovviamente molto lavoro è coinvolto nel supporto di un meccanismo di rilevamento, soprattutto per il servizio. Sono stato in grado di incapsulare che con il mio DiscoverableServiceHost definito come:

public class DiscoverableServiceHost : ServiceHost,...
{ 
  public const string DiscoveryPath = "DiscoveryRequests";

  protected string Namespace {get;}

  public Uri DiscoveryAddress {get;set;}

      public NetEventRelayBinding DiscoveryRequestBinding {get;set;}
      
  public NetOnewayRelayBinding DiscoveryResponseBinding {get;set;}

  public DiscoverableServiceHost(object singletonInstance,
    params Uri[] baseAddresses);
  public DiscoverableServiceHost(Type serviceType,
    params Uri[] baseAddresses);
}

Oltre all'individuazione, DiscoverableServiceHost pubblica sempre gli endpoint del servizio nel Registro di sistema servizio bus. Per attivare il rilevamento, come avviene con rilevamento WCF standard, è necessario aggiungere un comportamento di individuazione e un endpoint di individuazione WCF. È intenzionale, in modo da evitare di aggiungere un'altra opzione di controllo sia in uno posizionare una singola configurazione coerenza in cui è possibile attivare o disattivare tutte le modalità di individuazione.

È possibile utilizzare DiscoverableServiceHost come qualsiasi altro servizio basarsi sul bus di servizio:

Uri baseAddress = 
  new Uri("sb://MyServiceNamespace.servicebus.windows.
net/MyService/");

ServiceHost host = new DiscoverableServiceHost(typeof(MyService),baseAddress);
           
// Address is dynamic
host.AddServiceEndpoint(typeof(IMyContract),new NetTcpRelayBinding(),
  Guid.NewGuid().ToString());
 
// A host extension method to pass creds to behavior
host.SetServiceBusCredentials(...);

host.Open();

Si noti che quando si utilizza il rilevamento, l'indirizzo del servizio può essere completamente dinamico.

Figura 2 fornisce l'implementazione parziale di elementi pertinenti del DiscoverableServiceHost.

Figura 2 implementazione DiscoverableServiceHost (parziale)

public class DiscoverableServiceHost : ServiceHost,...
{  
  Uri m_DiscoveryAddress;
  ServiceHost m_DiscoveryHost;
    
  // Extracts the service namespace out of the endpoints or base addresses
  protected string Namespace
  {
    get
    {...}
  }

  bool IsDiscoverable
  {
    get
    {
      if(Description.Behaviors.Find<ServiceDiscoveryBehavior>() != null)
      {
        return Description.Endpoints.Any(endpoint => 
          endpoint is DiscoveryEndpoint);
      }
      return false;
    }
  }
   
  public Uri DiscoveryAddress
  {
    get
    {
      if(m_DiscoveryAddress == null)
      {
        m_DiscoveryAddress =  
          ServiceBusEnvironment.CreateServiceUri("sb",Namespace,DiscoveryPath);
      }
      return m_DiscoveryAddress;
    }
    set
    {
      m_DiscoveryAddress = value;
    }
  }

  public DiscoverableServiceHost(Type serviceType,params Uri[] 
    baseAddresses) : base(serviceType,baseAddresses)
  {}

  void EnableDiscovery()
  {
    // Launch the service to monitor discovery requests
    DiscoveryRequestService discoveryService = 
      new DiscoveryRequestService(Description.Endpoints.ToArray());

    discoveryService.DiscoveryResponseBinding = DiscoveryResponseBinding;

    m_DiscoveryHost = new ServiceHost(discoveryService);

    m_DiscoveryHost.AddServiceEndpoint(typeof(IServiceBusDiscovery),
      DiscoveryRequestBinding, DiscoveryAddress.AbsoluteUri);

     m_DiscoveryHost.Open();
  }

  protected override void OnOpening()
  {
    if(IsDiscoverable)
    {
      EnableDiscovery();
    }
     
    base.OnOpening();
  }
  protected override void OnClosed()
  {
    if(m_DiscoveryHost != null)
    {
      m_DiscoveryHost.Close();
    }

    base.OnClosed();
  }

  [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single,...]
  class DiscoveryRequestService : IServiceBusDiscovery
  {
    public DiscoveryRequestService(ServiceEndpoint[] endpoints);

    public NetOnewayRelayBinding DiscoveryResponseBinding
    {get;set;}
  }   
}

La proprietà helper IsDiscoverable di DiscoverableServiceHost restituisce true solo se il servizio ha un problema di rilevamento e individuazione almeno un endpoint. DiscoverableServiceHost esegue l'override del metodo OnOpening di ServiceHost. Se il servizio deve essere individuati, OnOpening chiama il metodo EnableDiscovery.

EnableDiscovery è il cuore di DiscoverableServiceHost. Crea un host interno per una classe singleton privata denominata DiscoveryRequestService (vedere di Figura 3).

Figura 3 della Classe DiscoveryRequestService (parziale)

public class DiscoverableServiceHost : ServiceHost
{
  [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single,
    UseSynchronizationContext = false)]
  class DiscoveryRequestService : IServiceBusDiscovery
  {
    readonly ServiceEndpoint[] Endpoints;

    public NetOnewayRelayBinding DiscoveryResponseBinding
    {get;set;}

    public DiscoveryRequestService(ServiceEndpoint[] endpoints)
    {
      Endpoints = endpoints;
    }

    void IServiceBusDiscovery.OnDiscoveryRequest(string contractName,
      string contractNamespace, Uri[] scopesToMatch, Uri responseAddress)
    {
      ChannelFactory<IServiceBusDiscoveryCallback> factory = 
        new ChannelFactory<IServiceBusDiscoveryCallback>(
        DiscoveryResponseBinding, new EndpointAddress(responseAddress));

      IServiceBusDiscoveryCallback callback = factory.CreateChannel();

      foreach(ServiceEndpoint endpoint in Endpoints)
      {
        if(endpoint.Contract.Name == contractName && 
          endpoint.Contract.Namespace == contractNamespace)
        {
          Uri[] scopes = DiscoveryHelper.LookupScopes(endpoint);

          if(scopesToMatch != null)
          {
            bool scopesMatched = true;
            foreach(Uri scope in scopesToMatch)
            {
              if(scopes.Any(uri => uri.AbsoluteUri == scope.AbsoluteUri) 
                == false)
              {
                scopesMatched = false;
                break;
              }
            }
            if(scopesMatched == false)
            {
              continue;
            }
          }
          callback.DiscoveryResponse(endpoint.Address.Uri,contractName,
            contractNamespace,scopes);
        }
      }
      (callback as ICommunicationObject).Close();
    }
  }   
}

public static class DiscoveryHelper
{
  static Uri[] LookupScopes(ServiceEndpoint endpoint)
  {
    Uri[] scopes = new Uri[]{};
    EndpointDiscoveryBehavior behavior = 
      endpoint.Behaviors.Find<EndpointDiscoveryBehavior>();
    if(behavior != null)
    {
      if(behavior.Scopes.Count > 0)
      {
        scopes = behavior.Scopes.ToArray();
      }
    }
    return scopes;
  }
  // More members
}

Il costruttore di DiscoveryRequestService accetta gli endpoint del servizio per il quale è necessario monitorare le richieste di rilevamento (questi sono fondamentalmente i punti finali di DiscoverableServiceHost).

EnableDiscovery aggiunge quindi l'host di un endpoint che implementa IServiceBusDiscovery, poiché DiscoveryRequestService effettivamente risponde alle richieste di individuazione dai client. L'indirizzo dell'endpoint di individuazione predefinito l'URI “ DiscoveryRequests ” sotto lo spazio dei nomi del servizio. È tuttavia possibile modificare che prima di aprire DiscoverableServiceHost eventuali altri URI utilizzando la proprietà DiscoveryAddress. Chiusura DiscoverableServiceHost anche la chiusura dell'host per l'endpoint di individuazione.

Figura 3 Elenca l'implementazione di DiscoveryRequestService.

OnDiscoveryRequest crea un proxy per richiamare l'individuazione client. L'associazione utilizzata è una semplice NetOnewayRelayBinding, ma è possibile controllare che impostando la proprietà DiscoveryResponseBinding. Si noti che DiscoverableServiceHost ha una proprietà corrispondente per tale scopo. OnDiscoveryRequest scorre quindi l'insieme di endpoint fornito al costruttore. Per ogni endpoint, viene verificato che il contratto corrispondente contratto richiesto nella richiesta di individuazione. Se il contratto corrispondente, OnDiscoveryRequest Cerca gli ambiti associati all'endpoint e verifica che tali ambiti corrispondano ambiti facoltativi nella richiesta di individuazione. Infine, OnDiscoveryRequest richiama il client con l'indirizzo, contratto e l'ambito dell'endpoint.

Individuazione client

Per il client, scritto la classe helper ServiceBusDiscoveryClient, definita come segue:

public class ServiceBusDiscoveryClient : ClientBase<IServiceBusDiscovery>,...
{         
  protected Uri ResponseAddress
  {get;}

  public ServiceBusDiscoveryClient(string serviceNamespace,string secret);

  public ServiceBusDiscoveryClient(string endpointName);
  public ServiceBusDiscoveryClient(NetOnewayRelayBinding binding,
    EndpointAddress address);

   public FindResponse Find(FindCriteria criteria);
}

ServiceBusDiscoveryClient è modellata DiscoveryClient WCF e ha utilizzato allo stesso modo, come illustrato in di Figura 4.

Figura 4 utilizzo ServiceBusDiscoveryClient

string serviceNamespace = "...";
string secret = "...";

ServiceBusDiscoveryClient discoveryClient = 
  new ServiceBusDiscoveryClient(serviceNamespace,secret);
         
FindCriteria criteria = new FindCriteria(typeof(IMyContract));
FindResponse discovered = discoveryClient.Find(criteria);
discoveryClient.Close();
        
EndpointAddress address = discovered.Endpoints[0].Address;
Binding binding = new NetTcpRelayBinding();
ChannelFactory<IMyContract> factory = 
  new ChannelFactory<IMyContract> (binding,address);
// A channel factory extension method to pass creds to behavior
factory.SetServiceBusCredentials(secret);

IMyContract proxy = factory.CreateChannel();
proxy.MyMethod();
(proxy as ICommunicationObject).Close();

ServiceBusDiscoveryClient è un proxy per l'endpoint di eventi individuazione IServiceBusDiscovery. Client utilizzano per generare la richiesta di individuazione in servizi individuabili. L'indirizzo dell'endpoint di individuazione predefinito “ DiscoveryRequests ”, ma è possibile specificare un diverso indirizzo utilizzando uno dei costruttori che accettano un nome di endpoint o l'indirizzo dell'endpoint. Un'istanza semplice di NetOnewayRelayBinding utilizzerà per l'endpoint di individuazione, ma è possibile specificare un'associazione diversa utilizzando uno dei costruttori che accettano un nome di endpoint o un'istanza di binding. ServiceBusDiscoveryClient supporta la cardinalità e i timeout di individuazione, come DiscoveryClient.

Figura 5 viene illustrata l'implementazione parziale di ServiceBusDiscoveryClient.

Figura 5 implementazione ServiceBusDiscoveryClient (parziale)

public class ServiceBusDiscoveryClient : ClientBase<IServiceBusDiscovery> 
{         
   protected Uri ResponseAddress
   {get;private set;}

   public ServiceBusDiscoveryClient(string endpointName) : base(endpointName)
   {
      string serviceNamespace =  
        ServiceBusHelper.ExtractNamespace(Endpoint.Address.Uri);
      ResponseAddress = ServiceBusEnvironment.CreateServiceUri(
        "sb",serviceNamespace,"DiscoveryResponses/"+Guid.NewGuid());
    }

   public FindResponse Find(FindCriteria criteria)
   {
      string contractName = criteria.ContractTypeNames[0].Name;
      string contractNamespace = criteria.ContractTypeNames[0].Namespace;

      FindResponse response = DiscoveryHelper.CreateFindResponse();

      ManualResetEvent handle = new ManualResetEvent(false);

     Action<Uri,Uri[]> addEndpoint = (address,scopes)=>
     {
       EndpointDiscoveryMetadata metadata = new EndpointDiscoveryMetadata();
       metadata.Address = new EndpointAddress(address);
         if(scopes != null)
         {
           foreach(Uri scope in scopes)
             {
               metadata.Scopes.Add(scope);
             }
         }
         response.Endpoints.Add(metadata);
                                         
         if(response.Endpoints.Count >= criteria.MaxResults)
         {
           handle.Set();
         }
     };

     DiscoveryResponseCallback callback = 
       new DiscoveryResponseCallback(addEndpoint);

     ServiceHost host = new ServiceHost(callback);
        

     host.AddServiceEndpoint(typeof(IServiceBusDiscoveryCallback),
       Endpoint.Binding,ResponseAddress.AbsoluteUri);

     host.Open();

     try
     {         
       DiscoveryRequest(criteria.ContractTypeNames[0].Name,
         criteria.ContractTypeNames[0].Namespace,
         criteria.Scopes.ToArray(),ResponseAddress);

       handle.WaitOne(criteria.Duration);
     }
     catch
     {}
     finally
     {
       host.Abort();
     }
     return response;
   }
   void DiscoveryRequest(string contractName,string contractNamespace,
     Uri[] scopesToMatch,Uri replayAddress)
   {
     Channel.OnDiscoveryRequest(contractName,contractNamespace,
       scopesToMatch, replayAddress);
   }

   [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single,
     UseSynchronizationContext = false)]
   class DiscoveryResponseCallback : IServiceBusDiscoveryCallback
   {
     readonly Action<Uri,Uri[]> Action;

     public DiscoveryResponseCallback(Action<Uri,Uri[]> action)
     {
       Action = action;
     }
     public void DiscoveryResponse(Uri address,string contractName,
       string contractNamespace,Uri[] scopes)
     {
       Action(address,scopes);
     }
   }
}

public static class DiscoveryHelper
{
  internal static FindResponse CreateFindResponse()
  {
    Type type = typeof(FindResponse);

    ConstructorInfo constructor =  

      type.GetConstructors(BindingFlags.Instance|BindingFlags.NonPublic)[0];

     return constructor.Invoke(null) as FindResponse;
  }  
  // More members
}

Il metodo Find deve disporre di un modo per ricevere richiamate dai servizi individuati. A tal fine, ogni volta che viene chiamato, trova apre e chiude un host per una classe singleton di sincronizzazione interna denominata DiscoveryResponseCallback. Ricerca aggiunge all'host un endpoint IServiceBusDiscoveryCallback di supporto. Il costruttore di DiscoveryResponseCallback accetta un delegato del tipo azione < Uri Uri [] >. Ogni volta che un servizio risponde nuovamente, l'implementazione di DiscoveryResponse richiama il delegato, fornendo l'indirizzo individuato e l'ambito. Il metodo Find utilizza un'espressione lambda per aggregare le risposte in un'istanza di FindResponse. Purtroppo non è alcun costruttore pubblico per FindResponse, in modo che la ricerca viene utilizzato il metodo CreateFindResponse di DiscoveryHelper, a sua volta utilizza la reflection per crearne un'istanza. Ricerca crea inoltre un handle di evento waitable. I segnali di espressione lambda che gestiscono quando viene soddisfatta la cardinalità. Dopo aver chiamato DiscoveryRequest, trova attenderà per l'handle di essere segnalato oppure per la durata di individuazione scadere e quindi interrompe l'host di interrompere l'elaborazione delle risposte di rilevamento in corso.

Ulteriori classi di supporto sul lato client

Anche se ho scritto ServiceBusDiscoveryClient funzionalmente identici a DiscoveryClient, sarebbe beneficiano offerta dal mio ServiceBusDiscoveryHelper un'esperienza di individuazione semplificata:

public static class ServiceBusDiscoveryHelper
{
  public static EndpointAddress DiscoverAddress<T>(
    string serviceNamespace,string secret,Uri scope = null);

  public static EndpointAddress[] DiscoverAddresses<T>(
    string serviceNamespace,string secret,Uri scope = null);
   
  public static Binding DiscoverBinding<T>(
    string serviceNamespace,string secret,Uri scope = null);
}

DiscoverAddress <T>Individua un servizio con una cardinalità di una, DiscoverAddresses <T>Individua tutti gli endpoint del servizio disponibile (cardinalità di tutti) e DiscoverBinding <T>utilizza l'endpoint dei metadati del servizio per rilevare l'associazione dell'endpoint. Allo stesso modo, definita la classe ServiceBusDiscoveryFactory:

public static class ServiceBusDiscoveryFactory
{
  public static T CreateChannel<T>(string serviceNamespace,string secret,
    Uri scope = null) where T : class;
   
  public static T[] CreateChannels<T>(string serviceNamespace,string secret,
    Uri scope = null) where T : class;
}

CreateChannel <T>presuppone la cardinalità di una, e utilizza l'endpoint dei metadati per ottenere indirizzi e l'associazione utilizzata per creare il proxy del servizio. CreateChannels <T>Crea il proxy per tutti i servizi individuati, utilizzando tutti gli endpoint rilevati metadati.

Annunci

Per supportare gli annunci, è possibile utilizzare nuovamente l'associazione di inoltro eventi sostituire UDP multicast. Innanzitutto, ho definito contratto di annuncio IServiceBusAnnouncements:

[ServiceContract]
public interface IServiceBusAnnouncements
{
  [OperationContract(IsOneWay = true)]
  void OnHello(Uri address, string contractName,
    string contractNamespace, Uri[] scopes);

  [OperationContract(IsOneWay = true)]
  void OnBye(Uri address, string contractName, 
    string contractNamespace, Uri[] scopes);
}

Come illustrato in di Figura 6, questa volta spetta ai client per esporre un endpoint di associazione di eventi e monitorare gli annunci.

image: Availability Announcements over the Service Bus

Figura 6 di annunci di disponibilità tramite il servizio bus

I servizi verranno annunciare la disponibilità (attraverso l'associazione unidirezionale relay) fornendo l'indirizzo (passaggio 1 di Figura 6) e il client continuerà a richiamarli (passaggio 2 di Figura 6).

Lato servizio annunci

Mio DiscoveryRequestService supporta gli annunci:

public class DiscoverableServiceHost : ServiceHost,...
{
   public const string AnnouncementsPath = "AvailabilityAnnouncements";   

   public Uri AnnouncementsAddress
   {get;set;}
      
   public NetOnewayRelayBinding AnnouncementsBinding
   {get;set;}

  // More members 
}

Tuttavia, paragonabile con annunci di WCF incorporati, per impostazione predefinita non annuncia la disponibilità. Per attivare gli annunci, è necessario configurare un endpoint annuncio con il comportamento di rilevamento. Nella maggior parte dei casi, questo è tutto da eseguire. DiscoveryRequestService genera gli eventi di disponibilità in URI “ AvailabilityAnnouncements ” sotto lo spazio dei nomi del servizio. È possibile modificare tale impostazione predefinita impostando la proprietà AnnouncementsAddress prima di aprire l'host. Gli eventi che verranno generati per impostazione predefinita con un'associazione semplice inoltro unidirezionale, ma è possibile fornire un'alternativa utilizzando la proprietà AnnouncementsBinding prima di aprire l'host. DiscoveryRequestService genera gli eventi di disponibilità in modo asincrono per evitare il blocco delle operazioni durante l'apertura e la chiusura dell'host. Figura 7 vengono mostrati gli elementi di supporto di annuncio di DiscoveryRequestService.

Figura 7 Supporting annunci con DiscoveryRequestService

public class DiscoverableServiceHost : ServiceHost,...
{
   Uri m_AnnouncementsAddress;

   bool IsAnnouncing
   {
      get
      {
         ServiceDiscoveryBehavior behavior = 
           Description.Behaviors.Find<ServiceDiscoveryBehavior>();
         if(behavior != null)
         {
           return behavior.AnnouncementEndpoints.Any();
         }
         return false;
      }
   }

   public Uri AnnouncementsAddress
   {
      get
      {
        if(m_AnnouncementsAddress == null)
        {
          m_AnnouncementsAddress = ServiceBusEnvironment.
CreateServiceUri("sb",Namespace,AnnouncementsPath);
        }
        return m_AnnouncementsAddress;
     }
     set
     {
       m_AnnouncementsAddress = value;
     }
  }

  IServiceBusAnnouncements CreateAvailabilityAnnouncementsClient()
  {    
    ChannelFactory<IServiceBusAnnouncements> factory = 
      new ChannelFactory<IServiceBusAnnouncements>(
      AnnouncementsBinding,new EndpointAddress(AnnouncementsAddress));

    return factory.CreateChannel();
  }

   protected override void OnOpened()
   {
      base.OnOpened();

      if(IsAnnouncing)
      {
        IServiceBusAnnouncements proxy =  
          CreateAvailabilityAnnouncementsClient();
        PublishAvailabilityEvent(proxy.OnHello);
      }
   }

   protected override void OnClosed()
   {
     if(IsAnnouncing)
     {
       IServiceBusAnnouncements proxy = 
         CreateAvailabilityAnnouncementsClient();
       PublishAvailabilityEvent(proxy.OnBye);
     }
     ...
}

  void PublishAvailabilityEvent(Action<Uri,string,string,Uri[]> notification)
  {
    foreach(ServiceEndpoint endpoint in Description.Endpoints)
    {
      if(endpoint is DiscoveryEndpoint || endpoint is 
        ServiceMetadataEndpoint)
      {
        continue;
      }
      Uri[] scopes = LookupScopes(endpoint);

      WaitCallback fire = delegate
      {
        try
        {
          notification(endpoint.Address.Uri, endpoint.Contract.Name,
            endpoint.Contract.Namespace, scopes);
          (notification.Target as ICommunicationObject).Close();

        }
        catch
        {}
      };
      ThreadPool.QueueUserWorkItem(fire); 
    }
  }
}

Il metodo di supporto CreateAvailabilityAnnouncementsClient utilizza una channel factory per creare un proxy per l'endpoint di eventi IServiceBusAnnouncements annunci. Dopo l'apertura e prima della chiusura DiscoveryRequestService genera le notifiche. DiscoveryRequestService esegue l'override di metodi OnOpened sia OnClosed di ServiceHost. Se l'host è configurato per annunciare, OnOpened e OnClosed chiamare CreateAvailabilityAnnouncementsClient per creare un proxy e passarlo al metodo PublishAvailabilityEvent per generare l'evento in modo asincrono. Poiché l'azione di generazione dell'evento è identico per gli annunci di hello e di bye e l'unica differenza è il metodo di IServiceBusAnnouncements chiamare, PublishAvailabilityEvent accetta un delegato per il metodo di destinazione. Per ciascun endpoint di DiscoveryRequestService PublishAvailabilityEvent Cerca gli ambiti associati a tale endpoint e code di annuncio al pool di thread di Microsoft .NET Framework utilizza un metodo anonimo WaitCallback. Il metodo anonimo richiama il delegato fornito, quindi chiude il proxy destinazione sottostante.

Ricezione di annunci

Potrebbe avere successivamente AnnouncementService fornita da WCF come descritto nel mio articolo di gennaio, ma è presente un lungo elenco di elementi che è stato migliorato con il mio AnnouncementSink <T> e non vedo un caso in cui si preferisce utilizzare AnnouncementService in favore di AnnouncementSink <T>. Desideravo inoltre sfruttare e riutilizzare il comportamento di AnnouncementSink <T>e la relativa classe base.

Pertanto, per il client, ho scritto ServiceBusAnnouncementSink <T>, definito come:

[ServiceBehavior(UseSynchronizationContext = false,
  InstanceContextMode = InstanceContextMode.Single)]
public class ServiceBusAnnouncementSink<T> : AnnouncementSink<T>,
   IServiceBusAnnouncements, where T : class
{
  public ServiceBusAnnouncementSink(string serviceNamespace,string secret);

  public ServiceBusAnnouncementSink(string serviceNamespace,string owner,
    string secret);
  public Uri AnnouncementsAddress get;set;}

  public NetEventRelayBinding AnnouncementsBinding {get;set;}
}

I costruttori di ServiceBusAnnouncementSink <T>richiede lo spazio dei nomi del servizio.

ServiceBusAnnouncementSink <T>supporta IServiceBusAnnouncements self-hosted singleton. ServiceBusAnnouncementSink <T>pubblica anche se stesso nel Registro di sistema servizio bus. ServiceBusAnnouncementSink <T>sottoscrive per impostazione predefinita gli annunci di disponibilità su “ AvailabilityAnnouncements ” URI sotto lo spazio dei nomi del servizio. È possibile modificare che (prima di aprirlo) impostando la proprietà AnnouncementsAddress. ServiceBusAnnouncementSink <T>utilizza (impostazione predefinita) un normale NetEventRelayBinding per ricevere le notifiche, ma è possibile modificare che impostando il AnnouncementsBinding prima di aprire ServiceBusAnnouncementSink <T>. I client di ServiceBusAnnouncementSink <T>possono sottoscrivere i delegati di AnnouncementSink <T>Per ricevere gli annunci o semplicemente possano accedere all'indirizzo nel contenitore di indirizzo di base. Per un esempio, vedere di Figura 8.

Figura 8 ricezione annunci

class MyClient 
{
   AddressesContainer<IMyContract> m_Addresses;

   public MyClient()
   {
      string serviceNamespace = "...";
      string secret = "...";

      m_Addresses = new ServiceBusAnnouncementSink<IMyContract>(
        serviceNamespace,secret);

      m_Addresses.Open();

      ...
}
   public void OnCallService()
   {  
      EndpointAddress address = m_Addresses[0];

      IMyContract proxy = ChannelFactory<IMyContract>.CreateChannel(
         new NetTcpRelayBinding(),address);
      proxy.MyMethod();
      (proxy as ICommunicationObject).Close();
   }
   ...
}

Figura 9 viene mostrata l'implementazione parziale di ServiceBusAnnouncementSink <T>senza alcuni la gestione degli errori.

Figura 9 implementazione ServiceBusAnnouncementSink <T>(Parziale)

[ServiceBehavior(UseSynchronizationContext = false,
   InstanceContextMode = InstanceContextMode.Single)]
public class ServiceBusAnnouncementSink<T> : AnnouncementSink<T>,
  IServiceBusAnnouncements
{
   Uri m_AnnouncementsAddress;

   readonly ServiceHost Host;
   readonly string ServiceNamespace;
   readonly string Owner;
   readonly string Secret;

   public ServiceBusAnnouncementSink(string serviceNamespace,
     string owner,string secret)
   {
      Host = new ServiceHost(this);
      ServiceNamespace = serviceNamespace;
      Owner = owner;
      Secret = secret;
   }

   public Uri AnnouncementsAddress
   {
      get
      {
         if(m_AnnouncementsAddress == null)
         {
           m_AnnouncementsAddress = 
             ServiceBusEnvironment.CreateServiceUri(
             "sb",ServiceNamespace,
             DiscoverableServiceHost.AnnouncementsPath);
         }
         return m_AnnouncementsAddress;
      }
      set
      {
        m_AnnouncementsAddress = value;
      }
   }
   public override void Open()
   {
      base.Open();

      Host.AddServiceEndpoint(typeof(IServiceBusAnnouncements),
        AnnouncementsBinding, AnnouncementsAddress.AbsoluteUri);
      Host.SetServiceBusCredentials(Owner,Secret);
      Host.Open();
   }
   public override void Close()
   {
      Host.Close();

      base.Close();
   }

   void IServiceBusAnnouncements.OnHello(Uri address,string contractName,
     string contractNamespace,Uri[] scopes)
   {
      AnnouncementEventArgs args = 
        DiscoveryHelper.CreateAnnouncementArgs(
        address,contractName,contractNamespace,scopes);
      OnHello(this,args); //In the base class AnnouncementSink<T>
   }

   void IServiceBusAnnouncements.OnBye(Uri address,string contractName,
     string contractNamespace,Uri[] scopes)
   {
     AnnouncementEventArgs args = DiscoveryHelper.CreateAnnouncementArgs(
       address,contractName,contractNamespace,scopes);        
     OnBye(this,args); //In the base class AnnouncementSink<T>
   }
}

public static class DiscoveryHelper
{
  static AnnouncementEventArgs CreateAnnouncementArgs(Uri address,
    string contractName, string contractNamespace, Uri[] scopes)
  {
    Type type = typeof(AnnouncementEventArgs);
    ConstructorInfo constructor = 
      type.GetConstructors(BindingFlags.Instance|BindingFlags.NonPublic)[0];

    ContractDescription contract = 
      new ContractDescription(contractName,contractNamespace);

    ServiceEndpoint endpoint = 
      new ServiceEndpoint(contract,null,new EndpointAddress(address));
    EndpointDiscoveryMetadata metadata = 
      EndpointDiscoveryMetadata.FromServiceEndpoint(endpoint);

    return constructor.Invoke(
      new object[]{null,metadata}) as AnnouncementEventArgs;
  }
  // More members
}

Il costruttore di ServiceBusAnnouncementSink <T>ospitato come singleton e Salva lo spazio dei nomi del servizio. Quando si apre ServiceBusAnnouncementSink <T>, aggiunge il proprio host un endpoint IServiceBusAnnouncements di supporto. L'implementazione dei metodi di IServiceBusAnnouncements di gestione eventi crea un'istanza AnnouncementEventArgs, popolare con indirizzo servizio annunciato, contratto e ambiti e quindi chiama l'implementazione della classe base dei metodi rispettivi avvisi come se fosse stato chiamato utilizzando 
regular individuazione WCF. Questo entrambi compila la classe base di AddressesContainer <T>e genera gli eventi appropriati dei AnnouncementSink <T>. Nota per creare un'istanza di AnnouncementEventArgs, è necessario utilizzare la reflection per mancanza di un costruttore pubblico.

Gestione dei metadati

Utilizzando il supporto per il rilevamento per il bus di servizio, estesa la funzionalità di individuazione dello strumento Explorer metadati (presentato negli articoli precedenti) per supportare il servizio bus. Se si sceglie la ricerca pulsante (vedere di Figura 10), per ogni spazio dei nomi servizio già fornite credenziali di gestione metadati tenterà di individuare i metadati scambiare gli endpoint dei servizi individuabili e visualizzare gli endpoint rilevati nella struttura.

image: Configuring Discovery over the Service Bus

Figura 10 configurazione rilevamento su Bus Service

Gestione dei metadati di default utilizzando l'URI “ DiscoveryRequests ” sotto lo spazio dei nomi del servizio. È possibile modificare tale percorso selezionando Service Bus dal menu, quindi ricerca, per visualizzare la finestra di dialogo Configura AppFabric Service Bus Discovery (vedere di Figura 10).

Per ogni spazio dei nomi per il servizio di interesse, la finestra di dialogo consente di configurare il percorso relativo dell'endpoint individuazione eventi nella casella di testo percorso di rilevamento.

Gestione metadati supporta anche gli annunci degli endpoint di scambio metadati di servizio bus. Per attivare la notifica della disponibilità, visualizzare la finestra di dialogo configurazione individuazione e selezionare la casella di controllo Abilita annunci di disponibilità. Explorer metadati imposterà utilizzando URI “ AvailabilityAnnouncements ” sotto lo spazio dei nomi del servizio specificato, ma è possibile configurare per ogni spazio dei nomi servizio altro percorso desiderato per l'endpoint di annunci.

Il supporto in Gestione metadati per gli annunci rende un bus di servizio semplice, pratico e utile strumento di monitoraggio.

Juval Lowy   è un progettista software presso IDesign .NET e architettura di consulenza e formazione. Questo articolo contiene estratti dal suo ultimo libro, “ Programming WCF Services 3 ° Edition ” (O’Reilly 2010). È inoltre il direttore regionale Microsoft per la Silicon Valley. Contatto Lowy in idesign.net .

Grazie all'esperto di tecnica seguente per la revisione di questo articolo: Wade Wegner