Windows Azure Integration Platform - Service Bus 2.0?

Freitag, 31. Januar 2014

Im Rahmen des Windows Azure Spring Release im April 2013 wurde die neue Version des Windows Azure Service Bus veröffenlicht. Sie bietet einige neue Features, die unterschiedliche Szenarien in der Entwicklung von Integrationslösungen vereinfachen oder besser machen sollen. In diesem Artikel werden die wichtigsten der neuen Features aus der Version 2.0 vorgestellt.

Was ist neu bei der Windows Azure Integration-Plattform?

Wie vermutlich bekannt, ist Service Bus ein Messaging System, das die Implementierung von zahlreichen Integration-Patterns ermöglicht. Die Integration-Patterns sollen nicht das Thema dieses Artikels sein, aber es ist wichtig zu wissen, dass sich die Implementierung von Service Bus-Features in einem hohen Maß an die theoretischen und praktischen Erkenntnisse von Integration-Patterns anlehnt. Ein typischer Entwickler muss sich in der Regel mit solchen Patterns nicht unbedingt auseinandersetzen. Diese Patterns sind aber sehr wichtig, wenn es darum geht, mehrere Systeme miteinander zu verbinden.

Lösungen wie der Service Bus stellen eine fast unverzichtbare Komponente in der Architektur solcher Systeme dar. Diese Systeme sind bei Enterprise- und sog. Distributed System-Entwicklern tagtäglich im Einsatz. Es muss jedoch festgestellt werden, dass solche Systeme in einfacheren und mäßig komplexen Anwendungen selten zum Einsatz kommen.

In einer Welt, die immer mehr durch Devices und Services geprägt wird, werden solche Patterns, und dadurch auch Messaging Systeme, unverzichtbar. Ziel dieses Artikels ist es, die neuesten Features aus der Welt des Service Bus vorzustellen. Zu Message Browsing finden Sie ein weiteres vergleichbares Beispiel unter [1].

Message Browse

In der Vergangenheit wurden die meisten Features im Service Bus untergebracht, die in der Industrie gefragt sind. Im Wurf 2.0 bietet Microsoft einige neue Funktionalitäten, die je nach Szenario eine große Vereinfachung bedeuten.

Bisher war es nicht möglich zu erfahren, welche Messages in der Queue vorhanden sind, ohne die Message zu empfangen. Anders ausgedrückt, die Receiver mussten die Message empfangen und konnten erst dann feststellen, dass diese von keiner Bedeutung ist. Bedauerlicherweise ist eine Message, die von der Queue empfangen wurde, nicht mehr in der Queue vorhanden. Um dies auszuschließen, müsste man neue Messages generieren. Das ist erst einmal sehr mühsam und hinzu kommt, dass dies eine Fehlerquelle darstellen kann. Doch jetzt geht es auch anders – das neue Feature heißt „Message Browse“.

Bevor wir damit anfangen, ist es wichtig, einen Blick in Listing 1 zu werfen. Die Methode prepareQueue() wird in den meisten weiteren Beispielen verwendet. Sie sorgt dafür, dass bevor ein Beispiel ausgeführt wird, eine neue, leere Queue angelegt wird. Dadurch ist sichergestellt, dass die Queue leer ist, bevor die erste Message dort hineingeschrieben wird.

Die Message Browse-Funktionalität ist im Grunde durch die neue Methode Peek() möglich. Diese Methode ähnelt der schon vorhandenen Methode Receive(), mit dem Unterschied, dass Peek() die Message empfängt ohne diese aus der Queue zu entfernen. Zum Beispiel würden zwei aufeinander folgende Aufrufe der Receive()-Methode dazu führen, dass zwei Messages aus der Queue regulär empfangen werden. Wenn aber zuerst ein Peek() aufgerufen wird, wird Peek() Message M1 empfangen. Dann kann der Code die Message analysieren und entscheiden, diese zu bearbeiten. In diesem Falle müsste man den Receiver aufrufen, um zu gewährleisten, dass die Message empfangen wurde.

