Building a Scalable Order Processing System with Azure Service Bus, Azure Functions, and .NET Core API

01 Sep 2024
Seamless-integration-of-Azure-ServiceBus-With-Net-Core-Web-API

Introduction

In this blog post, I will guide you through the process of building an order processing system using Console Application, Azure Service Bus, Azure Functions, and a .NET Core API. We will start by creating a:

  • Console Application that acts as a sender, generating orders and sending them to a queue in Azure Service Bus. If you haven't already set up an Azure Service Bus, don't worry—I'll provide instructions to help you get started.
  • Once the orders are in the queue, an Azure Function will be triggered whenever a new message is received. This function will consume the messages and call a .NET Core API application to process the orders.
  • The .NET Core API will handle the order processing and return a success response if everything goes smoothly.

Prerequisites

  • Basic Knowledge of C#
  • An Azure account
  • Knowledge of Azure Service Bus
  • Azure Service Bus created with a queue in Azure portal. If you want to create one follow my blog Create Azure Service Bus namespace with queue. Azure Service bus Acts as the message broker.

Complete Source Code My Git hub repo

Step 1: Setup console application

1.1 - Create the project

In Visual Studio, create a C# console application named "OrderCreationApplication". Select ".Net 8.0" as framework.Also,I selected the option to "place the solution and project in the same directory". This ensures that no additional folder named "OrderCreationApplication" is created, which would be redundant for me.This console application simulates sending order requests to Azure Service Bus.

1.2 - Install required packages

  • Right-click on the project in Solution Explorer.
  • Select "Manage NuGet Packages".
  • Search for Azure.Messaging.ServiceBus and install it.

1.3 - Add "appsettings.json" file to the project

We create "appsettings.json" file so that we can externalise the connection string from the source code.After adding "appsettings.json", ensure you right click on it --> properties and "Copy to Output Directory" as "Copy always"

            {
              "ConnectionStrings": {
                "ServiceBus": "{YOUR_CONNECTION_STRING}"
              }
            }

1.4 - Install two more NuGet Packages

We need to install two more packages i.e "Microsoft.Extensions.Configuration" and "Microsoft.Extensions.Configuration.Json" to read from the appsettings.json file

1.5 - Add .gitignore

Add this "appsettings.json" file to ".gitignore" so that we donnot accidentally push the connection string to the repository.Idea is that since "appsettings.json" may contain secrets, such as connection strings, you should never store it in a remote repository

1.5 - Update the Program.cs

            
             using Azure.Messaging.ServiceBus;
  
             using Microsoft.Extensions.Configuration;

             var builder = new ConfigurationBuilder()
                 .SetBasePath(Directory.GetCurrentDirectory())
                 .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);
             
             IConfiguration configuration = builder.Build();
             var connectionString= configuration.GetConnectionString("ServiceBus");
             var queueName = "orderqueue";
             
              await SendOrdersAsync();
              Console.WriteLine("Press any key to exit...");
              Console.ReadKey();


               async Task SendOrdersAsync()
                  {
                      var client = new ServiceBusClient(connectionString);
                      ServiceBusSender sender = client.CreateSender(queueName);

                      for (int i = 1; i <= 5; i++)
                      {
                          var order = new Order
                          {
                              Product = $"Product-{i}",
                              Quantity = i * 5,
                              CustId =  $"Cust-{i}"
                          };

                          var message = new ServiceBusMessage(System.Text.Json.JsonSerializer.Serialize(order))
                          {
                              ContentType = "application/json"
                          };

                          await sender.SendMessageAsync(message);
                          Console.WriteLine($"Sent order: {System.Text.Json.JsonSerializer.Serialize(order)}");
                      }

                      await sender.DisposeAsync();
                      await client.DisposeAsync();
                  }

                  public class Order
                  {
                      public string Product { get; set; }
                      public int Quantity { get; set; }
                      public string CustId { get; set; }
                  }
            
            

Step 2: Setup Azure function project

2.1 - Create the project

In Visual Studio, search for "Azure Functions" project and create a project.I named it "OrderReceiverAzFn". Select ".Net 6.0" as framework.Also, I selected Function- Trigger and Authorization level- Anonymous. This Listens for messages from the Service Bus and triggers the .NET Core API.

azureFunctionCreationEg

2.2 - Install required packages

Navigate to manage nuget packages and install

  • Microsoft.NET.SDK.Functions
  • Microsoft.Azure.WebJobs.Extensions.ServiceBus

2.3 - Externalise connection string

We need to externalise the connection string as we saw in our console applicaiton. here we already have locationsettings.json to which we add "ServiceBusConnection". The value specified in the Connection property of the Service Bus trigger (or any other binding) must match the key in your local.settings.json file. This key is used to retrieve the corresponding connection string or setting when the function is executed.

