.NET multi-tier application using Azure Service Bus queues
Developing for Microsoft Azure is easy using Visual Studio and the free Azure SDK for .NET. This tutorial walks you through the steps to create an application that uses multiple Azure resources running in your local environment.
You'll learn the following:
- How to enable your computer for Azure development with a single download and install.
- How to use Visual Studio to develop for Azure.
- How to create a multi-tier application in Azure using web and worker roles.
- How to communicate between tiers using Service Bus queues.
Note
To complete this tutorial, you need an Azure account. You can activate your MSDN subscriber benefits or sign up for a free account.
In this tutorial, you'll build and run the multi-tier application in an Azure cloud service. The front end is an ASP.NET MVC web role and the back end is a worker-role that uses a Service Bus queue. You can create the same multi-tier application with the front end as a web project that is deployed to an Azure website instead of a cloud service. You can also try out the .NET on-premises/cloud hybrid application tutorial.
The following screenshot shows the completed application.
To submit an order for processing, the front-end UI component, running in the web role, must interact with the middle tier logic running in the worker role. This example uses Service Bus messaging for the communication between the tiers.
Using Service Bus messaging between the web and middle tiers decouples the two components. In contrast to direct messaging (that is, TCP or HTTP), the web tier doesn't connect to the middle tier directly; instead it pushes units of work, as messages, into Service Bus, which reliably retains them until the middle tier is ready to consume and process them.
Service Bus provides two entities to support brokered messaging: queues and topics. With queues, each message sent to the queue is consumed by a single receiver. Topics support the publish/subscribe pattern in which each published message is made available to a subscription registered with the topic. Each subscription logically maintains its own queue of messages. Subscriptions can also be configured with filter rules that restrict the set of messages passed to the subscription queue to those that match the filter. The following example uses Service Bus queues.
This communication mechanism has several advantages over direct messaging:
Temporal decoupling. When you use the asynchronous messaging pattern, producers and consumers don't need to be online at the same time. Service Bus reliably stores messages until the consuming party is ready to receive them. This enables the components of the distributed application to be disconnected, either voluntarily, for example, for maintenance, or due to a component crash, without impacting the system as a whole. Furthermore, the consuming application might only need to come online during certain times of the day.
Load leveling. In many applications, system load varies over time, while the processing time required for each unit of work is typically constant. Intermediating message producers and consumers with a queue means that the consuming application (the worker) only needs to be provisioned to accommodate average load rather than peak load. The depth of the queue grows and contracts as the incoming load varies. This directly saves money in terms of the amount of infrastructure required to service the application load.
Load balancing. As load increases, more worker processes can be added to read from the queue. Each message is processed by only one of the worker processes. Furthermore, this pull-based load balancing enables optimal use of the worker machines even if the worker machines differ in terms of processing power, as they'll pull messages at their own maximum rate. This pattern is often termed the competing consumer pattern.
The following sections discuss the code that implements this architecture.
In this tutorial, you'll use Microsoft Entra authentication to create ServiceBusClient
and ServiceBusAdministrationClient
objects. You'll also use DefaultAzureCredential
and to use it, you need to do the following steps to test the application locally in a development environment.
- Register an application in the Microsoft Entra ID.
- Add the application to the
Service Bus Data Owner
role. - Set the
AZURE-CLIENT-ID
,AZURE-TENANT-ID
, ANDAZURE-CLIENT-SECRET
environment variables. For instructions, see this article.
For a list of Service Bus built-in roles, see Azure built-in roles for Service Bus.
The first step is to create a namespace, and obtain a Shared Access Signature (SAS) key for that namespace. A namespace provides an application boundary for each application exposed through Service Bus. A SAS key is generated by the system when a namespace is created. The combination of namespace name and SAS key provides the credentials for Service Bus to authenticate access to an application.
To begin using Service Bus messaging entities in Azure, you must first create a namespace with a name that is unique across Azure. A namespace provides a scoping container for Service Bus resources (queues, topics, etc.) within your application.
To create a namespace:
Sign in to the Azure portal.
Navigate to the All services page.
On the left navigation bar, select Integration from the list of categories, hover the mouse over Service Bus, and then select + button on the Service Bus tile.
In the Basics tag of the Create namespace page, follow these steps:
For Subscription, choose an Azure subscription in which to create the namespace.
For Resource group, choose an existing resource group, or create a new one.
Enter a name for the namespace. The namespace name should adhere to the following naming conventions:
- The name must be unique across Azure. The system immediately checks to see if the name is available.
- The name length is at least 6 and at most 50 characters.
- The name can contain only letters, numbers, hyphens
-
. - The name must start with a letter and end with a letter or number.
- The name doesn't end with
-sb
or-mgmt
.
For Location, choose the region in which your namespace should be hosted.
For Pricing tier, select the pricing tier (Basic, Standard, or Premium) for the namespace. For this quickstart, select Standard.
If you select Premium tier, select whether you can enable geo-replication for the namespace. The Geo-Replication feature ensures that the metadata and data of a namespace are continuously replicated from a primary region to one or more secondary regions.
Important
If you want to use topics and subscriptions, choose either Standard or Premium. Topics/subscriptions aren't supported in the Basic pricing tier.
If you selected the Premium pricing tier, specify the number of messaging units. The premium tier provides resource isolation at the CPU and memory level so that each workload runs in isolation. This resource container is called a messaging unit. A premium namespace has at least one messaging unit. You can select 1, 2, 4, 8 or 16 messaging units for each Service Bus Premium namespace. For more information, see Service Bus Premium Messaging.
Select Review + create at the bottom of the page.
On the Review + create page, review settings, and select Create.
Once the deployment of the resource is successful, select Go to resource on the deployment page.
You see the home page for your service bus namespace.
Creating a new namespace automatically generates an initial Shared Access Signature (SAS) policy with primary and secondary keys, and primary and secondary connection strings that each grant full control over all aspects of the namespace. See Service Bus authentication and authorization for information about how to create rules with more constrained rights for regular senders and receivers.
A client can use the connection string to connect to the Service Bus namespace. To copy the primary connection string for your namespace, follow these steps:
On the Service Bus Namespace page, select Shared access policies on the left menu.
On the Shared access policies page, select RootManageSharedAccessKey.
In the Policy: RootManageSharedAccessKey window, select the copy button next to Primary Connection String, to copy the connection string to your clipboard for later use. Paste this value into Notepad or some other temporary location.
You can use this page to copy primary key, secondary key, primary connection string, and secondary connection string.
In this section, you build the front end of your application. First, you create the pages that your application displays. After that, add code that submits items to a Service Bus queue and displays status information about the queue.
Using administrator privileges, start Visual Studio: right-click the Visual Studio program icon, and then select Run as administrator. The Azure Compute Emulator, discussed later in this article, requires that Visual Studio be started with administrator privileges.
In Visual Studio, on the File menu, select New, and then select Project.
On the Templates page, follow these steps:
Select C# for programming language.
Select Cloud for the project type.
Select Azure Cloud Service.
Select Next.
Name the project MultiTierApp, select location for the project, and then select Create.
On the Roles page, double-click ASP.NET Web Role, and select OK.
Hover over WebRole1 under Azure Cloud Service solution, select the pencil icon, and rename the web role to FrontendWebRole. Then select OK. (Make sure you enter "Frontend" with a lower-case 'e,' not "FrontEnd".)
In the Create a new ASP.NET Web Application dialog box, select MVC, and then select Create.
In Solution Explorer, in the FrontendWebRole project, right-click References, then select Manage NuGet Packages.
Select the Browse tab, then search for Azure.Messaging.ServiceBus. Select the Azure.Messaging.ServiceBus package, select Install, and accept the terms of use.
Note that the required client assemblies are now referenced and some new code files have been added.
Follow the same steps to add the
Azure.Identity
NuGet package to the project.In Solution Explorer, expand FrontendWebRole, right-click Models and select Add, then select Class. In the Name box, type the name OnlineOrder.cs. Then select Add.
In this section, you create the various pages that your application displays.
In the OnlineOrder.cs file in Visual Studio, replace the existing namespace definition with the following code:
namespace FrontendWebRole.Models { public class OnlineOrder { public string Customer { get; set; } public string Product { get; set; } } }
In Solution Explorer, double-click Controllers\HomeController.cs. Add the following using statements at the top of the file to include the namespaces for the model you just created, as well as Service Bus.
using FrontendWebRole.Models; using Azure.Messaging.ServiceBus;
Also in the HomeController.cs file in Visual Studio, replace the existing namespace definition with the following code. This code contains methods for handling the submission of items to the queue.
namespace FrontendWebRole.Controllers { public class HomeController : Controller { public ActionResult Index() { // Simply redirect to Submit, since Submit will serve as the // front page of this application. return RedirectToAction("Submit"); } public ActionResult About() { return View(); } // GET: /Home/Submit. // Controller method for a view you will create for the submission // form. public ActionResult Submit() { // Will put code for displaying queue message count here. return View(); } // POST: /Home/Submit. // Controller method for handling submissions from the submission // form. [HttpPost] // Attribute to help prevent cross-site scripting attacks and // cross-site request forgery. [ValidateAntiForgeryToken] public ActionResult Submit(OnlineOrder order) { if (ModelState.IsValid) { // Will put code for submitting to queue here. return RedirectToAction("Submit"); } else { return View(order); } } } }
From the Build menu, select Build Solution to test the accuracy of your work so far.
Now, create the view for the
Submit()
method you created earlier. Right-click within theSubmit()
method (the overload ofSubmit()
that takes no parameters) in the HomeController.cs file, and then choose Add View.In the Add New Scaffolded Item dialog box, select Add.
In the Add View dialog box, do these steps:
In the Template list, choose Create.
In the Model class list, select the OnlineOrder class.
Select Add.
Now, change the displayed name of your application. In Solution Explorer, double-click the Views\Shared\_Layout.cshtml file to open it in the Visual Studio editor.
Replace all occurrences of My ASP.NET Application with Northwind Traders Products.
Remove the Home, About, and Contact links. Delete the highlighted code:
Finally, modify the submission page to include some information about the queue. In Solution Explorer, double-click the Views\Home\Submit.cshtml file to open it in the Visual Studio editor. Add the following line after
<h2>Submit</h2>
. For now, theViewBag.MessageCount
is empty. You'll populate it later.<p>Current number of orders in queue waiting to be processed: @ViewBag.MessageCount</p>
You now have implemented your UI. You can press F5 to run your application and confirm that it looks as expected.
Now, add code for submitting items to a queue. First, you create a class that contains your Service Bus queue connection information. Then, initialize your connection from Global.aspx.cs. Finally, update the submission code you created earlier in HomeController.cs to actually submit items to a Service Bus queue.
In Solution Explorer, right-click FrontendWebRole (right-click the project, not the role). Select Add, and then select Class.
Name the class QueueConnector.cs. Select Add to create the class.
Now, add code that encapsulates the connection information and initializes the connection to a Service Bus queue. Replace the entire contents of QueueConnector.cs with the following code, and enter values for
your Service Bus namespace
(your namespace name) andyourKey
, which is the primary key you previously obtained from the Azure portal.using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Threading.Tasks; using Azure.Messaging.ServiceBus; using Azure.Messaging.ServiceBus.Administration; namespace FrontendWebRole { public static class QueueConnector { // object to send messages to a Service Bus queue internal static ServiceBusSender SBSender; // object to create a queue and get runtime properties (like message count) of queue internal static ServiceBusAdministrationClient SBAdminClient; // Fully qualified Service Bus namespace private const string FullyQualifiedNamespace = "<SERVICE BUS NAMESPACE NAME>.servicebus.windows.net"; // The name of your queue. internal const string QueueName = "OrdersQueue"; public static async Task Initialize() { // Create a Service Bus client that you can use to send or receive messages ServiceBusClient SBClient = new ServiceBusClient(FullyQualifiedNamespace, new DefaultAzureCredential()); // Create a Service Bus admin client to create queue if it doesn't exist or to get message count SBAdminClient = new ServiceBusAdministrationClient(FullyQualifiedNamespace, new DefaultAzureCredential()); // create the OrdersQueue if it doesn't exist already if (!(await SBAdminClient.QueueExistsAsync(QueueName))) { await SBAdminClient.CreateQueueAsync(QueueName); } // create a sender for the queue SBSender = SBClient.CreateSender(QueueName); } } }
Now, ensure that your Initialize method gets called. In Solution Explorer, double-click Global.asax\Global.asax.cs.
Add the following line of code at the end of the Application_Start method.
FrontendWebRole.QueueConnector.Initialize().Wait();
Finally, update the web code you created earlier, to submit items to the queue. In Solution Explorer, double-click Controllers\HomeController.cs.
Update the
Submit()
method (the overload that takes no parameters) as follows to get the message count for the queue.public ActionResult Submit() { QueueRuntimeProperties properties = QueueConnector.adminClient.GetQueueRuntimePropertiesAsync(QueueConnector.queueName).Result; ViewBag.MessageCount = properties.ActiveMessageCount; return View(); }
Update the
Submit(OnlineOrder order)
method (the overload that takes one parameter) as follows to submit order information to the queue.public ActionResult Submit(OnlineOrder order) { if (ModelState.IsValid) { // create a message var message = new ServiceBusMessage(new BinaryData(order)); // send the message to the queue QueueConnector.sbSender.SendMessageAsync(message); return RedirectToAction("Submit"); } else { return View(order); } }
You can now run the application again. Each time you submit an order, the message count increases.
You'll now create the worker role that processes the order submissions. This example uses the Worker Role with Service Bus Queue Visual Studio project template. You already obtained the required credentials from the portal.
Make sure you have connected Visual Studio to your Azure account.
In Visual Studio, in Solution Explorer right-click the Roles folder under the MultiTierApp project.
Select Add, and then select New Worker Role Project. The Add New Role Project dialog box appears.
In the Add New Role Project dialog box, select Worker Role. Don't select Worker Role with Service Bus Queue as it generates code that uses the legacy Service Bus SDK.
In the Name box, name the project OrderProcessingRole. Then select Add.
In Solution Explorer, right-click OrderProcessingRole project, and select Manage NuGet Packages.
Select the Browse tab, then search for Azure.Messaging.ServiceBus. Select the Azure.Messaging.ServiceBus package, select Install, and accept the terms of use.
Follow the same steps to add the
Azure.Identity
NuGet package to the project.Create an OnlineOrder class to represent the orders as you process them from the queue. You can reuse a class you have already created. In Solution Explorer, right-click the OrderProcessingRole class (right-click the class icon, not the role). Select Add, then select Existing Item.
Browse to the subfolder for FrontendWebRole\Models, and then double-click OnlineOrder.cs to add it to this project.
Add the following
using
statement to the WorkerRole.cs file in the OrderProcessingRole project.using FrontendWebRole.Models; using Azure.Messaging.ServiceBus; using Azure.Messaging.ServiceBus.Administration;
In WorkerRole.cs, add the following properties.
Important
Use the connection string for the namespace you noted down as part of prerequisites.
// Fully qualified Service Bus namespace private const string FullyQualifiedNamespace = "<SERVICE BUS NAMESPACE NAME>.servicebus.windows.net"; // The name of your queue. private const string QueueName = "OrdersQueue"; // Service Bus Receiver object to receive messages message the specific queue private ServiceBusReceiver SBReceiver;
Update the
OnStart
method to create aServiceBusClient
object and then aServiceBusReceiver
object to receive messages from theOrdersQueue
.public override bool OnStart() { // Create a Service Bus client that you can use to send or receive messages ServiceBusClient SBClient = new ServiceBusClient(FullyQualifiedNamespace, new DefaultAzureCredential()); CreateQueue(QueueName).Wait(); // create a receiver that we can use to receive the message SBReceiver = SBClient.CreateReceiver(QueueName); return base.OnStart(); } private async Task CreateQueue(string queueName) { // Create a Service Bus admin client to create queue if it doesn't exist or to get message count ServiceBusAdministrationClient SBAdminClient = new ServiceBusAdministrationClient(FullyQualifiedNamespace, new DefaultAzureCredential()); // create the OrdersQueue if it doesn't exist already if (!(await SBAdminClient.QueueExistsAsync(queueName))) { await SBAdminClient.CreateQueueAsync(queueName); } }
Update the
RunAsync
method to include the code to receive messages.private async Task RunAsync(CancellationToken cancellationToken) { // TODO: Replace the following with your own logic. while (!cancellationToken.IsCancellationRequested) { // receive message from the queue ServiceBusReceivedMessage receivedMessage = await SBReceiver.ReceiveMessageAsync(); if (receivedMessage != null) { Trace.WriteLine("Processing", receivedMessage.SequenceNumber.ToString()); // view the message as an OnlineOrder OnlineOrder order = receivedMessage.Body.ToObjectFromJson<OnlineOrder>(); Trace.WriteLine(order.Customer + ": " + order.Product, "ProcessingMessage"); // complete message so that it's removed from the queue await SBReceiver.CompleteMessageAsync(receivedMessage); } } }
You've completed the application. You can test the full application by right-clicking the MultiTierApp project in Solution Explorer, selecting Set as Startup Project, and then pressing F5. The message count doesn't increment, because the worker role processes items from the queue and marks them as complete. You can see the trace output of your worker role by viewing the Azure Compute Emulator UI. You can do this by right-clicking the emulator icon in the notification area of your taskbar and selecting Show Compute Emulator UI.
To learn more about Service Bus, see the following resources: