.Net 6 - Create Image Processing with Azure Functions & Service Bus
Tags: dotnet, azure, serverless, api
Published: 2022-02-27 10:55:33

In this article we will be creating an image processing system, we will utilise Azure Services to accomplish this from Azure Service Bus to Azure functions

You can watch the full video step by step on YouTube:
{% embed https://youtu.be/gvJUeNAunwM %}

Full source code on github https://github.com/mohamadlawand087/image-processing

We will be building the following system

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mrb1yeyaxk4hkjq7kwik.jpg)

Create web api

```bash
dotnet new webapi -n "ImageProcessing"
```

Install Packages

```csharp
dotnet add package Azure.Storage.Blobs 
dotnet add Azure.Messaging.ServiceBus
```

We start by going to Azure and creating our ServiceBus and our blob storage

We create an interface and class to handle the Azure Storage. First in the root folder we create a folder called Services and inside the services folder we create another folder called interfaces. Inside the interfaces folder we add the following interface

```csharp
namespace ImageProcessing.Services.Interfaces;

public interface IBlobsManagement
{
    Task<string> UploadFile(string containerName, string fileName, byte[] file);
}
```

Now inside the Services folder we add the implementation of the IBlobsManagement interface, we create a new class called BlobsManagement

```csharp
using Azure.Storage.Blobs;
using Azure.Storage.Blobs.Models;
using ImageProcessing.Services.Interfaces;

namespace ImageProcessing.Services;

public class BlobsManagement : IBlobsManagement
{
    public async Task<string> UploadFile(string containerName, string fileName, byte[] file, string connectionString)
    {
        try
        {
            // Get a reference to a container named "sample-container" and then create it
            var container = new BlobContainerClient(connectionString, containerName);
            await container.CreateIfNotExistsAsync();
            await container.SetAccessPolicyAsync(PublicAccessType.Blob);

            // Get a reference to a blob named "sample-file" in a container named "sample-container"
            var blob = container.GetBlobClient(fileName);

            Stream str = new MemoryStream(file);
            // Upload local file
            await blob.UploadAsync(str);

            return blob.Uri.AbsoluteUri;
        }
        catch (Exception)
        {
            // TODO: Add better error handling
            return string.Empty;
        }
    }
}
```

Next we need to inject the blob implementation in the program.cs so we need to add the following

```csharp
builder.Services.AddScoped<IBlobsManagement, BlobsManagement>();
```

Now we need to handle the Service bus implementation. inside the Services ⇒ Interface folder we need to add a new interface called IAzureServiceBud

```csharp
namespace ImageProcessing.Services.Interfaces;

public interface IAzureServiceBus
{
    Task SendMessageAsync<T>(T serviceBusMessage, string queueName, string connectionString);
}
```

Next we need to add the implementation for our service bus interface, inside the Services folder we need to add the following AzureServiceBus class

```csharp
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Text.Json;
using ImageProcessing.API.Services.Interfaces;
using Azure.Messaging.ServiceBus;

namespace ImageProcessing.API.Services;

public class QueuesManagement : IQueuesManagement
{
    public async Task<bool> SendMessage<T>(T serviceMessage, string queue, string connectionString)
    {
        try
        {
            // since ServiceBusClient implements IAsyncDisposable we create it with "await using"
            await using var client = new ServiceBusClient(connectionString);
            
            // create the sender
            ServiceBusSender sender = client.CreateSender(queue);
            
            var msgBody = JsonSerializer.Serialize(serviceMessage);

// create a message that we can send. UTF-8 encoding is used when providing a string.
            ServiceBusMessage message = new ServiceBusMessage(msgBody);

// send the message
            await sender.SendMessageAsync(message);

            return true;
        }
        catch (Exception e)
        {
            Console.WriteLine(e);
            return false;
        }
       
    }
}
```

Similar to the BlobsManagement initialisation inside the the Program.cs we need to do the same for the AzureServiceBus

```csharp
builder.Services.AddScoped<IAzureServiceBus, AzureServiceBus>();
```

We create a folder called Models and inside of it we create a class called ImageResize

```csharp
public class ImageResize
{
    public string FileName { get; set; }
    public string Url { get; set; }
    public int Width { get; set; }
    public int Length { get; set; }
    public string ImageContainer { get; set; }
    public string Target { get; set; }
}
```

Now we need to add the configurations for ServiceBus and Blob inside our appsettings.json

```json
"ServiceBus" : {
    "ImagesBus": " "
  },
  "Storage": {
    "ImagesConnection": ""
  },
```

Create Images Controller