2.4 - Create the Azure Funcion


            using System.Net.Http;
            using System.Text;
            using System.Threading.Tasks;
            using Microsoft.Azure.WebJobs;
            using Microsoft.Extensions.Configuration;
            using Microsoft.Extensions.Logging;
            using Newtonsoft.Json;
            
            namespace OrderAzureFunctionApplication
            {
                public class Function1
                {
                    private static readonly HttpClient httpClient = new HttpClient();
            
                    [FunctionName("OrderReceiverAzFn")]
                    public static async Task Run(
                        [ServiceBusTrigger("orderqueue", Connection = "ServiceBusConnection")] string myQueueItem,
                        ILogger log, ExecutionContext context)
                    {
                        // Build configuration
                        var config = new ConfigurationBuilder()
                       .SetBasePath(context.FunctionDirectory) // Use FunctionDirectory instead of FunctionAppDirectory
                       .AddJsonFile("local.settings.json", optional: true, reloadOnChange: true)
                       .Build();
            
                        // Access the Service Bus connection string
                        var serviceBusConnection = config["Values:ServiceBusConnection"];
            
                        log.LogInformation($"C# ServiceBus queue trigger function processed message: {myQueueItem}");
            
                        // Deserialize the order
                        var order = JsonConvert.DeserializeObject <Order>(myQueueItem);
            
                        // Trigger the .NET Core API
                        var apiUrl = "{Your_API_Endpoint}";
                        var json = JsonConvert.SerializeObject(order);
                        var content = new StringContent(json, Encoding.UTF8, "application/json");
            
                        var response = await httpClient.PostAsync(apiUrl, content);
                        if (response.IsSuccessStatusCode)
                        {
                            log.LogInformation("Order processed successfully.");
                        }
                        else
                        {
                            log.LogError($"Failed to process order. Status Code: {response.StatusCode}");
                        }
                    }
            
                    public class Order
                    {
                        public string Product { get; set; }
                        public int Quantity { get; set; }
                        public string CustId { get; set; }
                    }
                }
            }
          

2.4- Update local.settings.json to include SERVICE_BUS_CONNECTION

I added "ServiceBusConnection" Key to Values Object. The value of which is the connection string to Service Bus. Please follow my blog to find the Service Bus Connection string link in the blog Create Azure Service Bus namespace with queue

{
            "IsEncrypted": false,
            "Values": {
              "AzureWebJobsStorage": "UseDevelopmentStorage=true",
              "FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated",
              "ServiceBusConnection": "{Your_Connection_String}"
            }
          }
          

Step 3: Create .NET Core API

3.1 - Create the project

In Visual Studio, search for "ASP .Net Core Web API" project and create a project.I named it "OrderWebAPI". Select ".Net 8.0" as framework.Selected "Authentication type" as none and left other values default.This API is triggered by our Azure function OrderReceiverAzFn

3.2 - Create the controller

Create OrderController.cs in the "Controllers" folder.Basically it exposes a POST method for our Azure Function "OrderReceiverAzFn" to trigger


            namespace OrderWebAPI.Controllers
            {
                [ApiController]
                [Route("api/[controller]")]
                public class OrderController : Controller
                {
                    [HttpPost]
                    public IActionResult ProcessOrder([FromBody] Order order)
                    {
                        // Process the order- Save to DB etc
                        return Ok($"Order for {order.Product} processed successfully.");
                    }
                    public class Order
                    {
                        public string Product { get; set; }
                        public int Quantity { get; set; }
                        public string CustId { get; set; }
                    }
                }
            }

Step 4:Run all the applications

Run the Console Application,Azure Functions and the .NetCore API. After console application creates the order, you can check the messages in Azure portal in Azure Service Bus resource.These orders are consumed by Azure function that in turn calls .Net Core API for processing. API process it and if successful returns Ok to Azure function. Below image show thats. On the left is the terminal from "console application" while on the right are the logs from "Azure function"

final_outcome

Conclusion

In conclusion, we have successfully built a robust order processing system that leverages the power of Azure Service Bus, Azure Functions, and a .NET Core API. By creating a Console Application to send orders to an Azure Service Bus queue, we established a seamless flow of information. The Azure Function acts as a trigger, consuming messages from the queue and invoking the .NET Core API to process each order. This architecture not only enhances scalability and reliability but also allows for easy integration of additional services in the future. As you continue to explore Azure and its capabilities, consider how you can further optimize and expand this system to meet your specific needs. Thank you for following along, and I hope this guide has provided you with valuable insights into building cloud-based applications using Azure technologies!

Complete Source Code
My Git hub repo
The link to my blog
Official Microsoft Link