Azure

Asynchronously upload multiple photos to Azure blob container

In this post we are going to see how to asynchronously upload photos to an Azure blob container while at the same time, displaying the upload status for each photo. If you aren’t familiar with Azure Storage Services or even worst with Azure itself, that’s OK. It really worth to take a look at those amazing services Azure Cloud offers. You do not need to have a valid or a trial subcription to Azure cloud to follow this example, since Microsoft offers the Azure Storage Emulator so developers can work locally to their machines before move to the cloud. Azure storage emulator is exactly what it’s name implies: A tool that emulates Table, Queue and Blob Azure storage services. It’s included inside the Windows Azure SDK for .NET which you can download and install from here. Following are the tools, SDKs and toolkits we will use for the following example:

  1. Windows Azure SDK for .NET
  2. Ajax Control Toolkit for AjaxFileUpload control
  3. Fancybox that is a tool to diplay images (download from here)
  4. Azure Storage explorer that is a tool to diplay the contents of your storage Tables, Queues and Blob container (download from here)
  5. Image resizer class: You expect your users to upload photos of average size 2-3 MB. Hence, you need to reduce the original size before uploading.. Moreover, fancybox uses two photos: A thumbnail one for previewing and the original

Let’s start. Create an empty ASP.NET Web Form Application (I used Visual Studio 2013 and .NET 4.5.1) named ‘AzureUploadPhotosAsync’. First of all we need to setup the Ajax Control Toolkit in order to use the AjaxFileUpload control. This control allows you to asynchronously upload multiple images while diplaying their upload status. You can install it for free using the Manage NuGet Packages option:

azure-upload-photos-01

Now that you have it installed, you need to configure it in the web.config file as follow (if mine web.config doesn’t fit for your solution make the appropriate modifications)

<?xml version="1.0" encoding="utf-8"?>
<!--
  For more information on how to configure your ASP.NET application, please visit
  http://go.microsoft.com/fwlink/?LinkId=169433
  -->
<configuration>
  <appSettings>
    <add key="ValidationSettings:UnobtrusiveValidationMode" value="WebForms" />
    <add key="aspnet:UseTaskFriendlySynchronizationContext" value="true" />
  </appSettings>
  <system.web>
    <compilation debug="true" targetFramework="4.5.1" />
    <httpRuntime targetFramework="4.5.1" />
  <pages>
      <controls>
        <add tagPrefix="asp" namespace="System.Web.UI" assembly="System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
        <add tagPrefix="asp" namespace="System.Web.UI.WebControls" assembly="System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
        <add tagPrefix="ajaxToolkit" assembly="AjaxControlToolkit" namespace="AjaxControlToolkit" />
      </controls>
    </pages>
    <httpHandlers>
      <add verb="*" path="AjaxFileUploadHandler.axd" type="AjaxControlToolkit.AjaxFileUploadHandler, AjaxControlToolkit" />
      <add verb="*" path="CombineScriptsHandler.axd" type="AjaxControlToolkit.CombineScriptsHandler, AjaxControlToolkit" />
    </httpHandlers>
  </system.web>
  <system.webServer>
    <validation validateIntegratedModeConfiguration="false" />
    <handlers>
      <add name="AjaxFileUploadHandler" verb="*" path="AjaxFileUploadHandler.axd" type="AjaxControlToolkit.AjaxFileUploadHandler, AjaxControlToolkit" />
      <add name="CombineScriptsHandler" verb="*" path="CombineScriptsHandler.axd" type="AjaxControlToolkit.CombineScriptsHandler, AjaxControlToolkit" />
    </handlers>
  </system.webServer>
  <location path="AjaxFileUploadHandler.axd">
    <system.web>
      <authorization>
        <allow users="*" />
      </authorization>
    </system.web>
  </location>
</configuration>

Create a Web Form page named ‘UploadPhotos.aspx’ and set it as your start up page. Page the following code inside the page:

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="UploadPhotos.aspx.cs" Inherits="AzureUploadPhotosAsync.UploadPhotos" %>
<%@ Register Assembly="AjaxControlToolkit" Namespace="AjaxControlToolkit" TagPrefix="ajaxToolkit" %>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
    <script type="text/javascript">
        function ClientUploadComplete() {
            window.location.replace("MyPhotos.aspx?album=10");
        }
    </script>
</head>
<body>
    <form id="form1" runat="server">
        <ajaxToolkit:ToolkitScriptManager ID="sriptmanger" runat="server" CombineScripts="false" />
        <div style="width: 500px; margin: 0 auto;">
            <ajaxToolkit:AjaxFileUpload ID="AjaxFileUpload1" runat="server" AllowedFileTypes="jpg,jpeg,png,gif,tiff"
                MaximumNumberOfFiles="50" OnUploadComplete="File_Upload" OnClientUploadCompleteAll="ClientUploadComplete" ForeColor="Red" />
        </div>
    </form>