Listing 2 zeigt, wie das funktioniert. Zuerst schreiben wir in Listing 1 zehn Messages in die Queue und dann empfangen wir diese mit Peek(). Am Ende werden die Messages regulär mit Receive() gelesen. Somit enthält die Queue am Ende des Beispiels keine Messages mehr.

Listing 1: Queue Recreation

/// <summary>
/// Prepares an empty queue.
/// </summary>
/// <param name="qName">The name/path the queue.</param>
private static QueueDescription prepareQueue(string qName)
{
NamespaceManager namespaceManager =  NamespaceManager.CreateFromConnectionString(m_ConnStr);
            
    if (namespaceManager.QueueExists(qName))
        namespaceManager.DeleteQueue(qName);

    return namespaceManager.CreateQueue(qName);
}

Listing 2: Message Browse

private static void messageBrowse()
        {
            string qName = "hello/msgBrowse";

            var queueDesc = prepareQueue(qName);

            var client = QueueClient.CreateFromConnectionString(m_ConnStr, qName, ReceiveMode.PeekLock);
            
            for (int i = 0; i < 10; i++)
            {
                client.Send(new BrokeredMessage("i = " + i.ToString() + ", payload = " + new Random().Next().ToString()));
            }

            BrokeredMessage msg;

            int cnt = 10;

            while (true)
            {
                msg = client.Peek();

                if (msg != null)
                {
                    Console.WriteLine("{0} {1} - {2} - {3}", msg.EnqueuedTimeUtc.ToLocalTime().ToShortDateString(), 
                        msg.EnqueuedTimeUtc.ToLocalTime().ToLongTimeString(), msg.SequenceNumber, msg.GetBody<string>());
                }

                if (--cnt <= 0)
                    break;
            }

            cnt = 10;

            while (true)
            {
                msg = client.Receive();

                if (msg != null)
                {
   			Console.WriteLine("{0} {1} - {2} - {3}", msg.EnqueuedTimeUtc.ToLocalTime().ToShortDateString(), msg.EnqueuedTimeUtc.ToLocalTime().ToLongTimeString(), msg.SequenceNumber, msg.GetBody<string>());
                }

                if (--cnt <= 0)
                    break;
            }

            Console.WriteLine("The end...");
        }
			
		

Message Pump

Bisher waren Entwickler gezwungen, eine Art von Gaming-Loop zu schreiben, wenn Sie kontinuierlich auf Messages in der Queue „hören“ wollten. Folgender Codeschnipsel verdeutlicht, was damit gemeint ist:

	while (true){
		var rcvMsg = receiver.Receive(TimeSpan.FromSeconds(12));
		
		if (rcvMsg != null){
			Console.WriteLine(rcvMsg.GetBody<string>());
		}
		Thread.Sleep(2000);
	}
		

Dieser Code ist auf den ersten Blick nicht besonders kompliziert. Das Problem ist aber die Messages nach dem Empfang weiter zu bearbeiten. Um den Code sauber zu halten, müssen Sie die Message-Prozessoren registrieren und die Messages an diese verteilen. Darüber hinaus müssen Sie auch die Threads kontrollieren, auf denen die Prozessoren aufgerufen werden. Diese kleinen, versteckten Anforderungen sind den meisten Entwicklern nicht bewusst, aber äußerst wichtig. Um das Problem aus dem Weg zu räumen bevor sich jemand beschwert, kann man jetzt die Messages nach dem Event-Driven Pattern empfangen:

