Azure WebJobs: ServiceBusTrigger
Introduction
In case we face a challenge to build a Web Job that listens or monitors a queue in the Microsoft Azure Service Bus within a certain namespace to process each message that is sent there by a message producer this article will provide you a walk-through how to accomplish that. The Web Job in this case acts as a message consumer of the messages on the queue. Below a high level diagram of a scenario that will be explained in this article and how to face the challenge.
http://i208.photobucket.com/albums/bb152/Steef-Jan/WebJobs/Overview_zpsthzffacs.png
Picture 1. High Level Overview.
Steps to follow
The following steps demonstrate how to build, deploy and test the Web Job.
Step 1: Build Azure Web Job
We can build WebJobs inside Visual Studio by installing the WebJobs SDK. Once we have installed the SDK we have a template available to build a Web Job in C# or Visual Basic.
http://i208.photobucket.com/albums/bb152/Steef-Jan/WebJobs/Azure%20WebJob%201_zpssqourbww.png
Picture 2. Visual Studio Templates.
We can select this template, specify a name for the Web Job and click Ok. We will see that a program class will be created.
namespace ServiceBusMonitorWebJob { // To learn more about Microsoft Azure WebJobs SDK, please see http://go.microsoft.com/fwlink/?LinkID=320976 class Program { // Please set the following connection strings in app.config for this WebJob to run: // AzureWebJobsDashboard and AzureWebJobsStorage static void Main() { var host = new JobHost(); // The following code ensures that the WebJob will be running continuously host.RunAndBlock(); } } }
And a Functions.cs.
using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; using Microsoft.Azure.WebJobs; namespace WebJob1 { public class Functions { // This function will get triggered/executed when a new message is written // on an Azure Queue called queue. public static void ProcessQueueMessage([QueueTrigger("queue")] string message, TextWriter log) { log.WriteLine(message); } } }
By default a method will be created for us to monitor or listen to an Azure Storage Queue, not Service Bus Queue! To have a method that will be triggered/executed when a message is written to an Azure Service Bus Queue we will need to have ServiceBusTrigger. This not available in the project and we will need to add the Microsoft.Azure.WebJobs.ServiceBus NuGet package.
http://i208.photobucket.com/albums/bb152/Steef-Jan/WebJobs/Azure%20WebJob%202_zpsne7lfzrj.png
Picture 3. Add NuGet Package Microsoft.Azure.WebJobs.ServiceBus.
Now a can change to ServiceBusTrigger in method ProcessQueueMessage and specify the queue I want to listen to.
public static void ProcessQueueMessage([ServiceBusTrigger("inboundqueue")] BrokeredMessage message, TextWriter log)
Next change is changing the type of the message from string type to BrokeredMessage type. This type is not available in our class unless we add using statement for Microsoft.ServiceBus.Messaging. The package is already in the project, because it is part of the imported NuGet package. The TextWriter object can be used to write log statements that can be viewed in the AzureWebJob Dashboard.
When a message arrives on inboundqueue it will be picked up by Web Job and enter the ProcessQueueMessage method at runtime. Here I can extract the message body and send it for instance to Redis cache as a key value pair. To send it to Redis Cache I need to import another NuGet Package i.e. StackExchange.Redis (client library). Now the complete code for the functions class looks like below:
using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; using Microsoft.Azure.WebJobs; using Microsoft.ServiceBus.Messaging; using StackExchange.Redis; using System.Configuration; namespace ServiceBusMonitorWebJob { public class Functions { private static Lazy<ConnectionMultiplexer> lazyConnection = new Lazy<ConnectionMultiplexer>(() => { return ConnectionMultiplexer.Connect(ConfigurationManager.AppSettings["RedisConnectionString"]); }); public static ConnectionMultiplexer Connection { get { return lazyConnection.Value; } } /// <summary> /// Method to Add Key/Value pair to Redis Cache /// </summary> /// <param name="key">Key as string</param> /// <param name="value">Value as string</param> static void AddToRedis(string key, string value) { IDatabase cache = Connection.GetDatabase(); cache.StringSet(key, value); } // This function will get triggered/executed when a new message is written // on an Azure Queue called queue. public static void ProcessQueueMessage([ServiceBusTrigger("inboundqueue")] BrokeredMessage message, TextWriter log) { //Log log.WriteLine("Message picked up from inboundqueue : " + message.MessageId); //Retrieve the message body regardless of the content as a stream Stream stream = message.GetBody<Stream>(); StreamReader reader = new StreamReader(stream); string s = reader.ReadToEnd(); //Log log.WriteLine("Message Body : " + s); //Add to Redis Cache AddToRedis(message.MessageId, s); } } }
Before the Web Job can be deployed to a WebApp a few configuration settings have to be done in the app.config. The connection strings for the AzureWebJobsDashboard and AzureWebJobsStorage need to be provided in the connectionStrings Section. These are required to view the log in Azure i.e. AzureWebJobsDashboard. The connection string that needs to be specified is the connection string to an Azure Storage account. The format is as follows:
DefaultEndpointsProtocol=https;AccountName=[Storage Account Name];AccountKey=[Access Key]
The other connection string that has to be provided is for the AzureWebJobsServiceBus. Format is:
Endpoint=sb://namespace.servicebus.windows.net/;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=[Access Key]
Finally in the appSettings section the connection string for the Redis needs to be specified. Connection string has the format of:
namespace.redis.cache.windows.net,abortConnect=false,ssl=true,password=
Once configuration is done the app.config will look like:
<?xml version="1.0" encoding="utf-8"?> <configuration> <connectionStrings> <add name="AzureWebJobsDashboard" connectionString="DefaultEndpointsProtocol=https;AccountName=mijnnuon2;AccountKey=xxxxxxxxxxxxxxxxxxxxxxxx" /> <add name="AzureWebJobsStorage" connectionString="DefaultEndpointsProtocol=https;AccountName=mijnnuon2;AccountKey=yyyyyyyyyyyyyy" /> <add name="AzureWebJobsServiceBus" connectionString="Endpoint=sb://dev-mijnnuon-servicebus.servicebus.windows.net/;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=zzzzzzzzzzzzzzzzzzzzz"/> </connectionStrings> <startup> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" /> </startup> <runtime> <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> <dependentAssembly> <assemblyIdentity name="Microsoft.WindowsAzure.Storage" publicKeyToken="31bf3856ad364e35" culture="neutral" /> <bindingRedirect oldVersion="0.0.0.0-4.2.1.0" newVersion="4.2.1.0" /> </dependentAssembly> <dependentAssembly> <assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" /> <bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0" /> </dependentAssembly> <dependentAssembly> <assemblyIdentity name="Microsoft.ServiceBus" publicKeyToken="31bf3856ad364e35" culture="neutral" /> <bindingRedirect oldVersion="0.0.0.0-2.7.0.0" newVersion="2.7.0.0" /> </dependentAssembly> </assemblyBinding> </runtime> <system.serviceModel> <extensions> <!-- In this extension section we are introducing all known service bus extensions. User can remove the ones they don't need. --> <behaviorExtensions> <add name="connectionStatusBehavior" type="Microsoft.ServiceBus.Configuration.ConnectionStatusElement, Microsoft.ServiceBus, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /> <add name="transportClientEndpointBehavior" type="Microsoft.ServiceBus.Configuration.TransportClientEndpointBehaviorElement, Microsoft.ServiceBus, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /> <add name="serviceRegistrySettings" type="Microsoft.ServiceBus.Configuration.ServiceRegistrySettingsElement, Microsoft.ServiceBus, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /> </behaviorExtensions> <bindingElementExtensions> <add name="netMessagingTransport" type="Microsoft.ServiceBus.Messaging.Configuration.NetMessagingTransportExtensionElement, Microsoft.ServiceBus, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /> <add name="tcpRelayTransport" type="Microsoft.ServiceBus.Configuration.TcpRelayTransportElement, Microsoft.ServiceBus, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /> <add name="httpRelayTransport" type="Microsoft.ServiceBus.Configuration.HttpRelayTransportElement, Microsoft.ServiceBus, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /> <add name="httpsRelayTransport" type="Microsoft.ServiceBus.Configuration.HttpsRelayTransportElement, Microsoft.ServiceBus, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /> <add name="onewayRelayTransport" type="Microsoft.ServiceBus.Configuration.RelayedOnewayTransportElement, Microsoft.ServiceBus, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /> </bindingElementExtensions> <bindingExtensions> <add name="basicHttpRelayBinding" type="Microsoft.ServiceBus.Configuration.BasicHttpRelayBindingCollectionElement, Microsoft.ServiceBus, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /> <add name="webHttpRelayBinding" type="Microsoft.ServiceBus.Configuration.WebHttpRelayBindingCollectionElement, Microsoft.ServiceBus, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /> <add name="ws2007HttpRelayBinding" type="Microsoft.ServiceBus.Configuration.WS2007HttpRelayBindingCollectionElement, Microsoft.ServiceBus, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /> <add name="netTcpRelayBinding" type="Microsoft.ServiceBus.Configuration.NetTcpRelayBindingCollectionElement, Microsoft.ServiceBus, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /> <add name="netOnewayRelayBinding" type="Microsoft.ServiceBus.Configuration.NetOnewayRelayBindingCollectionElement, Microsoft.ServiceBus, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /> <add name="netEventRelayBinding" type="Microsoft.ServiceBus.Configuration.NetEventRelayBindingCollectionElement, Microsoft.ServiceBus, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /> <add name="netMessagingBinding" type="Microsoft.ServiceBus.Messaging.Configuration.NetMessagingBindingCollectionElement, Microsoft.ServiceBus, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /> </bindingExtensions> </extensions> </system.serviceModel> <appSettings> <add key="RedisConnectionString" value="mijnnuon.redis.cache.windows.net,abortConnect=false,ssl=true,password="/> </appSettings> </configuration>
Step 2: Deploy Azure Web Job
Before the deployment (Publish to Azure) of the Web Job can be done a configuration setting in Web App has to be done to enable AzureWebJobDashboard.
http://i208.photobucket.com/albums/bb152/Steef-Jan/WebJobs/Azure%20WebJob%204_zpsxfzva9ab.png
Picture 4. Web App Settings, add the AzureWebJobsDashboard connection string.
This is an important step. We have to specify the name AzureWebJobsServiceBus, the connection string in format:
*DefaultEndpointsProtocol=https;AccountName=[Storage Account Name];AccountKey=[Access Key]
*
and choose type custom. In case we forget this than observing the Web Job logs will result in the following error:
http://i208.photobucket.com/albums/bb152/Steef-Jan/WebJobs/Code%206_zpsafnh3x9f.png
Picture 5. Error message when not configuring the connection string in the Web App.
Now the Web Job can be deployed via Visual Studio to a WebApp. Right click on the project and choose Publish as Azure WebJob...
http://i208.photobucket.com/albums/bb152/Steef-Jan/WebJobs/Azure%20WebJob%203_zpsusneu2oy.png
Picture 6. Publish the Web Job to Azure in Visual Studio.
We will see a Publish Web dialog and here we import the publishing setting from WebApp. These settings can be downloaded from Azure Portal.
http://i208.photobucket.com/albums/bb152/Steef-Jan/WebJobs/Azure%20WebJob%206_zps3bjc5cci.png
Picture 7. Import the publishing settings of the Web App.
Next we can click OK and we will go to the next section of the dialog i.e. Connection.
http://i208.photobucket.com/albums/bb152/Steef-Jan/WebJobs/Azure%20WebJob%207_zpskzykcmp0.png
Picture 8. Validate connection to Web App.
Click Validate Connection to see if connection info is correct. When valid we can click Publish. Now the Web Job will be published to Web App. In the output window of Visual Studio we will see that deployment went successful.
Step 3: Test the Azure Web Job
In the Azure Portal we can see the Web Job in the Web App.
http://i208.photobucket.com/albums/bb152/Steef-Jan/WebJobs/Azure%20WebJob%208_zpsiowva9qt.png
Picture 9. Web Job inside the Web App.
When we click on the logs URL we will be redirected to the Microsoft Azure Web Jobs portal.
http://i208.photobucket.com/albums/bb152/Steef-Jan/WebJobs/WebJob%20details_zpsttz3frjo.png
Picture 10. Continuous WebJob details.
Nothing much has happened so far, only that the Job has started. In case I send a message to the queue using for instance ServiceBus Explorer, will see some action. Send a message via the ServiceBus Explorer to the queue.
http://i208.photobucket.com/albums/bb152/Steef-Jan/WebJobs/Azure%20WebJob%209_zps4hkspygp.png
Picture 11. Send Message to Service Bus Queue inboundqueue.
Refresh the AzureWeb Job Portal and a new entry is available.
http://i208.photobucket.com/albums/bb152/Steef-Jan/WebJobs/Azure%20WebJob%2010_zps1t4ck4pb.png
Picture 12. Invoke Function entry in Microsoft Azure WebJobs portal.
Once we click on the Functions.ProcessQueueMessage we examine the logs by click Toggle OutPut.
http://i208.photobucket.com/albums/bb152/Steef-Jan/WebJobs/Azure%20WebJob%2011_zpsuspjz3kl.png
Picture 13. Web Job log entries.
To explore what is in my REDIS cache we need to navigate to the service in the Azure Portal and open a console. Enter GET and messagid.
http://i208.photobucket.com/albums/bb152/Steef-Jan/WebJobs/Azure%20WebJob%2012_zpsw1wikuvc.png
http://i208.photobucket.com/albums/bb152/Steef-Jan/WebJobs/Azure%20WebJob%2013_zpsw4dlspg6.png
Picture 14. REDIS Cache Output in Console.
As we can see the message is now in the Cache.
Wrap up
It took me some time to get the into ServiceBusTrigger details. After some digging around I was able to get the ServiceBusTrigger working and see its behavior through the Azure Web Job Portal. The trigger is not limited to queues as it will also work for Service Bus Topic/Subscription. The signature of the method would look like:
public static void ProcessQueueMessage([ServiceBusTrigger(Topic, Subscription)] BrokeredMessage message, TextWriter log)
See Also
Resources to explore with regards to this article are:
- http://stackoverflow.com/questions/15441853/with-azure-brokeredmessage-get-the-body-without-knowing-the-type
- https://azure.microsoft.com/en-us/documentation/articles/websites-dotnet-webjobs-sdk-get-started/
- http://blogs.blackmarble.co.uk/blogs/sspencer/post/2014/09/22/5-Tips-for-using-Azure-Web-Jobs.aspx
- http://stackoverflow.com/questions/28077330/why-do-i-need-to-configure-connection-strings-for-webjobs-in-azure-management-po
- https://azure.microsoft.com/en-us/documentation/articles/storage-configure-connection-string/
For a demonstration see channel 9: https://channel9.msdn.com/Blogs/Microsoft-Integration/Microsoft-Integration-WebJob-Service-Bus-Triggers