</body>
</html>

The only declaration I’m gonna explain for the moment is the javascript function ClientUploadComplete we bound on the OnClientUploadCompleteAll event of the AjaxFileUpload control: This function will be invoked as soon as all the photos have been uploaded to the blob container. The function will redirect us to another page (which we will create a little bit later) passing an extra fiction parameter albumID in the query string. The page will read the albumID value, load and display all of it’s photos using the fancybox tool.

Before moving to the code behind file for this page we need to build an Azure blob client that is capable to store blobs (photos in our case) to the storage emulator. If you want to learn more about Blob storage visit this page. In a nutchel, what we are going to do here is to create a blob container named azurephotos where the client we will build, is going to store our blobs (images). One thing I love about blob storage is it’s hierarchy naming capabilities. Let us pause for a moment: Have you ever notice facebook photos URIs? Here is an example of my favourite artist Armin Van Buuren’s page.

<fb:post fb-iframe-plugin-query="app_id=249643311490&container_width=0&href=https%3A%2F%2Fwww.facebook.com%2Farminvanbuuren%2Fphotos%2Fpb.25498833315.-2207520000.1409941889.%2F10152338464483316%2F%3Ftype%3D3%26theater&locale=en_US&sdk=joey" fb-xfbml-state="rendered" class=" fb_iframe_widget" href="https://www.facebook.com/arminvanbuuren/photos/pb.25498833315.-2207520000.1409941889./10152338464483316/?type=3&theater"><span style="vertical-align: bottom; width: 552px; height: 534px;"><iframe class="" src="http://www.facebook.com/v2.3/plugins/post.php?app_id=249643311490&channel=http%3A%2F%2Fstatic.ak.facebook.com%2Fconnect%2Fxd_arbiter%2FKTWTb9MY5lw.js%3Fversion%3D41%23cb%3Df3cd1fcfcb935a%26domain%3Dchsakell.com%26origin%3Dhttp%253A%252F%252Fchsakell.com%252Ff2e361ce2e37342%26relation%3Dparent.parent&container_width=0&href=https%3A%2F%2Fwww.facebook.com%2Farminvanbuuren%2Fphotos%2Fpb.25498833315.-2207520000.1409941889.%2F10152338464483316%2F%3Ftype%3D3%26theater&locale=en_US&sdk=joey" style="border: medium none; visibility: visible; width: 552px; height: 534px;" title="fb:post Facebook Social Plugin" scrolling="no" allowfullscreen="true" allowtransparency="true" name="f1f912f611038e8" frameborder="0" height="1000px" width="1000px"></iframe></span></fb:post>

Take a look at it’s uri. Using this pattern, programmatically you could load/list all armin’s photos using something like ‘www.facebook.com/arminvanbuuren/photos/’. If you wanted a specific photos you would add a specific value after the /photos/. I am sure MVC developers are really familiar with this kind of routing technique. We are going to use a similar pattern in our blob container cause Blob storage services can list blob using this kind of patterns. Let me explain further. For each photo we upload, we will reduce it’s size (using an image resizer class), we will create a thumbnail and store the following two images in the following patterns:

HQ images

http://127.0.0.1:10000/devstoreaccount1/azurephotos/hq/username/album/{albumID}/{uniqueidentifier}.fileformatextention

Thumbnail images

http://127.0.0.1:10000/devstoreaccount1/azurephotos/thumbnail/username/album/{albumID}/{uniqueidentifier}.fileformatextention

In this way, in case we wanted to retrieve all HQ images for a user with username “chsakell” we would use a function listblobs with parameter “http://127.0.0.1:10000/devstoreaccount1/azurephotos/hq/username. In case we wanted to get all thumbnails for albumID=10 we would pass the parameter “http://127.0.0.1:10000/devstoreaccount1/azurephotos/thumbnail/username/album/10 and so on..

azure-upload-photos-00

NOTE: http://127.0.0.1:10000/devstoreaccount1 is the emulator’s URI and ‘azurephotos’ is the blob’s container name we will create for storing our photos. As far the unique identifier for each image it’s a usual technice to keep file names private from public users. For each image we create a Guid value and store this value in an SQL Server database table with all relative photo information such as title, length, file upload format, user uploaded, album etc.. In my solution I created a localdb database named AzureDB and created the respective connection string in the web.config file. Use the following SQL code to create a table that will store the information needed for each photo uploaded:

CREATE TABLE [dbo].[AzurePhotos] (
    [PhotoId]           INT              IDENTITY (1, 1) NOT NULL,
    [PhotoUser]         NVARCHAR (50)    NOT NULL,
    [PhotoTitle]        NVARCHAR (100)   NOT NULL,
    [PhotoGuid]         UNIQUEIDENTIFIER NOT NULL,
    [PhotoUploadFormat] NVARCHAR (10)    NOT NULL,
    [PhotoByteLength]   INT              NOT NULL,
    [PhotoAlbum]        INT              NOT NULL,
    [PhotoDateUploaded] DATETIME         DEFAULT (getdate()) NULL,
    PRIMARY KEY CLUSTERED ([PhotoId] ASC)
);

azure-upload-photos-02

I have also configured the Azure Storage Emulator. All that said here is the final Web.config file:

<?xml version="1.0" encoding="utf-8"?>
<!--
  For more information on how to configure your ASP.NET application, please visit
  http://go.microsoft.com/fwlink/?LinkId=169433
  -->
<configuration>
  <connectionStrings>
    <add name="AzureDB" connectionString="Data Source=(LocalDB)\v11.0;AttachDbFilename=|DataDirectory|\AzureDB.mdf;Integrated Security=True" providerName="System.Data.SqlClient"/>
  </connectionStrings>
  <appSettings>
    <add key="ValidationSettings:UnobtrusiveValidationMode" value="WebForms" />
    <add key="aspnet:UseTaskFriendlySynchronizationContext" value="true" />
    <add key="devstorage" value="DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==" />
  </appSettings>
  <system.web>
    <compilation debug="true" targetFramework="4.5.1" />
    <httpRuntime targetFramework="4.5.1" />
  <pages>
      <controls>
        <add tagPrefix="asp" namespace="System.Web.UI" assembly="System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
        <add tagPrefix="asp" namespace="System.Web.UI.WebControls" assembly="System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
        <add tagPrefix="ajaxToolkit" assembly="AjaxControlToolkit" namespace="AjaxControlToolkit" />
      </controls>
    </pages>
    <httpHandlers>
      <add verb="*" path="AjaxFileUploadHandler.axd" type="AjaxControlToolkit.AjaxFileUploadHandler, AjaxControlToolkit" />
      <add verb="*" path="CombineScriptsHandler.axd" type="AjaxControlToolkit.CombineScriptsHandler, AjaxControlToolkit" />
    </httpHandlers>
  </system.web>
  <system.webServer>
    <validation validateIntegratedModeConfiguration="false" />
    <handlers>
      <add name="AjaxFileUploadHandler" verb="*" path="AjaxFileUploadHandler.axd" type="AjaxControlToolkit.AjaxFileUploadHandler, AjaxControlToolkit" />
      <add name="CombineScriptsHandler" verb="*" path="CombineScriptsHandler.axd" type="AjaxControlToolkit.CombineScriptsHandler, AjaxControlToolkit" />
    </handlers>
  </system.webServer>
  <location path="AjaxFileUploadHandler.axd">
    <system.web>
      <authorization>
        <allow users="*" />
      </authorization>
    </system.web>
  </location>
</configuration>

Let’s keep up. Create a folder named Utilies and add the following C# classes:

AzurePhotoClient.cs

using Microsoft.WindowsAzure;
using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Blob;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Web;
namespace AzureUploadPhotosAsync.Utilities
{
    public class AzurePhotoClient
    {
        private CloudBlobContainer BlobContainer;
        CloudStorageAccount storageAccount;
        public AzurePhotoClient(string container)
        {
            // Retrieve storage account from connection string.
            // Emulator initialization
            storageAccount = CloudStorageAccount.DevelopmentStorageAccount;
            // Create the blob client.
            CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient();
            // Retrieve reference to a specific created container.
            BlobContainer = blobClient.GetContainerReference(container);
            if (!BlobContainer.Exists())
            {
                // create the container and set public access to the blobs
                BlobContainer.Create();
                BlobContainerPermissions permissions = new BlobContainerPermissions()
                {
                    PublicAccess = BlobContainerPublicAccessType.Blob
                };
                BlobContainer.SetPermissions(permissions);
            }
        }
        public string UploadFromStream(FileStream fileStream, string fileName)
        {
            // Create the container and blob.
            CloudBlockBlob blob = BlobContainer.GetBlockBlobReference(fileName);
            // Set the content type to image
            blob.Properties.ContentType = "image/" + Path.GetExtension(fileName).Replace(".", "");
            blob.UploadFromStream(fileStream);
            // Return a URI fro viewing the photo
            return blob.Uri.AbsoluteUri;
        }
        public string UploadFromBytes(byte[] fileBytes, string fileName)
        {
            // Create the container and blob.
            CloudBlockBlob blob = BlobContainer.GetBlockBlobReference(fileName);
            // Set the content type to image
            blob.Properties.ContentType = "image/" + Path.GetExtension(fileName).Replace(".", "");
            blob.UploadFromByteArray(fileBytes, 0, fileBytes.Length - 1);
            // Return a URI fro viewing the photo
            return blob.Uri.AbsoluteUri;
        }
    }
}