receiver.OnMessage((msg) =>{ . . . your code here . . . }

Listing 3: Message Pump Beispiel – Empfangen von Messages mit Callback.

        /// <summary>
        /// Message pump sample.
        /// </summary>
        private static void messagePump()
        {
            string qName = "hello/msgpump";

            prepareQueue(qName);

            var client = QueueClient.CreateFromConnectionString(m_ConnStr, qName, ReceiveMode.PeekLock);

            Console.WriteLine("Executing on base ThreadId: {0}", Thread.CurrentThread.ManagedThreadId);

            for (int i = 0; i < 10; i++)
            {
                client.Send(new BrokeredMessage("i = " + i.ToString() + ", payload = " + new Random().Next().ToString()));
            }

            int cnt = 0;

            client.OnMessage((msg) =>
            {
                Console.WriteLine("ThreadId: {0}, Message: {1}", Thread.CurrentThread.ManagedThreadId, msg.GetBody<string>());

                if (cnt == 10)
                    return;
            },
                new OnMessageOptions()
                {
                    AutoComplete = true,
                    MaxConcurrentCalls = 5
                }
               );

            Console.ReadLine();
        }
			
		

Anschließend werden zehn Messages gesendet und mit client.OnMessages registriert, die callback-Routine, die aufgerufen wird, wenn die Messages empfangen werden. Das ist im Prinzip alles. Abb.1 zeigt den Thread Identifier bei jedem Aufruf von callback. Man sieht deutlich, dass die Threads unterschiedlich sind. Achtung: Das ist eine Console-Anwendung ohne Synchronisationskontext.

Abb.1 zeigt den Thread Identifier bei jedem Aufruf von callback
Abb.1 zeigt den Thread Identifier bei jedem Aufruf von callback

Diese Implementierung beruht auf einem sog. Message Pump-Verfahren. Aus diesem Grund werden Sie den Event-Driven Message Support unter dem Namen Message Pump finden. Um etwas mehr Kontrolle über Message Pump zu bekommen, gibt es ein Argument (optional) namens OnMessageOptions. Hier kann man angeben, ob die Message automatisch auf completed gesetzt wird, nachdem die Callback-Routine erfolgreich beendet ist. Ansonsten müsste man immer selbst msg.Compete() aufrufen.

Viel interessanter ist das Argument ConsurrentCalls. Darüber kann spezifiziert werden, wie viele simultane Aufrufe erfolgen dürfen. Dabei kann dieser Parameter auf  MaxConcurrentCalls = int.MaxValue

Gesetzt werden. Dieser Wert wird sowieso nie erreicht, da im Hintergrund Thread-Pool mit einem maximalen Wert verwendet wird, der deutlich unter Max-Int liegt. Dieses Feature ist besonders dann nützlich, wenn Sie die Messages unbedingt nacheinander bearbeiten möchten. In diesem Falle setzt man MaxConcurrentCalls auf 1.

Task Programming Model

Das bisherige Service Bus SDK bietet grundsätzlich ein synchrones Programmierungsmodell an. Für diejenigen, die unbedingt asynchron programmieren wollen, gab es auch ein Asynchron Programming Modell [2]. Dieses Modell hat den Ruf, etwas schwer verständlich zu sein. Eigentlich stammt dieses Model aus der C++-Welt vor .NET. Erst nach einigen Jahren mit .NET ergab sich das Task-Modell als der ultimative Weg zur Vereinigung von altem C++, VB und neuen, reinen .NET-Entwicklern. Die meisten Entwicklungen aus dem Server-Bereich und somit auch Service Bus bieten in der Regel zuerst ein asynchrones Model an. Das heißt wenn man eine Message senden möchte, geht man wie folgt vor:

 client.Send(new BrokeredMessage(„hello“));


Dies ist offensichtlich ein blocking-call. Das heißt, dass der Sender keine weitere Aufgabe erledigen kann, solange die Netzwerkpakete auf der Reise sind. In den meisten Szenarien ist das genau, was wir wollen, weil es einfach zu verstehen und zu implementieren ist. Leider zeigten die Performanzmessungen, dass man in einer Schleife mit blocking-calls keine nennenswerte Performanz erzielen kann. Folgender Code eignet sich z.B. nicht für großen Netzwerkdurchsatz:

while(..)
{
  client.Send(new BrokeredMessage(„hello“));
}
		

Manche dachten sogar, dass das Service Bus-Protokoll zu langsam sei. Um die Geschwindigkeit zu erhöhen, sollte man mehrere Threads zur Hilfe rufen. Vielmehr möchten wir Folgendes erreichen:

client.SendAsync(new BrokeredMessage(„hello“)):

Wie das geht, zeigt ausführlich Listing 4. Task-Factory (diese hat nichts mit Service Bus zu tun) ist die Helper-Methode, die einen Task aus IAsync Begin- und End- Methoden erzeugen kann:

var sendTask = Task.Factory.FromAsync

Diese Methode hat einen kleinen Nachteil. Es ist nämlich nicht einfach, die richtigen Generic-Argumente zu treffen. Im Prinzip ist dies machbar, aber etwas mühsam. Um dies leichter bewerkstelligen zu können, bietet das SDK 2.0 für den Service Bus dies alles schon an. Jede Methode, die bisher eine Begin-End-Variante hatte, hat jetzt eine asynchrone Schwester-Methode:

SendAsync(),
ReceiveAsync(),
AbandonAsync(),
CompleteAsync(),
. . .

Somit ist mit dem Service Bus SDK 2.0 das Task Programming-Modell Bestandteil der Bibliothek.

Listing 4: Zeigt wie aus Aznc-Programming Model Task-Programming Model angegangen werden kann.

/// <summary>
/// Demonstrate how to create Task based call before SDK 2.0
/// </summary>
private static async void asyncNativeSamples()
{
  await sendAsync(new BrokeredMessage("hello IAsync"));
  Console.ReadLine();
}

private static Task sendAsync(BrokeredMessage msg)
{
  var qDesc = prepareQueue("hello/autodelete");
  var client = QueueClient.CreateFromConnectionString(m_ConnStr, qDesc.Path);
  var sendTask = Task.Factory.FromAsync<BrokeredMessage>(
  client.BeginSend,
  client.EndSend, msg, null);
  // Not required.
		
  //sendTask.Start() 
  return sendTask;	
}		
		

Suspend/Resume

Manchmal ist es notwendig, für eine gewisse Zeit die Operationen an der Queue und/oder dem Topic zu stoppen. Man möchte beispielsweise die Messages weiter empfangen und verarbeiteten, aber das Empfangen von neuen Messages unterbinden, weil man vielleicht die Queue leeren will. Man kann sich bestimmt zahlreiche andere Gründe überlegen, warum man die Service Bus-Entitäten außer Kraft setzen möchte. Diese Anforderung ist vermutlich meistens durch einen Ausnahmezustand verursacht, aber oft von extrem hoher Bedeutung.

Die Operation, welche Entitäten außer Kraft setzt, nennt man „Suspend“ und ihre Kehroperation „Resume“. Eine Entität (Queue oder Topic) kann verschiedene Zustände annehmen, die mit der Klasse EntityStatus bestimmt sind – dies ist aus Listing 5 zu sehen. Der Status der Entität kann explizit Active (default status), Disabled, SendDisabled und ReceiveDisabled gesetzt werden. Restoring ist ein Zwischenzustand, der nicht explizit gesetzt werden kann. Der Status Disabled entspricht der Operation „Suspend“ und Active der Operation „Resume“. Der Status kann wie folgt manipuliert werden:

namespaceManager.GetQueue(„queuePath“);
qDesc.Status = EntityStatus.ReceiveDisabled;
namespaceManager.UpdateQueue(qDesc);		
		

Dieses Code-Snippet zeigt, wie Suspend von Queue Receive-Operationen ausgeführt werden kann. Am Ende ist es wichtig ein Update aufzurufen. Ab diesem Moment wird die Queue keine weiteren Messages an die Receiver bereitstellen, auch wenn welche in der Queue vorhanden sind. In Listing 6 werden zuerst die Messages in die Queue geschrieben, dann werden sie im OnMessage-Handler (Message-Pump) empfangen und in der Console ausgegeben. Wenn die Queue in den Status ReceiveDisabled versetzt wird, kann man deutlich merken, dass keine weiteren Messages empfangen werden. Kurz danach, nachdem die Queue erneut aktiviert wurde (EntityStatus=Active), werden übrig gebliebene Messages empfangen.

Dieses Feature vereinfacht die Management-Operationen und wird auch von SharePoint Workflow im Cumulative Update [3] mitgeliefert. Mit anderen Worten, dieses Feature wurde für SharePoint 2013 Workflow Suspend/Resume implementiert und bereits vor dem Service Bus 2.0 Release mit dem Cumulative Update ausgeliefert.

Listing 5: Status von Entitäten

// Summary:
//     Enumerates the possible values for the status of a messaging entity.
[DataContract(Name = "EntityStatus", Namespace = "http://schemas.microsoft.com/netservices/2010/10/servicebus/connect")]
		
public enum EntityStatus
		
{
  // Summary:
  //     The status of the messaging entity is active.
  [EnumMember]
  Active = 0,
  // Summary:
  //     The status of the messaging entity is disabled.
  [EnumMember]
  Disabled = 1,
  // Summary:
  //     Resuming the previous status of the messaging entity.
  [EnumMember]
  Restoring = 2,
  // Summary:
  //     The sending status of the messaging entity is disabled.
  [EnumMember]
  SendDisabled = 3,
  // Summary:
  //     The receiving status of the messaging entity is disabled.
  [EnumMember]
  ReceiveDisabled = 4,
		
  }
		

Listing 6: Suspend-Resume-Beispiel

public static void SuspendResumeSample()
{
  string qName = "hello/msgpump";
  var qDesc = prepareQueue(qName);
  var client = QueueClient.CreateFromConnectionString(m_ConnStr, qName, ReceiveMode.PeekLock);
  Console.WriteLine("Sending messages...");
  for (int i = 0; i < 10; i++)
  {
    client.Send(new BrokeredMessage("i = " + i.ToString() + ", payload = " + new Random().Next().ToString()));
  }
  int cnt = 10;
  client.OnMessage((msg) =>
  {
    Console.WriteLine("ThreadId: {0}, Message: {1}", Thread.CurrentThread.ManagedThreadId, msg.GetBody<string>());
	Thread.Sleep(1000);
    if (cnt == 10)
      return;
  },
  new OnMessageOptions()
  {
    AutoComplete = true
  }
  );
  // Wait to receive few messages.
  Thread.Sleep(2000);

  NamespaceManager namespaceManager = NamespaceManager.CreateFromConnectionString(m_ConnStr);
  // Execute Suspend.
  qDesc.Status = EntityStatus.ReceiveDisabled;
  namespaceManager.UpdateQueue(qDesc);
  Console.WriteLine("Receive disabled!");

  Thread.Sleep(25000);
  
  // Execute Resume
  qDesc.Status = EntityStatus.Active;
  namespaceManager.UpdateQueue(qDesc);
  Console.WriteLine("Receive enabled...");
  Console.ReadLine();
		
}
		
		

Autodelete On Idle

Manchmal, besonders während der Entwicklung, werden Sie feststellen, dass die Anzahl der Queues oder Topics unter dem Namespace gestiegen ist. Abb. 2 zeigt, wo die Verwaltung von Service Bus Namespaces  im Management-Portal aufgehängt ist. Wenn man einen Namespace auswählt, kommt man durch Navigations-Toolbar zu den verschiedenen Entitäten (Queues, Topics, Relays und Notification Hubs). Die Abb. 3 zeigt die Queue-Verwaltung.

Abb. 2 zeigt, wo die Verwaltung von Service Bus Namespaces  im Management-Portal aufgehängt ist
Abb. 2 zeigt, wo die Verwaltung von Service Bus Namespaces im Management-Portal aufgehängt ist
Abb. 3 zeigt die Queue-Verwaltung
Abb. 3 zeigt die Queue-Verwaltung

Die Liste von Queues in Abb. 3 ist noch überschaubar, sie kann jedoch schnell unübersichtlich werden. Um dies zu vermeiden, kann man z.B. ein Tool implementieren, dass nach einer Regel die nicht genutzten Entitäten löscht. Leider ist dies in der Praxis auch nicht immer anwendbar. Viel besser wäre es, wenn die nicht genutzten Entitäten einfach automatisch entfernt werden könnten. Dieses Feature nennt man „AutodeleteOnidle“, d.h. wenn in der Queue keine Messages geschrieben werden bzw. keine Messages gelesen werden, ist die Queue per Definition im „Idle“-Zustand (Ruhe-Zustand) – ein AutoDelete kann jetzt wie folgt erzwungen werden:

QueueDescription desc = new QueueDescription(qName);
desc.AutoDeleteOnIdle = TimeSpan.FromDays(3);
returnnamespaceManager.CreateQueue(desc);
		

Sollte die Queue bereits existieren, könnte sie Messages enthalten. Wenn Sie in diesem Fall AutoDelete erzwingen möchten, können Sie die Queue wie folgt aktualisieren, ohne diese neu erzeugen zu müssen:

qDesc.AutoDeleteOnIdle = TimeSpan.FromMinutes(5);

NamespaceManager namespaceManager = NamespaceManager.CreateFromConnectionString(m_ConnStr);
namespaceManager.UpdateQueue(qDesc);
		

Wann immer an einer bestehenden Entität etwas verändert werden soll, muss am Ende stets ein Update()-Aufruf erfolgen, wie bereits die Erläuterung von Suspend/Resume gezeigt hat. Listing 7 zeigt ein realistisches Szenario: Eine Queue wird mit Auto-Delete on Idle mit fünf Minuten konfiguriert. Danach wird jede Minute mit QueueExists() überprüft, ob die Queue noch vorhanden ist. Erst nach ca. fünf bis sechs Minuten wird die Queue gelöscht. Ergebnis von Listing 7 ist in Abb. 4 zu sehen.

Abb. 4 zeigt das Ergebnis von Listing 7
Abb. 4 zeigt das Ergebnis von Listing 7

AutoDeleteOnIdle ist ebenso mit TopicDescription und SubscriptionDescription möglich. Dieses Feature ist sehr hilfreich, sollte aber nicht unbedingt im jeden Szenario verwendet werden. Angenommen, es wird eine Anwendung implemntiert, die an einem Topic mehrere Subskriptionen verwendet. Z.B. soll eine Subskription per E-Mail verschickt werden, wenn bestimmte Messages zum Topic geschickt werden. Die andere Subskription zeigt z.B Real-Time Messages in einem Dashboard.

Bei der ersten Subskription wird offensichtlich Event-Sourcing verwendet, um eine Business-Funktionalität durchzuführen. Dies bedeutet, selbst wenn der Subscriber, an dem düe E-Mail verschickt werden soll, längere Zeit nicht online ist, sollten die Messages übermittelt werden. Deshalb wäre ein AutoDelete vermutlich keine gute Idee. Im Falle eines Real-Time Dashboard würden die angesammelten Messages vermutlich stören, wenn diese zur Dashboard-Anwendung geschickt werden, sobald Dashboard wieder online ist.

Listing 7: Autodelete-Beispiel

public static void AutodeletOnIdleSample()
{
  var qDesc = prepareQueue("hello/autodelete");
  qDesc.AutoDeleteOnIdle = TimeSpan.FromMinutes(5);
  NamespaceManager namespaceManager = NamespaceManager.CreateFromConnectionString(m_ConnStr);
  namespaceManager.UpdateQueue(qDesc);
  DateTime createdAt = DateTime.Now;
  while (namespaceManager.QueueExists("hello/autodelete"))
  {
    Thread.Sleep(60000);
    Console.WriteLine("Queue exists after: " + (DateTime.Now - createdAt).TotalMinutes.ToString("#.#") + " min");
  }
  Console.WriteLine("Queue has been finally removed after {0} minutes.", ((DateTime.Now - createdAt).TotalSeconds / 60).ToString("#.#") + " min");
  Console.ReadLine();
}
		

Shared Signature Access

Wenn ein Namespace im Service Bus Management-Portal erzeugt wird, erstellt das Portal im Hintergrund einen Access Control Service (ACS) Namespace und legt einen Benutzer mit dem Namen Owner an [4]. Dieser Benutzer hat die administrativen Privilegien durch die Permission „Manage“. Wenn Sie weitere Benutzer hinzufügen möchten, müssen Sie diese im Portal verwalten oder skripten. So lange die Anzahl von Benutzern überschaubar ist, ist eine solche Lösung akzeptabel. Je nach Anforderung können dem Benutzer Permissions wie Manage, Listen oder Send explizit gegeben werden. Um die Verwaltung von Benutzern zu vermeiden, überlegte sich das Service Bus Team den vielen Anforderungen gerecht zu werden, indem die Permissions vom Access Control Service komplett entkoppelt werden. Um das zu verdeutlichen folgendes Beispiel: Es wird eine Factory-Instanz aus dem IssuerName und seinem Key erzeugt.

var factory0 = MessagingFactory.Create(new Uri("sb://namespace.servicebus.windows.net"), TokenProvider.CreateSharedSecretTokenProvider("issuername", "...key..."));

Desweitern ist es unter anderem ebenso notwendig die Credentials mit Benutzernamen und Password zu erzeugen.

var factory = MessagingFactory.Create(new Uri("sb://namespace.servicebus.windows.net"), TokenProvider.CreateUserNamePasswordTokenProvider("username", "pwd"));

In den beiden Fällen müssen sogenannte Service Identities [4] Credentials an alle Anwendungen (Benutzer) verteilt werden. Die Verwendung des Shared Access Signatures Service Bus 2.0 bietet ein einfacheres Konzept: Um eine Factory zu erzeugen, benötigt man nicht mehr die Service-Identitäten und Ihre Kennworte bzw. Keys. Man verwendet jetzt die neue Methode TokenProvider.​CreateShared​Access​Signature​Token​Provider.

 Var factory1=MessagingFactory.Create(new Uri("sb://namespace.servicebus.windows.net"), TokenProvider.CreateSharedAccessSignatureTokenProvider("RuleName", “..key..”));

Auf den ersten Blick sieht sie genau wie ihre Schwestermethoden Create​Shared​Secret​Token​Provider  und Create​User​Name​Password​Token​Provider aus. Sie ist aber fundamental anders. Sie benötigt ebenso einen Key. Dieses Mal ist das ein frei generierter SharedAccessKey und nicht der Key der Service-Identität. Desweitern verwendet diese Methode auch einen Parameter („RuleName“) der ein frei wählbarer Text ist und im Grunde auf den Namen KeyName getauft ist. Also

CreateSharedAccessSignatureTokenProvider(string keyName, stringsharedAccessKey).

Damit dies zum Tragen kommt, muss folgender Code ausgeführt werden. Zuerst muss der SharedAccessKey erzeugt werden:

string saKey = SharedAccessAuthorizationRule.GenerateRandomKey();

Der Key muss 44 Bytes lang sein und sieht wie folgt aus: „rDVTL8PQUtHJFSu​6FDIM3fwrBDa​ywxmon4xTaD71FXw=“. Desweitern müssen auf den Entitäten die AuthorizationRules gesetzt werden. Folgendes Code-Snippet erzeugt eine Regel, diese hat den Namen „demouser1_cansend“ und die Permission „Send“ zum Senden von Messages.

QueueDescription qDesc1 = new QueueDescription(“myqueue”);
qDesc1.Authorization.Add(new SharedAccessAuthorizationRule("demouser1_can_Send", key1, new AccessRights[] { AccessRights.Send }));

Wenn ein Client zu der Queue “myqueue” Messages schicken möchte, muss er wie folgt vorgehen:

TokenProvider.CreateSharedAccessSignatureTokenProvider("demouser1_can_Send", „rDVTL8PQUtHJFSu6FDIM3fwrBDaywxmon4xTaD71FXw=“.));

