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
Create web api
dotnet new webapi -n "ImageProcessing"
Install Packages
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
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
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
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
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
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
builder.Services.AddScoped<IAzureServiceBus, AzureServiceBus>();
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; }
}
Now we need to add the configurations for ServiceBus and Blob inside our appsettings.json
"ServiceBus" : {
"ImagesBus": " "
},
"Storage": {
"ImagesConnection": ""
},
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);
}
}
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
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}");
}
}
}
}
}
Please note this is a prototype of how we approach a service base oriented functionality. Please add your questions below.
Thank you for reading
Top comments (1)
Mohamad, thank you for the full example and provided code, very useful.