DbAccess.cs

using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.IO;
using System.Linq;
using System.Web;
namespace AzureUploadPhotosAsync.Utilities
{
    public class DbAccess
    {
        public void InsertPhotoToDb(int userID, string filename, Guid guid, int length, int albumID)
        {
            using (SqlConnection connection = new SqlConnection(Utilities.Params.GetConnectionString()))
            {
                // Insert the new record
                using (SqlCommand command = connection.CreateCommand())
                {
                    command.CommandText = @"INSERT INTO AzurePhotos ([PhotoUser],[PhotoTitle],[PhotoGuid],[PhotoUploadFormat],[PhotoByteLength],[PhotoAlbum])
                            VALUES (@UserId, @PhotoTitle, @PhotoGuid,@PhotoUploadFormat ,@PhotoByteLength,@PhotoAlbum)";
                    command.Parameters.AddWithValue("@UserId", userID);
                    command.Parameters.AddWithValue("@PhotoTitle", filename);
                    command.Parameters.AddWithValue("@PhotoGuid", guid);
                    command.Parameters.AddWithValue("@PhotoUploadFormat", Path.GetExtension(filename));
                    command.Parameters.AddWithValue("@PhotoByteLength", length);
                    command.Parameters.AddWithValue("@PhotoAlbum", albumID);
                    connection.Open();
                    command.ExecuteNonQuery();
                }
            }
        }
    }
}

I found the following ImageResizer class on the internet. I modified a little bit and used it to file resize the orinal photo and create a thumbnail 200×200. (Credits for this goes to it’s original creator)