Und hier ist der vollständige Code:

public static void AuthorizationRulesSample()
{
  NamespaceManager namespaceManager = NamespaceManager.CreateFromConnectionString(m_ConnStr);
  string qName = "hello/rule1q";
  string key1 = SharedAccessAuthorizationRule.GenerateRandomKey();
  string key2 = SharedAccessAuthorizationRule.GenerateRandomKey();
  if(namespaceManager.QueueExists(qName))
    namespaceManager.DeleteQueue(qName);
  QueueDescription qDesc1 = new QueueDescription(qName);
  qDesc1.Authorization.Add(new SharedAccessAuthorizationRule("demouser1_can_Send", key1, new AccessRights[] { AccessRights.Send }));
  qDesc1.Authorization.Add(new SharedAccessAuthorizationRule("demouser2_can_Listen", key2, new AccessRights[] { AccessRights.Listen }));
  namespaceManager.CreateQueue(qDesc1);
  var factory1 = MessagingFactory.Create(new Uri("sb://namespace.servicebus.windows.net"), TokenProvider.CreateSharedAccessSignatureTokenProvider("demouser1_can_Send", key1));
  var factory2 = MessagingFactory.Create(new Uri("sb://namespace.servicebus.windows.net"), TokenProvider.CreateSharedAccessSignatureTokenProvider("demouser2_can_Listen", key2));
  var clientDemoUser1 = factory1.CreateQueueClient(qName,                                                  ReceiveMode.PeekLock);
  var clientDemoUser2 = factory2.CreateQueueClient(qName,                                                  ReceiveMode.PeekLock);
  clientDemoUser1.Send(new BrokeredMessage("abc"));
  try
  {
    // This one has not a right to send to queue.
    clientDemoUser2.Send(new BrokeredMessage("abc"));
  }
  catch (UnauthorizedAccessException)
  {
  }
  try
  {
    // This one has not a right to Listen on queue.
    var msg = clientDemoUser1.Receive();
  }
  catch (UnauthorizedAccessException)
  {
  }
  // This one can listen.
  var msg2 = clientDemoUser2.Receive();
  Console.ReadLine();
}

