Introduction
In this article, I am implementing multi-tier application of sending and receiving messages through the Azure Service Bus queue.
Azure Service Bus queue is being used as a repository for First In First Out (FIFO) message processing. A sender sends a message to a queue, where it waits until a receiver picks it.
Background
To demonstrate “Service Bus” I will use a popular Pizza Order Delivery process. Example: When a customer orders pizza, the order will stay in the queue until the order is picked to be processed.
I named the application "Amazing Pizza Order Delivery". Pizza Orders will be placed by the customers through an MVC portal (Web Role). The order will be processed by a background processor (Worker Role).
Implementation
First you need to have an Azure Account to implement the Service Bus. Once your account is set up, go to Azure UI and click on "Create a resource". Please note that the Azure Layout changes time to time so it might look different at the time you are reading this article.
On search bar, type "service bus" and find "service bus resource", select it and click "Create".
Then Click on "+ Add" button and enter a unique Service Bus Name.
In this case, opted for "AmazingServiceBus". Select appropriate pricing, subscription, and location (nearest geographical). Select an existing resource group or create a new one.
When all necessary information is filled out, click on "Create service bus namespace". It will start creating the namespace.
The notification will show "Deployment" and then "Success" message.
Deployment in progress...
Deployment successful...
We will configure "Shared access policies" in the namespace. For simplicity, I will leave the default settings. These settings will be used to access "Service Bus". Note down the "Primary Key" and "Primary connection string" to use it later in the application.
Add a "Queue" in Service Bus. For this example, In this instance "DeliveryOrderQueue".
Once the "Queue" is set up in Azure, let's start creating a Web UI.
Open VS (VS 2017) in admin mode and select "Azure Cloud Service", name the application "AmazingDeliveryService".
Follow the prompt and select "ASP.NET web role" from Azure Cloud Service as we are developing an MVC application. Change the name from "WebRole1" to "AmazingFrontEndWebRole".
Select MVC application with "No Authentication".
Once the MVC application is set up, goto reference, right click, select "Manage NuGet Package" and search for "Windows.Azure.ServiceBus".
Add a class "Connector.cs" to your project, keep class name "Connector", and add this code. This will be used to connect to "Azure Bus Service" to add a message to the queue.
- public static class Connector
- {
- public const string NameSpace = "amazingservicebus";
-
- public const string QueueName = "deliveryorderqueue";
-
- public const string key = "Primary key from servicebus";
-
- public static QueueClient OrdersQueueClient;
-
- public static NamespaceManager CreateNamespaceManager()
- {
- var uri = ServiceBusEnvironment.CreateServiceUri(
- "sb", NameSpace, String.Empty);
-
- var tP = TokenProvider.CreateSharedAccessSignatureTokenProvider(
- "RootManageSharedAccessKey", key);
-
- return new NamespaceManager(uri, tP);
- }
-
- public static void Initialize()
- {
- ServiceBusEnvironment.SystemConnectivity.Mode = ConnectivityMode.Http;
-
- var namespaceManager = CreateNamespaceManager();
-
- var messagingFactory = MessagingFactory.Create
- (namespaceManager.Address, namespaceManager.Settings.TokenProvider);
-
- OrdersQueueClient = messagingFactory.CreateQueueClient(QueueName);
- }
- }
Now, I will create a class for message transfer. I will call it "DeliveryModel" and will add it in "DeliveryModel.cs" file in model folder. Display name will be used to change the labels on Web UI.
Note: We will be using the same file to send and receive message
- public class DeliveryModel
- {
- [DisplayName("Customer Name")]
- public string CustomerName { get; set; }
-
- [DisplayName("Product Name")]
- public string ProductName { get; set; }
- }
Now change the "HomeController" class
- public class HomeController : Controller
- {
- public ActionResult Index()
- {
- return RedirectToAction("Submit");
- }
-
- public ActionResult About()
- {
- ViewBag.Message = "Your application description page.";
-
- return View();
- }
-
- public ActionResult Contact()
- {
- ViewBag.Message = "Your contact page.";
-
- return View();
- }
-
- public ActionResult Submit()
- {
- var namespaceManager = Connector.CreateNamespaceManager();
-
- var queue = namespaceManager.GetQueue(Connector.QueueName);
-
- ViewBag.MessageCount = queue.MessageCount;
-
- return View();
- }
-
- [HttpPost]
- [ValidateAntiForgeryToken]
- public ActionResult Submit(DeliveryModel order)
- {
- if (ModelState.IsValid)
- {
- var message = new BrokeredMessage(order);
- Connector.OrdersQueueClient.Send(message);
- return RedirectToAction("Submit");
-
- }
- else
- {
- return View(order);
- }
- }
- }
Right click on Submit and add a "Submit.cshtml" view. Tie Delivery Model as Model so it can generate automatic UI.
Change the "Submit.cshtml" to be like this...
- @model AmazingFrontendWebRole.Models.DeliveryModel
-
- @{
- ViewBag.Title = "Product Order";
- }
-
- <h2>Submit</h2>
-
-
- @using (Html.BeginForm())
- {
- @Html.AntiForgeryToken()
-
- <div class="form-horizontal">
- <h4>Please input Customer Name & Product Name,
- then click Order. Total Order Count : @ViewBag.MessageCount </h4>
- <hr />
- @Html.ValidationSummary(true, "", new { @class = "text-danger" })
- <div class="form-group">
- @Html.LabelFor(model => model.CustomerName,
- htmlAttributes: new { @class = "control-label col-md-2" })
- <div class="col-md-10">
- @Html.EditorFor(model => model.CustomerName,
- new { htmlAttributes = new { @class = "form-control" } })
- @Html.ValidationMessageFor(model => model.CustomerName, "",
- new { @class = "text-danger" })
- </div>
- </div>
-
- <div class="form-group">
- @Html.LabelFor(model => model.ProductName,
- htmlAttributes: new { @class = "control-label col-md-2" })
- <div class="col-md-10">
- @Html.EditorFor(model => model.ProductName,
- new { htmlAttributes = new { @class = "form-control" } })
- @Html.ValidationMessageFor(model => model.ProductName,
- "", new { @class = "text-danger" })
- </div>
- </div>
-
- <div class="form-group">
- <div class="col-md-offset-2 col-md-10">
- <input type="submit"
- value="Order" class="btn btn-default" />
- </div>
- </div>
- </div>
- }
-
- <div>
- @Html.ActionLink("Back to List", "Index")
- </div>
-
- @section Scripts {
- @Scripts.Render("~/bundles/jqueryval")
- }
Change the "_Layout.cshtml" file to remove unwanted options...
- <!DOCTYPE html>
- <html>
- <head>
- <meta charset="utf-8" />
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>@ViewBag.Title - My ASP.NET Application</title>
- @Styles.Render("~/Content/css")
- @Scripts.Render("~/bundles/modernizr")
- </head>
- <body>
- <div class="navbar navbar-inverse navbar-fixed-top">
- <div class="container">
- <div class="navbar-header">
- <button type="button" class="navbar-toggle"
- data-toggle="collapse" data-target=".navbar-collapse">
- <span class="icon-bar"></span>
- <span class="icon-bar"></span>
- <span class="icon-bar"></span>
- </button>
- @Html.ActionLink("Product Order", "Index", "Home",
- new { area = "" }, new { @class = "navbar-brand" })
- </div>
- <div class="navbar-collapse collapse">
- </div>
- </div>
- </div>
- <div class="container body-content">
- @RenderBody()
- <hr />
- <footer>
- <p>© @DateTime.Now.Year - My ASP.NET Application</p>
- </footer>
- </div>
-
- @Scripts.Render("~/bundles/jquery")
- @Scripts.Render("~/bundles/bootstrap")
- @RenderSection("scripts", required: false)
- </body>
- </html>
Call "Connector.Initialize()" in Application_Start() method.
- protected void Application_Start()
- {
- AreaRegistration.RegisterAllAreas();
- FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
- RouteConfig.RegisterRoutes(RouteTable.Routes);
- BundleConfig.RegisterBundles(BundleTable.Bundles);
-
- Connector.Initialize();
- }
Once all these have been set up, run the application and the UI will look like this.
When you submit an order, you will see the total number of orders in "Total Order Count".
You will also be able to see the total number of messages available in a queue in Azure.
Let's create an application to consume messages. Remember these messages will stay in the queue until consumer doesn't consume.
To create a consumer, we will use Cloud application but this time with a worker role. Create a new VS cloud application named "DeliveryProvider" assuming this will only consume orders from Service Bus Queue.
Select "Worker Role with Service Bus Queue", change the name of service bus to "DeliveryProviderServiceBus"
Once the solution is ready, go to Roles folder and right-click on "DeliveryProviderServiceBus" and open the properties window.
Go to settings in properties and change the value of "Microsoft.ServiceBus.ConnectionString" to the value of Primary Key from Azure's Shared Access Policies window
After that, add "DeliveryModel.cs" file to your project. This is the same file we used to send a message to the queue from MVC application. Note: Don't change namespace or class name.
- public class DeliveryModel
- {
- [DisplayName("Customer Name")]
- public string CustomerName { get; set; }
-
- [DisplayName("Product Name")]
- public string ProductName { get; set; }
- }
Change the code of "WorkerRole.cs" file to be like this
- public class WorkerRole : RoleEntryPoint
- {
-
- const string QueueName = "deliveryorderqueue";
-
- QueueClient Client;
- ManualResetEvent CompletedEvent = new ManualResetEvent(false);
-
- public override void Run()
- {
- Trace.WriteLine("Starting processing of messages");
-
- Client.OnMessage((receivedMessage) =>
- {
- try
- {
- Trace.WriteLine("Processing", receivedMessage.SequenceNumber.ToString());
-
- var order = receivedMessage.GetBody<DeliveryModel>();
-
- Trace.WriteLine(order.CustomerName + ": " + order.ProductName, "ProcessingMessage");
-
- receivedMessage.Complete();
- }
- catch (Exception ex)
- {
-
- }
- });
-
- CompletedEvent.WaitOne();
- }
-
- public override bool OnStart()
- {
-
- ServicePointManager.DefaultConnectionLimit = 4;
-
- string connectionString = CloudConfigurationManager.GetSetting
- ("Microsoft.ServiceBus.ConnectionString");
-
- var namespaceManager = NamespaceManager.CreateFromConnectionString(connectionString);
-
- Client = QueueClient.CreateFromConnectionString(connectionString, QueueName);
-
- return base.OnStart();
- }
-
- public override void OnStop()
- {
- Client.Close();
- CompletedEvent.Set();
- base.OnStop();
- }
- }
As you will see, the code I am trying to connect "deliveryorderqueue" from the connection string specified in Properties->Settings.
Go ahead and run this application. This will run in Azure Emulator in a local machine. Once it runs, it will start consuming orders from the "deliveryorderqueue". When you go to the Azure queue count, the number of orders will be reduced by the number of messages that have been read.