© The Author(s), under exclusive license to APress Media, LLC, part of Springer Nature 2023
A. Satapathi, A. MishraDeveloping Cloud-Native Solutions with Microsoft Azure and .NET https://doi.org/10.1007/978-1-4842-9004-0_3

3. Build a Worker Service to Process Messages from Azure Service Bus

Ashirwad Satapathi1   and Abhishek Mishra2
(1)
Gajapati, Odisha, India
(2)
Navi MUmbai, India
 

In the previous chapter, we discussed the enterprise Azure Service Bus and its key features. We explored ways to provision a service bus instance in the Microsoft Azure portal and interact with a service bus queue using our ASP.NET Core web application. You learned about ways to send as schedule a message to the service bus queue. In this chapter, we will build upon the knowledge gained from the previous chapter and explore ways to receive and process messages from the service bus queue.

We will be exploring ways to process messages from the service bus queue by building a .NET Core Worker Service for our fictional dental clinic to send email alerts for the scheduled appointment notifications to the clinic’s patients. The solution will be able to process the enqueued messages in the service bus queue and send an email alert to a patient to remind them about their scheduled appointment at our fictional dental clinic.

Structure

In this chapter, we will explore the following Azure-related topics:
  • Introduction to worker services

  • Build a .NET Core worker service to process messages to Azure Service Bus Queue

  • Deploy the worker service to Azure WebJobs

Objectives

After studying this chapter, you should be able to
  • Understand the fundamentals of worker services

  • Deploy worker services to Azure WebJobs

Introduction to .NET Core Worker Services

Often, we come across scenarios where we would like to perform certain operations which can or cannot be memory intensive by our applications. Many times these operations don’t require a user interface (UI) and are run as background services that run on a schedule or perform operations continuously. Some examples of such example can be to generate reports and send email alerts to the end users periodically or process messages from a message queue. These kinds of scenarios can be handled using background services that run either 24×7 or periodically to perform business requirements.

Along with ASP.NET Core 3.0, a new application template was introduced, the Worker Service template. This template allows us to create an out-of-the-box project to create background services with .NET Core with some boilerplate code. We can create cross-platform background services with the Worker Service template, which can be configured to run as Windows services or Linux daemons. These services can be configured to run and execute the business logic periodically over time or continuously as per the requirement.

The following are some of the scenarios where background services are helpful:
  • Long-running processes

  • CPU-intensive operations

  • Performing scheduled tasks

  • Processing messages asynchronously

Now that we have covered what the Worker Service template is and its use cases, we will explore the life-cycle methods of worker services.

Life-Cycle Methods of Worker Services

Worker classes are added as hosted services using the AddHostedService method in the program.cs class. A worker class implements the BackgroundService abstract class and the BackgroundService class implements the IHostedService interface. Any functionality that we want our worker class to perform needs to be implemented inside the ExecuteAsync method. This method is an abstract method present in the BackgroundService abstract class that is implemented by the worker class.

Apart from ExecuteAsync, there are two other methods, StartAsync and StopAsync, that can be overridden by a worker service to explicitly handle scenarios where we are concerned about activities that need to be performed at the start or end of the worker service. The BackgroundService class provides an implementation of StartAsync and StopAsync methods by default, but we can always override them as per the requirement in our respective worker class.

Problem Statement

As discussed in the previous chapter, our fictional dental clinic wants to send notifications to its customers to remind them about their upcoming appointments. As part of the work performed in the previous chapter, we are able to schedule messages in our service bus queue. But so far, we haven’t been able to do anything with the messages scheduled in the service queue. We need to send email alerts to the patients of our fictional dental clinic about their scheduled appointments to solve the problem statement.

Proposed Solution

Now that you have a brief understanding of background services and have the problem statement at hand, we will be working toward designing and developing a solution to complete the requirements of our fictional dental clinic. We have completed the part of scheduling the appointments in a service bus queue in the previous chapter, but we have yet to find a solution to process these appointments scheduled as messages in the queue. There are various ways we can process the messages, like using a background service or a function app. That solves one part of our requirement, which is to process messages. Now we need to solve one more puzzle: How do we send email notifications to the patients? Well, we have many solutions available to solve this problem. We can use the Gmail SMTP server or use third-party services like SendGrid to send emails to the clinic’s patients.

