Search code examples
azureazure-storageihttphandler

How to return images from azure storage account using IHttpHandler on ASP.NET MVC


I have an IHttpHandler that intercepts the image requests and responses if users has right to see the image or not via some database driven algorithm. Anyway, the code was working just fine until i moved my images to storage account. Now the image requests takes more time compared to the time i was serving from local disk of the server. So I wonder if I have done anything wrong on my IHttpHandler codes, that would be the couse of this.

I request images like:

<img src="/web-thumb/{userid}/{filename}.jpg">

I have created a route definition like:

    routes.Add("ThumbnailsRoute",
        new Route("web-thumb/{userid}/{filename}", new ThumbnailRouteHandler()));

and here is my thumbnail handler:

public class ThumbnailRouteHandler : IRouteHandler
{
    public IHttpHandler GetHttpHandler(RequestContext requestContext)
    {
        return new ThumbnailHandler(requestContext);
    }
}        


public class ThumbnailHandler : IHttpHandler
{
    public ThumbnailHandler()
    {
    }

    public ThumbnailHandler(RequestContext requestContext)
    {
        RequestContext = requestContext;
    }

    protected RequestContext RequestContext { get; set; }

    public bool IsReusable
    {
        get { return false; }
    }

    public void ProcessRequest(HttpContext context)
    {
        // find physical path to image here.  

        var binResponse = Utility.GetFileContent("images/" + RequestContext.RouteData.Values["userid"] + "/thumbnail/" + RequestContext.RouteData.Values["filename"]);

        context.Response.BinaryWrite(binResponse);

        context.Response.End();
    }
}

and here is the GetFileContent helper static function downloading image from storage to a byte array:

    public static byte[] GetFileContent(string fileName)
    {
        CloudBlobContainer container = getUserMediaContainer();
        CloudBlockBlob blockBlob = container.GetBlockBlobReference(fileName);
        if (blockBlob.Exists()) { 
            blockBlob.FetchAttributes();
            long fileByteLength = blockBlob.Properties.Length;
            byte[] fileContent = new byte[fileByteLength];
            for (int i = 0; i < fileByteLength; i++)
            {
                fileContent[i] = 0x20;
            }
            blockBlob.DownloadToByteArray(fileContent, 0);
            return fileContent;
        }
        return new byte[0];
    }
    private static CloudBlobContainer getUserMediaContainer()
    {
        CloudStorageAccount storageAccount = CloudStorageAccount.Parse(System.Configuration.ConfigurationManager.ConnectionStrings["StorageConn"].ConnectionString);
        CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient();

        CloudBlobContainer userMedia = blobClient.GetContainerReference("user-media");
        userMedia.CreateIfNotExists();

        return userMedia;
    }

Here is the request time breakdown of 35kb image request from both ends: enter image description here


Solution

  • Your GetFileContent function is creating a connection to storage and checking the container exists on every call, I would instead keep a reference to the CloudStorageAccount and just check the container exists when your application starts. I am assuming there is no reason the container will be deleted while your application is running.

    For example

    public static class Utility
    {
        // Keep ref to CloudStorageAccount
        private static CloudStorageAccount _storageAccount;
    
        private static CloudStorageAccount StorageAccount
        {
            get
            {
                if (_storageAccount == null)
                {
                    _storageAccount = CloudStorageAccount.Parse(System.Configuration.ConfigurationManager.ConnectionStrings["StorageConn"].ConnectionString);
                }
                return _storageAccount;
            }
        }
    
        static Utility()
        {
            // Just check the container exists when app starts or on first download
            CloudBlobClient blobClient = StorageAccount.CreateCloudBlobClient();
            CloudBlobContainer userMedia = blobClient.GetContainerReference("user-media");
            userMedia.CreateIfNotExists();
        }
    
        public static byte[] GetFileContent(string fileName)
        {
            CloudBlobClient blobClient = StorageAccount.CreateCloudBlobClient();
            CloudBlobContainer container = blobClient.GetContainerReference("user-media");
            CloudBlockBlob blockBlob = container.GetBlockBlobReference(fileName);
            if (blockBlob.Exists())
            {
                blockBlob.FetchAttributes();
                long fileByteLength = blockBlob.Properties.Length;
                byte[] fileContent = new byte[fileByteLength];
                blockBlob.DownloadToByteArray(fileContent, 0);
                return fileContent;
            }
            return new byte[0];
        }
    } 
    

    You haven't said what the magnitude of difference is in the download times but I assume you are not expecting to download from blob storage instead of from the local server without some change performance.