Fazit

Dieser Artikel zeigte viele der wichtigen neuen Features des Service Bus v2.0. Die neue Version stellt keine revolutionäre Änderung gegenüber Version 1.8. dar. Das war, offen gesagt, auch nicht die Erwartung. Vielmehr bietet diese Version einige Features an, die das Ziel haben, einige, häufig verwendete Szenarien einfacher und effizienter zu lösen. Damit ist die Story des Service Bus aber noch lange nicht abgeschlossen. Es gibt bereits eine öffentliche Preview-Version, die als Basis-Protokoll AMQP verwendet [5]. In derselben Version ist sogar die Performanz von nativen Service Bus-Protokollen um einiges optimiert worden. Was gegenwärtig ein Problem darstellt, ist dass das Windows Azure Service Bus SDK 2.0 nicht mit dem Service Bus for Windows Server SDK kompatibel ist. Dies wird möglicherweise zum Zeitpunkt der Veröffentlichung dieses Artikels bereits der Fall sein. Unabhängig davon: Durch die steigende Anzahl von Geräten am Markt steigt die Anforderung diese miteinander zu verbinden, dadurch wird Service Bus sehr bald eine noch wichtigere Rolle spielen.

[1] Message Browse-Beispiel
http://code.msdn.microsoft.com/​Service-Bus-Message-Browse-4e434ffe

