DEV Community

Cover image for .Net 6 - Create Image Processing with Azure Functions & Service Bus
Mohamad Lawand
Mohamad Lawand

Posted on

.Net 6 - Create Image Processing with Azure Functions & Service Bus

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:

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

We will be building the following system

Image description

Create web api

dotnet new webapi -n "ImageProcessing"
Enter fullscreen mode Exit fullscreen mode

Install Packages

dotnet add package Azure.Storage.Blobs 
dotnet add Azure.Messaging.ServiceBus
Enter fullscreen mode Exit fullscreen mode

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

namespace ImageProcessing.Services.Interfaces;

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

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

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;
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

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

builder.Services.AddScoped<IBlobsManagement, BlobsManagement>();
Enter fullscreen mode Exit fullscreen mode

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

namespace ImageProcessing.Services.Interfaces;

public interface IAzureServiceBus
{
    Task SendMessageAsync<T>(T serviceBusMessage, string queueName, string connectionString);
}
Enter fullscreen mode Exit fullscreen mode

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

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;
        }

    }
}
Enter fullscreen mode Exit fullscreen mode

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

builder.Services.AddScoped<IAzureServiceBus, AzureServiceBus>();
Enter fullscreen mode Exit fullscreen mode

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

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; }
}
Enter fullscreen mode Exit fullscreen mode

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

"ServiceBus" : {
    "ImagesBus": " "
  },
  "Storage": {
    "ImagesConnection": ""
  },
Enter fullscreen mode Exit fullscreen mode

Create Images Controller

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);
    }
}
Enter fullscreen mode Exit fullscreen mode

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 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

dotnet add package RestSharp 
dotnet add package SixLabors.ImageSharp 
dotnet add package Azure.Storage.Blobs 
dotnet add package Newtonsoft.Json 
Enter fullscreen mode Exit fullscreen mode

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

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}");
                }
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

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

Thank you for reading

Discussion (0)