```csharp
using Microsoft.AspNetCore.Mvc;
using ImageProcessing.Extensions;
using ImageProcessing.Models;
using ImageProcessing.Services.Interfaces;

namespace ImageProcessing.Controllers;

[ApiController]
[Route("[controller]")]
public class ImagesController : ControllerBase
{
    private readonly IConfiguration _configuration;
    private readonly IBlobsManagement _blobsManagement;
    private readonly IAzureServiceBus _serviceBus;

    protected ImagesController(
        IAzureServiceBus serviceBus,
        IBlobsManagement blobsManagement,
            IConfiguration configuration)
    {
        _configuration = configuration;
        _blobsManagement = blobsManagement;
        _serviceBus = serviceBus;
    }

    [HttpPost]
    [Route("Profile")]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> Profile(IFormFile? img)
    {
        if (img != null)
             await UploadFile(img, 300, 300);

        return Ok();
    }

    [NonAction]
    private async Task UploadFile(IFormFile? file, int width, int height)
    {
        if (file is not {Length: > 0}) return;
        var conn = _configuration["Storage:ImagesConnection"];
        
        MemoryStream? stream = null;
        await using (stream = new MemoryStream())
        {
            await file.CopyToAsync(stream);
            var bytes = stream.ToArray();
        }

        var fileBytes = await file.GetBytes();

        // Upload the Zip File into the server
        var name = Path.GetRandomFileName() + "_" + DateTime.UtcNow.ToString("dd/MM/yyyy").Replace("/", "_") + file.FileName.Replace(" ", "");

        var url = await _blobsManagement.UploadFile("images", name, fileBytes, conn);
        
        await SendImageProcessingRequest(url, name, width, height, "images");
    }

    [NonAction]
    private async Task SendImageProcessingRequest(string imgLocation, string imgName, int width, int height, string container)
    {
        var conn = _configuration["ServiceBus:ImagesBus"];
        ImageResize img = new ()
        {
            FileName = imgName,
            Width = width,
            Length = height,
            Url = imgLocation,
            ImageContainer = container
        };

        await _serviceBus.SendMessageAsync<ImageResize>(img, "resizerequest", conn);
    }
}
```

Now we need to create an Azure functions with a ServiceBusTrigger, the configuration for the ServiceBus and Blob storage should be the same as the one we have added in our API.

To create the Azure Functions i recommend using the VS Code tool [https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-azurefunctions](https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-azurefunctions) which will give you a step by step guide on how to create the functions and configure it

Once the function is created we need to install the following packages Azure Function Nuget Packages

```csharp
dotnet add package RestSharp 
dotnet add package SixLabors.ImageSharp 
dotnet add package Azure.Storage.Blobs 
dotnet add package Newtonsoft.Json 
```

Once all of the packages has been installed the next step is to start building our Function utilising the following

```csharp
using System;
using System.IO;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Functions;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Host;
using Microsoft.Extensions.Logging;
using Azure.Storage.Blobs;
using Microsoft.WindowsAzure.Storage;
using Newtonsoft.Json;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Gif;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;

namespace ImageProcessing.Functions
{
    public static class ProcessImageResize
    {
				// Decoder for our images based. onthe image format that we have
        private static IImageEncoder GetEncoder(string extension)
        {
            IImageEncoder encoder = null;

            extension = extension.Replace(".", "");

            var isSupported = Regex.IsMatch(extension, "gif|png|jpe?g", RegexOptions.IgnoreCase);

            if (isSupported)
            {
                switch (extension)
                {
                    case "png":
                        encoder = new PngEncoder();
                        break; 
                    case "gif":
                        encoder = new GifEncoder();
                        break;
                    case "jpg":
                        encoder = new JpegEncoder();
                        break;
                    case "jpeg":
                        encoder = new JpegEncoder();
                        break;
                    default:
                        break;
                }
            }

            return encoder;
        }
        
        
        [FunctionName("ProcessImageResize")]
        public static async Task Run([ServiceBusTrigger("imagequeue", Connection = "demoimagebus_SERVICEBUS")]string myQueueItem, ILogger log)
        {
            log.LogInformation($"C# ServiceBus queue trigger function processed message: {myQueueItem}");

            var resizeInfo = JsonConvert.DeserializeObject<ImageResizeDto>(myQueueItem);

            var storageConn = Environment.GetEnvironmentVariable("AzureWebJobsStorage");

            // Connect to the storage account
            var storageAccount = CloudStorageAccount.Parse(storageConn);

            var myClient = storageAccount.CreateCloudBlobClient();
            
            // connect to a container
            var container = myClient.GetContainerReference("images");
            
            await container.CreateIfNotExistsAsync();

            var blobName = resizeInfo.FileName;
            
            // Get reference of the blob storage
            var cloudBlockBlob = container.GetBlobReference(blobName);

            var ms = new MemoryStream();

            await cloudBlockBlob.DownloadToStreamAsync(ms);
						
						// Convert the downloeaded image to byte[]
            byte[] bytes = ms.ToArray();

            var extension = Path.GetExtension(resizeInfo.FileName);
            var encoder = GetEncoder(extension);

				   // Start image resize
		       using (var output = new MemoryStream())
            using (Image<Rgba32> image = Image.Load(bytes))
            {
                log.LogInformation("Image Resize has started");
                
                image.Mutate(x => x.Resize(new ResizeOptions()
                {
                    Size = new Size(resizeInfo.Width,resizeInfo.Height),
                    Compand = true,
                    Mode = ResizeMode.Max
                }));
                
                 image.Save(output, encoder);
                 output.Position = 0;
                
                // Create a new file and upload it to a blob storage
                var newFileName = $"resize_{resizeInfo.FileName}";

                var blobServiceClient = new BlobServiceClient(storageConn);
                var blobContainerClient = blobServiceClient.GetBlobContainerClient("images");

                var blobCopy = container.GetBlobReference(newFileName);

                if (!await blobCopy.ExistsAsync())
                {
                    log.LogInformation("Upload to blob has started");
                    var uploadResult = await blobContainerClient.UploadBlobAsync(newFileName, output);
                    
                    log.LogInformation($"Result: {uploadResult.Value.VersionId}");
                }
            }
        }
    }
}
```

Please note this is a prototype of how we approach a service base oriented functionality. Please add your questions below.

Thank you for reading