[2] Zeitverschiebung: Asynchrones Programmieren. .NET Pro
http://www.dotnetpro.de/​articles/​onlinearticle2898.aspx

[3] Workflow Manager Cumulative Update
http://www.microsoft.com/de-de/​download/​details.aspx?id=36800

[4] Zentrale Zugriffskontrolle
http://www.dotnetpro.de/articles/​onlinearticle4037.aspx

[5] Service Bus AMQP Support
http://www.windowsazure.com/en-us/​develop/​net/​how-to-guides/​service-bus-amqp/

[6] .Was ist neu im Service Bus 2.0?
http://developers.de/blogs/​damir_dobric/​archive/​2013/​05/​01/​what-is-new-in-service-bus-2-0.aspx

[7] Service Bus 2.0 Beispiele
http://code.msdn.microsoft.com/​ServiceBus-V2-Features-6b29f2ca

[8] Channel 9 Video zum Thema Service Bus 2.0
http://channel9.msdn.com/Blogs/​Subscribe/​Whats-new-in-the-Service-Bus-NET-SDK-20

Mit freundlicher Unterstützung von dotnetpro

Mit freundlicher Unterstützung von dotnetpro, der meistverbreiteten .NET-Entwicklerzeitschrift im deutschsprachigen Raum. http://www.dotnetpro.de/​articles/​onlinearticle4628.aspx

MSDN Flash Newsletter abonnieren