As we have found quite a few options to solve our problem statement, let’s walk through the approach that we have decided to use to solve the problem statement. We plan to build a background service using the Worker Service template, which will poll for messages continuously and send email alerts to customers using the Gmail SMTP server. We will later deploy this background service to an Azure WebJob and test the functionality by sending a message to the service bus queue by using the Service Bus Explorer in the Azure portal.

Before we start building the background service, we need a couple of things in place. The following are the prerequisites to start development activities:
  • Create a Listener SAS policy in Azure Service Bus Queue

  • Generate the app password for your Gmail account

Once we have these two things in place, we will start building our background service using the Worker Service template in Visual Studio 2022.

Create an SAS Policy to Access the Azure Service Bus Namespace

As discussed in Chapter 2, to access or interact with Azure Service Bus Queues or Topics from our application, we need to authenticate our application’s request to the service bus. This can be done in different ways, such as using Azure Active Directory (Azure AD)–based authentication or SAS policies. For the purpose of this book, we will be using SAS policies. For enterprise applications, it is recommended to use Azure AD–based authentication or managed identity if the applications are deployed inside Azure in an Azure Function or App Service by providing necessary RBAC permissions to the service bus instance.

To create an SAS policy, click Shared access policies in the Settings section, as shown in Figure 3-1, and then click Add to add a new policy. Enter the policy name in the Policy name box on the right and define the scope of this policy. Following the principle to provide least privilege, click the Listen check box to assign this policy only the ability to listen to messages to service bus queues or topics present inside our namespace, and then click Create.
Figure 3-1

Creating an SAS policy

Once the policy has been created, you need to fetch the primary connection string (see Figure 3-2). We will be using this connection string later in this chapter to interact with Azure Service Bus Queue to process the enqueued messages.
Figure 3-2

Getting the primary connection string

Generate an App Password in our GMAIL account

As discussed earlier, we plan to use the Gmail SMTP server to send emails to the clinic’s patients to remind them about their appointments. To send emails using your Gmail account, you have to generate an app password. We will be using this app password for authentication purposes of our SMTP client in our application. To generate the app password, go to the Security section of your Google Account and click App passwords, as shown in Figure 3-3.
Figure 3-3

Click App passwords

You have to enter your password to validate your credentials, as shown in Figure 3-4. Once you have entered the password, click Next.
Figure 3-4

Enter your password and click Next

As shown in Figure 3-5, I already have an app password created, EmailSender. To create a new app password, select the app type as other(Custom Name). Now, enter the name of your choice for the app and click Generate to create the app password.
Figure 3-5

Click Generate

This opens a pop-up message that has the app password generated for you, as shown in Figure 3-6. You need to copy it and store it to use it in your applications. Once copied, click Done.
Figure 3-6

Copy the app password for future use

Now that we have the connection string and password necessary to build our background service, we will start developing the solution using the Worker Service template in the next section.

Create a Worker Service to Process Scheduled Messages in Azure Service Bus Queue

In the previous section, you learned about worker services. The focus of this section is to solve the need of a fictional dental clinic to send appointment notifications to its patients by using the Worker Service template. The clinic currently has an API that schedules appointments as messages in the service bus queue. As part of this section, we will build a background service that polls for messages continuously and sends email alerts to the patients about their scheduled appointments.

As we have covered the business requirement, let’s start building our background service using the Worker Service template. Open Visual Studio 2022 and click Create a new project, as shown in Figure 3-7.
Figure 3-7

Creating a new project

Select the Worker Service project template, as shown in Figure 3-8, and click Next.
Figure 3-8

Click Next

Enter the project name, location, and solution name in the corresponding fields, as shown in Figure 3-9. Click Next to continue.
Figure 3-9

Entering the project name, location, and solution name

Now check the Do not use top-level statements check box and keep the other option as shown in Figure 3-10. Click Create to create our Worker Service project.
Figure 3-10

Click Create

Now Visual Studio will create a sample Worker Service project for us that will contain a simple worker service that inherits the BackgroundService abstract class and provides an implementation to the ExecuteAsync method, which logs a message every one second.