ImageResizer.cs

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Web;
namespace AzureUploadPhotosAsync.Utilities
{
    public class ImageResizer
    {
        private int allowedFileSizeInByte;
        private string sourcePath;
        private string destinationPath;
        private Stream fileStream;
        private string fileExtention;
        public ImageResizer(int allowedSize, string sourcePath, string destinationPath)
        {
            allowedFileSizeInByte = allowedSize;
            this.sourcePath = sourcePath;
            this.destinationPath = destinationPath;
        }
        public ImageResizer(int allowedSize, Stream fs, string extention)
        {
            allowedFileSizeInByte = allowedSize;
            this.fileStream = fs;
            this.fileExtention = extention;
        }
        public void ScaleImage()
        {
            using (MemoryStream ms = new MemoryStream())
            {
                using (FileStream fs = new FileStream(sourcePath, FileMode.Open))
                {
                    Bitmap bmp = (Bitmap)Image.FromStream(fs);
                    SaveTemporary(bmp, ms, 100);
                    while (ms.Length < 0.9 * allowedFileSizeInByte || ms.Length > allowedFileSizeInByte)
                    {
                        double scale = Math.Sqrt((double)allowedFileSizeInByte / (double)ms.Length);
                        ms.SetLength(0);
                        bmp = ScaleImage(bmp, scale);
                        SaveTemporary(bmp, ms, 100);
                    }
                    if (bmp != null)
                        bmp.Dispose();
                    SaveImageToFile(ms);
                }
            }
        }
        public byte[] ScaleImageFromStream(out byte[] imageThumbnailBytes, int width, int height)
        {
            using (MemoryStream ms = new MemoryStream())
            {
                using (this.fileStream)
                {
                    Bitmap bmp = (Bitmap)Image.FromStream(this.fileStream);
                    // Create cropped Thumbnail
                    int x = bmp.Width / 2 - width / 2;
                    int y = bmp.Height / 2 - height / 2;
                    Bitmap imageThumbnail = (Bitmap)CreateThumbnail(bmp, new Size(200, 200));
                    imageThumbnailBytes = ImageToByte(imageThumbnail);
                    // End thumnail
                    SaveTemporary(bmp, ms, 100, this.fileExtention);
                    while (ms.Length < 0.9 * allowedFileSizeInByte || ms.Length > allowedFileSizeInByte)
                    {
                        double scale = Math.Sqrt((double)allowedFileSizeInByte / (double)ms.Length);
                        ms.SetLength(0);
                        bmp = ScaleImage(bmp, scale);
                        SaveTemporary(bmp, ms, 100, this.fileExtention);
                    }
                    if (bmp != null)
                        bmp.Dispose();
                    //SaveImageToFile(ms);
                    return ms.ToArray();
                }
            }
        }
        private void SaveImageToFile(MemoryStream ms)
        {
            byte[] data = ms.ToArray();
            using (FileStream fs = new FileStream(destinationPath, FileMode.Create))
            {
                fs.Write(data, 0, data.Length);
            }
        }
        private void SaveTemporary(Bitmap bmp, MemoryStream ms, int quality)
        {
            EncoderParameter qualityParam = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, quality);
            var codec = GetImageCodecInfo();
            var encoderParams = new EncoderParameters(1);
            encoderParams.Param[0] = qualityParam;
            if (codec != null)
                bmp.Save(ms, codec, encoderParams);
            else
                bmp.Save(ms, GetImageFormat());
        }
        private void SaveTemporary(Bitmap bmp, MemoryStream ms, int quality, string extention)
        {
            EncoderParameter qualityParam = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, quality);
            var codec = GetImageCodecInfo(extention);
            var encoderParams = new EncoderParameters(1);
            encoderParams.Param[0] = qualityParam;
            if (codec != null)
                bmp.Save(ms, codec, encoderParams);
            else
                bmp.Save(ms, GetImageFormat());
        }
        public Bitmap ScaleImage(Bitmap image, double scale)
        {
            int newWidth = (int)(image.Width * scale);
            int newHeight = (int)(image.Height * scale);
            Bitmap result = new Bitmap(newWidth, newHeight, PixelFormat.Format24bppRgb);
            result.SetResolution(image.HorizontalResolution, image.VerticalResolution);
            using (Graphics g = Graphics.FromImage(result))
            {
                g.InterpolationMode = InterpolationMode.HighQualityBicubic;
                g.CompositingQuality = CompositingQuality.HighQuality;
                g.SmoothingMode = SmoothingMode.HighQuality;
                g.PixelOffsetMode = PixelOffsetMode.HighQuality;
                g.DrawImage(image, 0, 0, result.Width, result.Height);
            }
            return result;
        }
        private ImageCodecInfo GetImageCodecInfo()
        {
            FileInfo fi = new FileInfo(sourcePath);
            switch (fi.Extension)
            {
                case ".bmp": return ImageCodecInfo.GetImageEncoders()[0];
                case ".jpg":
                case ".jpeg": return ImageCodecInfo.GetImageEncoders()[1];
                case ".gif": return ImageCodecInfo.GetImageEncoders()[2];
                case ".tiff": return ImageCodecInfo.GetImageEncoders()[3];
                case ".png": return ImageCodecInfo.GetImageEncoders()[4];
                default: return null;
            }
        }
        private ImageCodecInfo GetImageCodecInfo(string extention)
        {
            switch (extention)
            {
                case ".bmp": return ImageCodecInfo.GetImageEncoders()[0];
                case ".jpg":
                case ".jpeg": return ImageCodecInfo.GetImageEncoders()[1];
                case ".gif": return ImageCodecInfo.GetImageEncoders()[2];
                case ".tiff": return ImageCodecInfo.GetImageEncoders()[3];
                case ".png": return ImageCodecInfo.GetImageEncoders()[4];
                default: return null;
            }
        }
        private ImageFormat GetImageFormat()
        {
            FileInfo fi = new FileInfo(sourcePath);
            switch (fi.Extension)
            {
                case ".jpg": return ImageFormat.Jpeg;
                case ".bmp": return ImageFormat.Bmp;
                case ".gif": return ImageFormat.Gif;
                case ".png": return ImageFormat.Png;
                case ".tiff": return ImageFormat.Tiff;
                default: return ImageFormat.Png;
            }
        }
        public Bitmap CropBitmap(Bitmap bitmap, int cropX, int cropY, int cropWidth, int cropHeight)
        {
            Rectangle rect = new Rectangle(cropX, cropY, cropWidth, cropHeight);
            Bitmap cropped = bitmap.Clone(rect, bitmap.PixelFormat);
            return cropped;
        }
        public byte[] ImageToByte(Image img)
        {
            byte[] byteArray = new byte[0];
            using (MemoryStream stream = new MemoryStream())
            {
                img.Save(stream, System.Drawing.Imaging.ImageFormat.Png);
                stream.Close();
                byteArray = stream.ToArray();
            }
            return byteArray;
        }
        public System.Drawing.Image CreateThumbnail(System.Drawing.Image image, Size thumbnailSize)
        {
            float scalingRatio = CalculateScalingRatio(image.Size, thumbnailSize);
            int scaledWidth = (int)Math.Round((float)image.Size.Width * scalingRatio);
            int scaledHeight = (int)Math.Round((float)image.Size.Height * scalingRatio);
            int scaledLeft = (thumbnailSize.Width - scaledWidth) / 2;
            int scaledTop = (thumbnailSize.Height - scaledHeight) / 2;
            // For portrait mode, adjust the vertical top of the crop area so that we get more of the top area
            if (scaledWidth < scaledHeight && scaledHeight > thumbnailSize.Height)
            {
                scaledTop = (thumbnailSize.Height - scaledHeight) / 4;
            }
            Rectangle cropArea = new Rectangle(scaledLeft, scaledTop, scaledWidth, scaledHeight);
            System.Drawing.Image thumbnail = new Bitmap(thumbnailSize.Width, thumbnailSize.Height);
            using (Graphics thumbnailGraphics = Graphics.FromImage(thumbnail))
            {
                thumbnailGraphics.CompositingQuality = CompositingQuality.HighQuality;
                thumbnailGraphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
                thumbnailGraphics.SmoothingMode = SmoothingMode.HighQuality;
                thumbnailGraphics.DrawImage(image, cropArea);
            }
            return thumbnail;
        }
        private float CalculateScalingRatio(Size originalSize, Size targetSize)
        {
            float originalAspectRatio = (float)originalSize.Width / (float)originalSize.Height;
            float targetAspectRatio = (float)targetSize.Width / (float)targetSize.Height;
            float scalingRatio = 0;
            if (targetAspectRatio >= originalAspectRatio)
            {
                scalingRatio = (float)targetSize.Width / (float)originalSize.Width;
            }
            else
            {
                scalingRatio = (float)targetSize.Height / (float)originalSize.Height;
            }
            return scalingRatio;
        }
    }
}

Params.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace AzureUploadPhotosAsync.Utilities
{
    public class Params
    {
        public static string GetConnectionString()
        {
            return System.Configuration.ConfigurationManager.ConnectionStrings["AzureDB"].ToString();
        }
        public static string HqPhotosBaseURI = "http://127.0.0.1:10000/devstoreaccount1/azurephotos/hq/";
        public static string ThumbnailPhotosBaseURI = "http://127.0.0.1:10000/devstoreaccount1/azurephotos/thumbnail/";
    }
}

Now that we have done with setting up the environment we can switch to the code behind file for the ‘UploadPhotos’ page and paste the following code:

using AjaxControlToolkit;
using AzureUploadPhotosAsync.Utilities;
using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.IO;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace AzureUploadPhotosAsync
{
    public partial class UploadPhotos : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
        }
        protected void File_Upload(object sender, AjaxFileUploadEventArgs e)
        {
            string filename = e.FileName;
            int userID = 1; // Foreign key to Table Users..
            int albumID = 10; // Foreign key to Table Albums..
            string username = "chsakell"; // USE Context.User.Identity.Name for authenticated users..
            try
            {
                // Resize image before upload | Create thumbnail for previews..
                Stream fileStream = e.GetStreamContents();
                ImageResizer imRes = new ImageResizer(307200, fileStream, Path.GetExtension(filename));
                byte[] imageThumbnail;
                byte[] scaledUploadFile = imRes.ScaleImageFromStream(out imageThumbnail, 200, 200);
                // Generate a Guid for public file name..
                Guid guid = Guid.NewGuid();
                AzurePhotoClient blobClient = new AzurePhotoClient("azurephotos");
                blobClient.UploadFromBytes(scaledUploadFile, "hq/" + username + "/album/" + albumID + "/" + guid + Path.GetExtension(filename));
                blobClient.UploadFromBytes(imageThumbnail, "thumbnail/" + username + "/album/" + albumID + "/" + guid + Path.GetExtension(filename));
                // Log photo to DB..
                DbAccess dbAccess = new DbAccess();
                dbAccess.InsertPhotoToDb(userID, filename, guid, scaledUploadFile.Length, albumID);
                e.DeleteTemporaryData();
            }
            catch (Exception ex)
            {
                // Log error..
            }
        }
    }
}

We assummed that a user with userID=1 has logged in in our website and wants to upload photos for an album with albumID=10. The code, resizes the original image, creates a thumnail, a GUID for that image, uploads the photos in our blob container and then stores the info in the localdb AzureDB database. We are ready to build and run our solution but before doing so, make sure you:

  1. Start storage Emulator: Make sure you don’t run any torrent service (ask Microsoft for that..)
  2. Start Azure Storage Explorer: You will not be able to see Emulator’s content unless a service named DSService.exe is running (Also ask Microsoft for that..) For this I simply created a console application named ‘DSSService.exe’ and just fired it

At this time you should be able to upload your photos..

azure-upload-photos-04

Let’s continue with displaying the photos in an elegant way: Will will use a tool name fancybox for this and a simple DataList control inside a new Web Form page named ‘MyPhotos.aspx’.

NOTE: Don’t bother figure out how fancy box works.. You can download the project we built from the link you will find at the bottom of the post (as always)

We will need a new model (class) for binding multiple instances to the DataList control. Create a Model folder and add the following class:

AzurePhoto.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace AzureUploadPhotosAsync.Models
{
    public class AzurePhoto
    {
        public string PhotoUser { get; set; }
        public string PhotoGuid { get; set; }
        public string PhotoUploadFormat { get; set; }
        public string PhotoTitle { get; set; }
        public int PhotoAlbum { get; set; }
        public string HqPhotoURI
        {
            get
            {
                return Utilities.Params.HqPhotosBaseURI + PhotoUser + "/album/" + PhotoAlbum + "/" + PhotoGuid + PhotoUploadFormat;
            }
        }
        public string ThumbnailPhotoURI
        {
            get
            {
                return Utilities.Params.ThumbnailPhotosBaseURI + PhotoUser + "/album/" + PhotoAlbum + "/" + PhotoGuid + PhotoUploadFormat;
            }
        }
        public AzurePhoto(string guid, string uploadFormat, string title, string username, int album)
        {
            this.PhotoGuid = guid;
            this.PhotoUploadFormat = uploadFormat;
            this.PhotoTitle = title;
            this.PhotoUser = username;
            this.PhotoAlbum = album;
        }
    }
}

Here’s the MyPhotos.aspx page code:

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="MyPhotos.aspx.cs" Inherits="AzureUploadPhotosAsync.MyPhotos" %>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
    <link href="Content/css/styles.css" rel="stylesheet" />
    <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
    <!-- Add jQuery library -->
    <%--<script type="text/javascript" src="lib/jquery-1.10.1.min.js"></script>--%>
    <!-- Add mousewheel plugin (this is optional) -->
    <script type="text/javascript" src="Content/fancybox/lib/jquery.mousewheel-3.0.6.pack.js"></script>
    <!-- Add fancyBox main JS and CSS files -->
    <script type="text/javascript" src="Content/fancybox/source/jquery.fancybox.js?v=2.1.5"></script>
    <link rel="stylesheet" type="text/css" href="Content/fancybox/source/jquery.fancybox.css?v=2.1.5" media="screen" />
    <!-- Add Button helper (this is optional) -->
    <link rel="stylesheet" type="text/css" href="Content/fancybox/source/helpers/jquery.fancybox-buttons.css?v=1.0.5" />
    <script type="text/javascript" src="Content/fancybox/source/helpers/jquery.fancybox-buttons.js?v=1.0.5"></script>
    <!-- Add Thumbnail helper (this is optional) -->
    <link rel="stylesheet" type="text/css" href="Content/fancybox/source/helpers/jquery.fancybox-thumbs.css?v=1.0.7" />
    <script type="text/javascript" src="Content/fancybox/source/helpers/jquery.fancybox-thumbs.js?v=1.0.7"></script>
    <!-- Add Media helper (this is optional) -->
    <script type="text/javascript" src="Content/fancybox/source/helpers/jquery.fancybox-media.js?v=1.0.6"></script>
    <script type="text/javascript">
        $(document).ready(function () {
            /*
			 *  Simple image gallery. Uses default settings
			 */
            $('.fancybox').fancybox();
            /*
			 *  Different effects
			 */
            // Change title type, overlay closing speed
            $(".fancybox-effects-a").fancybox({
                helpers: {
                    title: {
                        type: 'outside'
                    },
                    overlay: {
                        speedOut: 0
                    }
                }
            });
            // Disable opening and closing animations, change title type
            $(".fancybox-effects-b").fancybox({
                openEffect: 'none',
                closeEffect: 'none',
                helpers: {
                    title: {
                        type: 'over'
                    }
                }
            });
            // Set custom style, close if clicked, change title type and overlay color
            $(".fancybox-effects-c").fancybox({
                wrapCSS: 'fancybox-custom',
                closeClick: true,
                openEffect: 'none',
                helpers: {
                    title: {
                        type: 'inside'
                    },
                    overlay: {
                        css: {
                            'background': 'rgba(238,238,238,0.85)'
                        }
                    }
                }
            });
            // Remove padding, set opening and closing animations, close if clicked and disable overlay
            $(".fancybox-effects-d").fancybox({
                padding: 0,
                openEffect: 'elastic',
                openSpeed: 150,
                closeEffect: 'elastic',
                closeSpeed: 150,
                closeClick: true,
                helpers: {
                    overlay: null
                }
            });
            /*
			 *  Button helper. Disable animations, hide close button, change title type and content
			 */
            $('.fancybox-buttons').fancybox({
                openEffect: 'none',
                closeEffect: 'none',
                prevEffect: 'none',
                nextEffect: 'none',
                closeBtn: false,
                helpers: {
                    title: {
                        type: 'inside'
                    },
                    buttons: {}
                },
                afterLoad: function () {
                    this.title = 'Image ' + (this.index + 1) + ' of ' + this.group.length + (this.title ? ' - ' + this.title : '');
                }
            });
            /*
			 *  Thumbnail helper. Disable animations, hide close button, arrows and slide to next gallery item if clicked
			 */
            $('.fancybox-thumbs').fancybox({
                prevEffect: 'none',
                nextEffect: 'none',
                closeBtn: false,
                arrows: false,
                nextClick: true,
                helpers: {
                    thumbs: {
                        width: 50,
                        height: 50
                    }
                }
            });
            /*
			 *  Media helper. Group items, disable animations, hide arrows, enable media and button helpers.
			*/
            $('.fancybox-media')
				.attr('rel', 'media-gallery')
				.fancybox({
				    openEffect: 'none',
				    closeEffect: 'none',
				    prevEffect: 'none',
				    nextEffect: 'none',
				    arrows: false,
				    helpers: {
				        media: {},
				        buttons: {}
				    }
				});
        });
	</script>
</head>
<body>
    <form id="form1" runat="server">
        <div>
            <asp:DataList ID="dlImages" runat="server" RepeatLayout="Table" RepeatColumns="3"
                CellPadding="2" CellSpacing="20">
                <ItemTemplate>
                    <table class="item" cellpadding="0" cellspacing="0" border="0">
                        <tr>
                            <td align="center" class="header">
                                <span class="name">
                                    <%# Eval("PhotoTitle") %></span>
                            </td>
                        </tr>
                        <tr>
                            <td align="center" class="body">
                                <a class="fancybox" href='<%# Eval("HqPhotoURI") %>' data-fancybox-group="gallery" title='<%# Eval("PhotoTitle") %>' style="display: initial !important">
                                    <img class="image" src='<%# Eval("ThumbnailPhotoURI") %>' />
                                </a>
                            </td>
                        </tr>
                    </table>
                </ItemTemplate>
            </asp:DataList>
        </div>
    </form>
</body>
</html>

..and here’s it’s code behind file which retrieves and creates AzurePhoto instances from database to the DataList control:

using AzureUploadPhotosAsync.Models;
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace AzureUploadPhotosAsync
{
    public partial class MyPhotos : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            try
            {
                string username = "chsakell"; // USE Context.User.Identity.Name for authenticated users..
                int albumID = 10;// Int32.Parse(Request.QueryString["AblumID"].ToString());
                using (SqlConnection conn = new SqlConnection(Utilities.Params.GetConnectionString()))
                {
                    string query = @"SELECT * FROM AzurePhotos WHERE PhotoAlbum = @albumID";
                    using (SqlCommand cmd = new SqlCommand(query, conn))
                    {
                        cmd.CommandType = CommandType.Text;
                        cmd.Parameters.Add("@albumID", SqlDbType.Int).Value = albumID;
                        using(SqlDataAdapter da = new SqlDataAdapter(cmd))
                        {
                            DataTable dt = new DataTable("Photos");
                            List<AzurePhoto> albumPhotos = new List<AzurePhoto>();
                            da.Fill(dt);
                            foreach(DataRow row in dt.Rows)
                            {
                                AzurePhoto photo = new AzurePhoto(row["PhotoGuid"].ToString(), row["PhotoUploadFormat"].ToString(), row["PhotoTitle"].ToString(),username, Int32.Parse(row["PhotoAlbum"].ToString()));
                                albumPhotos.Add(photo);
                            }
                            dlImages.DataSource = albumPhotos;
                            dlImages.DataBind();
                        }
                    }
                }
            }
            catch (Exception ex)
            {
            }
        }
    }
}

I believe it’s time for a full demonstration…

azure-upload-async

I understand it maybe was a difficult post to follow, especially if you weren’t familiar with Azure Storage services or Azure Storage Emulator. I hope though, by downloading the solution you will make it work to your machine too. I am big fan of Azure and this is why I will keep posting more for this category in my blog. You can download the project we built from here.

Christos Sakellarios

Senior Software Engineer, Blogger

Related Articles

Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Back to top button