Open the Package Manager Console and run the following command to install the NuGet packages Azure.Messaging.ServiceBus, Microsoft.Extensions.Azure, and Newtonsoft.Json in our project. As discussed earlier in the chapter, Azure.Messaging.ServiceBus is used to communicate with Service Bus. Microsoft.Extensions.Azure is required to inject our Azure Clients inside our dependency container, and Newtonsoft.Json will be used for serialization and deserialization of objects.
Install-Package Azure.Messaging.ServiceBus
Install-Package Microsoft.Extensions.Azure
Install-Package Newtonsoft.Json
Once you have installed the NuGet packages, go to the appsettings.json file of our project and add the following key/value pairs. We need to store the service bus connection string in the ServiceBus key of the connectionStrings node, our queue name in queueName, host name in host, port number in port, email ID of the account from which we want to send emails in emailId, and the app password generated in the previous section in AppPwd.
"ConnectionStrings": {
    "ServiceBus": "provide-your-connection-string"
  },
"queueName": "provide-your-queue-name",
"host": "smtp.gmail.com",
"port": 587,
"emailId": "provide-your-email-address",
"AppPwd": "provide-your-gmail-app-password"
Open the program.cs file and the following namespace:
using Microsoft.Extensions.Azure;
Now, add the following code in program.cs to inject the service bus client in the dependency container of our project. We are using the serviceBusConn stored in our appsettings.json file to create our ServiceBusClient.
services.AddAzureClients(builder
                        => builder.AddServiceBusClient(
                            host.Configuration.GetConnectionString("ServiceBus")
                            )
                        );

For the time being, we are done with the program.cs file of our project. Let’s create two folders in our project: Business and Models. The Business folder will contain our interface and the classes implementing them. The Models folder will contain the classes of our data models.

After creating both folders, create a class called Appointment.cs in the Models folder and paste the following code. This class represents the appointment data that we receive in the request payload. We have used data annotations to mark the necessary fields as required.
public class Appointment
    {
        [Required]
        public int AppointmentId { get; set; }
        [Required]
        public int PatientId { get; set; }
        [Required]
        public string? PatientName { get; set; }
        [Required]
        public string? PatientEmail { get; set; }
        [Required]
        public int CaretakerId { get; set; }
        [Required]
        public DateTime ScheduledAt { get; set; }
    }
Now that our model is ready, create an interface called IMailService.cs in the Business folder and add the method definitions. As shown in the following code snippet, our interface contains one method definition, sendEmail. The sendEmail method will be responsible for taking the appointment data coming from the message payload and sending an email alert to patients by using an HTML template.
public interface IMailService
{
    void SendEmail(Appointment appointment);
}
Now that you know the purpose of the method, create a class called MailService.cs to implement the IMailService interface. Before implementing the interface, add the following property:
private readonly IConfiguration _configuration;
Now, instantiate the property by using constructor injection. Replace your constructor with the following code:
public MailService(IConfiguration configuration)
{
    _configuration = configuration;
}

In the preceding code snippet, we are instantiating an instance of the IConfiguration interface to access the values that we earlier stored in our appsetting.json file.

Now let’s implement the methods of the IMailService interface in the class. Let’s add the following code snippet to implement the methods.

The SendEmail takes the appointment object coming in the parameter of the method and then uses the PatientName and PatientEmail properties of the appointment object to send an email notification. We are defining an object of type MailMessage and provide values of required properties for our case i.e., recipient and sender’s email, Subject, Body. We have created a simple HTML template for the email body. You can customize it to be more elegant using CSS. After instantiating the MailMessage instance, we create an SMTP client using the configuration we stored in our appsettings.json file to send out emails to the patients of our fictional dental clinic

Add the following code as the implementation for the SendEmail method in our MailService class:
public void SendEmail(Appointment appointment)
{
    MailMessage mail = new MailMessage();
    mail.To.Add(appointment.PatientEmail);
    mail.Subject = $"You have a appointment scheduled at {appointment.ScheduledAt.ToString("g")}";
    mail.Body = $"<p>Hello there {appointment.PatientName}!" +
        $"<br /><br />You have a dental appointment scheduled at {appointment.ScheduledAt.ToString("g")}." +
        $"<br /><br />Thanks" +
        $"<br />Ashirwad</p>";
    mail.IsBodyHtml = true;
    mail.From = new MailAddress(_configuration.GetValue<String>("emailId"));
    using (var smtpClient = new SmtpClient())
    {
        smtpClient.Host = _configuration.GetValue<string>("host");
        smtpClient.Port = _configuration.GetValue<int>("port");
        smtpClient.EnableSsl = true;
        smtpClient.UseDefaultCredentials = false;
        smtpClient.Credentials = new System.Net.NetworkCredential(_configuration.GetValue<String>("emailId"), _configuration.GetValue<String>("AppPwd"));
        smtpClient.Send(mail);
    }
}
As we have added the implementation of the SendEmail method defined in our interface, let’s go to our program.cs file and add a singleton service in our DI container by adding the following code snippet:
services.AddSingleton<IMailService, MailService>();
Now, let’s add the following constructor code in our worker class to inject the ILogger, IMailService, ServiceBusClient object to our _logger, _mailService, and _sbClient property using constructor dependency injection and, finally, instantiate the ServiceBusReceiver client using the _sbClient object:
private readonly ILogger<Worker> _logger;
private readonly ServiceBusClient _sbClient;
private readonly ServiceBusReceiver _receiver;
private readonly IMailService _mailService;
public Worker(ILogger<Worker> logger, ServiceBusClient serviceBusClient, IConfiguration configuration, IMailService mailService)
{
    _logger = logger;
    _sbClient = serviceBusClient;
    _receiver = _sbClient.CreateReceiver(configuration.GetValue<String>("queueName"));
    _mailService = mailService;
}
Let’s add our logic to integrate the logic to send email in our worker service. We are going to perform three actions:
  • Fetch messages from the service bus queue using the service bus receiver client

  • Deserialize the content of the message coming from the service bus queue

  • Make a call to the SendEmail method of the MailService to send an email notification to the patients

To do so, we need to replace the ExecuteAsync method with the following snippet:
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
    while (!stoppingToken.IsCancellationRequested)
    {
        _logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
        _logger.LogInformation($"FullyQualifiedNamespace is {_receiver.FullyQualifiedNamespace}");
        try
        {
            var message = await _receiver.ReceiveMessageAsync();
            while (message != null)
            {
                _logger.LogInformation(message.Body.ToString());
                Appointment? data = JsonConvert.DeserializeObject<Appointment>(message.Body.ToString());
                if (data == null)
                {
                    _logger.LogError("Message content was null");
                }
                if (String.IsNullOrWhiteSpace(data.PatientEmail))
                {
                    _logger.LogError("Patient's email does not exist in the payload.");
                }
                _mailService.SendEmail(data);
                await _receiver.CompleteMessageAsync(message);
                message = await _receiver.ReceiveMessageAsync();
            }
            _logger.LogInformation("Waiting for 2 mins now.");
            await Task.Delay(120000);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex.Message);
        }
    }
}

And with this our background service to process the scheduled notification for our fictional dental clinic is complete. Press Ctrl+F5 to build and run our project. The complete source code of this background service project can be found at the following GitHub repository: https://github.com/AshirwadSatapathi/QueueProcessor.

In the next section, we will deploy and test our worker service.

Deploy and Test the Worker Service

Now that we have developed our worker service, let’s deploy it to Azure and perform a sanity test. We can deploy a worker service to Azure in multiple ways, like deploying it to a WebJobs or Azure Container Instance or Azure Container Apps. For the purpose of this chapter, we will be using Azure WebJobs, a feature of Azure App Service that allows us to run scripts or programs in the web app instance. WebJobs are used to run background tasks in Azure and are of two types: continuous and triggered.

Continuous WebJobs are used for background tasks that run on an endless loop and start immediately when the WebJob is created. Triggered WebJobs are used for background tasks that need to run when they are triggered manually or are triggered on a scheduled basis. Continuous WebJobs support remote debugging, which is not supported for triggered WebJobs.

Now that you know what Azure WebJobs are, let’s see how we can deploy a worker service to it using Visual Studio.

To initiate the deployment, you first have to create a publish profile. To do so, right-click the project name and click Publish, as shown in Figure 3-11.
Figure 3-11

Click Publish

Select Azure as the Target, as shown in Figure 3-12, and click Next.
Figure 3-12

Click Next

Select Azure WebJobs as the Specific Target, as shown in Figure 3-13, and click Next.
Figure 3-13

Select Azure WebJobs

Click the + icon (see Figure 3-14) to create a new App Service to host our worker service.
Figure 3-14

Click the + icon

Enter the App Service name, subscription name, resource group, and hosting plan in the respective fields as shown in Figure 3-15. Once you have filled in the required information, click Create. This will create an App Service that will host our worker service.
Figure 3-15

Click Create

As the resource to host our worker service has been provisioned, as shown in Figure 3-16, click Finish to create the publish profile. We will use this to deploy our worker service to the Azure WebJobs running as part of the new App Service we recently provisioned.
Figure 3-16

Click Finish

Now, we want to poll messages continuously, so we have to configure our WebJob type to be continuous. By default, the WebJob type is triggered. To modify it, click the edit button as highlighted in Figure 3-17.
Figure 3-17

Click Edit for the WebJob Type

In the dialog box that opens, shown in Figure 3-18, select the WebJob Type as Continuous and click Save.
Figure 3-18

Click Save

As we have configured the required settings, click Publish to initiate the deployment of our worker service. We can view the deployment status by looking at the Output pane. Once the build and publish have succeeded, we see a message in the pane as shown in Figure 3-19.
Figure 3-19

Click Publish

Now that we have deployed the worker service to our App Service, let’s go to our resource group in Azure and click the web app instance we provisioned, as shown in Figure 3-20.
Figure 3-20

Click DentalQueueProcessor

Right now, our worker service may not be functional because we haven’t configured the application setting with the required key/value pairs. To do so, go to the Configuration window of the App Service, shown in Figure 3-21, and add the AppPwd, emailId, host, port, and queueName in the application settings and ServiceBus key/value pair in the Connection strings section. Once done, click Save. Ideally, it is recommended to keep any kind of connection string or app secrets in a key vault instead of storing the values as is in the application settings of the Configuration section of our App Service.
Figure 3-21

Add key/value pairs in the Configuration window

Now that we have configured the key/value pair, our worker service should be fully functional. We can view the WebJobs by going to the WebJobs section of the App Service, as shown in Figure 3-22. Here, we can start, stop, or delete a WebJob. We can view the logs by clicking the Logs button.
Figure 3-22

View the deployed WebJobs

As our WebJob is fully functional, we can start testing the functionality by sending an appointment payload to the service bus queue as a message. We will be leveraging the service bus queue that we created in Chapter 2. Go to the Queues section of our service bus namespace in the Azure portal and click myqueue, as shown in Figure 3-23.
Figure 3-23

Go to the service bus queue

Now, click the Service Bus Explorer (Preview) section and click Send messages. We need to provide the appointment payload in the Message Body and define the Content type as application/json, as shown in Figure 3-24. Once done, click Send to enqueue a message to the service bus queue.
Figure 3-24

Enqueue a message in Service Bus Queue

As soon the message gets enqueued in the service bus queue, our worker service running in the WebJob will find it and will start processing. Once the message has been processed, the WebJob is going to send a message similar to the one shown in Figure 3-25.
Figure 3-25

Email alert sent to the patients

As you can see, we were able to send an email alert to the patients and solve the problem statement of out fictional dental clinic.

Summary

The Worker Service template provides an out-of-the-box template for building background services in .NET Core. Background services are useful for a variety of scenarios. We can leverage them to build long-running processes to execute continuously or on a scheduled basis. In this chapter, we explored ways to integrate capabilities in our worker service to receive and process messages in our background service by leveraging the powerful Azure Service Bus SDK and used the SMTP client to send email alerts to patients. We saw ways to create a Listener policy in the Azure Service queue and explored ways to create an app password for a Gmail account that can be used to authenticate operations. At the end of the chapter, you learned about ways to deploy our worker service to an Azure WebJob and test it using the Service Bus Explorer provided in the Azure portal for Azure Service Bus Queue.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset