Monday 20 February 2017

Creating a photo album for ASP.NET MVC 5 Users using Azure BLOB storage

 In this blog post, we will integrate ASP.NET MVC with Azure BLOB Storage by saving an image for an ASP.NET Identity 2.0 user in Azure BLOB Storage. We’re going to cover a lot of ground in this post, including:
  • Using ASP.NET Identity 2.0 user as reference in other table.
  • Creating a one to many relationship with ASP.NET Identity 2.0 user table
  • Connecting an ASP.NET MVC application to Azure Storage
  • Creating or Uploading a BLOB
  • Deleting a BLOB
  • Downloading a BLOB
  • Uploading a file from MVC form to a Azure BLOB
The final output of the blog post we plan on creating in this post will look more or less like the image below. As you can see, a user can upload a photo to her album, delete a photo, and of course, view photos too.
Architecture of the Application
The High-level architecture of the application can be drawn as shown in the image below:
Creating tables to save images
When we create an ASP.NET MVC application using the given MVC template in Visual Studio, by default, a basic authentication gets created using the ASP.NET Identity 2.0. In a default MVC project, a template for the authentication and authorization purpose ASP.NET Identity 2.0 creates the tables as shown in the image below:
The user’s information gets stored in the AspNetUsers table. Let us have a look into this table here:
For AspNetUsers, the table Id is the primary key. Our objective is to save various photos of the user. To maintain the user photos, let us create a table. In case you are not aware, ASP.NET Identity 2.0 creates tables using the Entity Framework Code First approach. So we are going to follow the same to create a table which will save information about user photos.
To create the table, right click on the Model folder, and add a class. Give the name UserImage to the created class.
UserImage.cs
public class UserImage
    {
        public string Id { get; set; }

        public string ImageUrl { get; set; }

        public string UserId { get; set; }

        public virtual ApplicationUser ApplicationUser { get; set; }

    }
Before we move ahead, let’s understand the relationship between AspNetUsers table and the UserImages table, which can be defined as below:
  • One user can have multiple photos
  • There would be no photo without any user association
  • AspNetUsers table and the UserImages table are in a one to many relationship.
  • AspNetUsers table is the primary table and UserImages table is the secondary table.
  • User id is a foreign key in the UserImages table.
 As we know, ASP.NET Identity 2.0 uses the Entity Framework Code First approach, hence we are creating a relationship between entities using the code first approach.  As you notice in code snippet above, for navigation properties, we are creating a virtual property of ApplicationUser class in UserImage class. Keep in mind that the ApplicationUser class is the model class of AspNetUsers table. We have created the relationship in UserImage class. Next, we need to create a relationship in the ApplicationUser class. You can find the ApplicationUser class inside Model/IndetityModel.cs
Do not delete any existing code in ApplicationUser class, and add the following codes. Below code is creating a one to many relationship between ApplicationUser and UserImage:
public ApplicationUser()
        {
            UserImages = new HashSet(); } public ICollection UserImages { get; set; } 
Last but not least to create a UserImages table, add the following line of code in the ApplicationDbContext class. You will find ApplicationDbContext class in the same Model/IndetityModel.cs
  public DbSet UserImages { get; set; } 
The last step left is to update the database with new table UserImages and create a relationship in AspNetUsers table. Since we are following the code first approach, migration should be a piece of cake!  From menu, select   View->Other Windows -> Manage -> Package Manager Console
On the Package Manger Console, run the following command.

PM> enable-migrations
PM> add-migration v1
PM> update-database
 After successful execution of these commands, the database will be updated with a new table. New table structure would be as given in the image below. As you see, UserId is a foreign key in the UserImages table.
Since we have not changed the connection string, the database would be created inside the default local database server. However, you can change the connection string pointing to any database server and update the connecting string in the ApplicationDbContext class.

Creating a helper class to upload image in Azure BLOB
We will use the Azure Storage Library created by the Azure team to perform operations on Azure BLOBS from the MVC Application. To work with this, we need to add the WindowsAzure.Storage NuGet package in the project. Right click on the project, and click on Manage NuGet Package. In the package manager, search WinowsAzure.Storage and install that in the project.
We are going to create a utility class to perform the following tasks in Azure BLOB storage.
  1. Upload an image to a BLOB
  2. Download an image
  3. Delete an image and a BLOB
In this example, I am going to hard code the container name. In case you are new to Azure BLOB storage, you upload an image as a BLOB inside a container. We are not going to create a container, but will use an already-created container from the portal.
Before we move ahead, let us do the following tasks
  1. Login to Azure Portal
  2. Click on New -> Data+ Storage -> Storage Account to create a new Storage Account
  3. Once the storage account is created, select that from all resources.
  4. Click on BLOB Services and then container. From the top click on + to create a container.
I am going to use already created container named jsr . You can find list of containers for your storage service, as shown in the image below:
To connect with Azure Storage from .NET code, we need a connection string or Keys to the Azure Storage. You can find connection string and Keys in the portal, by clicking on the Settings->Keys in the Azure Storage Service as shown in the image below:
We will need a Primary Connection String in the code to connect with Azure Storage.
Great, let us now go ahead and create a class. I am naming the class as BlobUtility and putting it inside a folder called Utilities.
In the constructor of BlobUtility class, we are creating a connecting string to connect with the Azure Storage. To create the connection string, we need to pass two parameters:
  1. Storage account name
  2. Storage account key
We have already noted the account name and account key from the portal. We will create instance of BlobUtility class in the controller class and pass the storage account name and key from there.
The listing below shows you how create a connection string for Azure Storage:
public class BlobUtility
    {
        public CloudStorageAccount storageAccount;
        public BlobUtility(string AccountName, string AccountKey)
        {
            string UserConnectionString = string.Format("DefaultEndpointsProtocol=https;AccountName={0};AccountKey={1}", AccountName, AccountKey);
            storageAccount = CloudStorageAccount.Parse(UserConnectionString);
        }
    }
Now we need functions to:
  1. Upload an image in a BLOB
  2. Delete an image or BLOB
  3. Download an image

To upload an image in BLOB, we will pass the name of the BLOB or image, the container name (which is JSR in this case), and the Image stream. We are going to upload the image as BLOCK. There are two ways a BLOB can be created:
  1. Page BLOB
  2. Block BLOB
We are going to create a Block BLOB. Also keep in mind that the container is a public container and there is no restriction on it. Below is the simplest possible way to upload an Image to a BLOB.
public CloudBlockBlob UploadBlob(string BlobName, string ContainerName, Stream stream)
        {
         
            CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient();
            CloudBlobContainer container = blobClient.GetContainerReference(ContainerName.ToLower());
            CloudBlockBlob blockBlob = container.GetBlockBlobReference(BlobName);
            // blockBlob.UploadFromByteArray()
            try
            {
                blockBlob.UploadFromStream(stream);
                return blockBlob;
            }
            catch (Exception e)
            {
                var r = e.Message;
                return null; 
            }


        }
In the above listing, we are performing the following tasks:
  • Creating an object on CloudBlobClient
  • Getting the reference of the container
  • Creating an object of block BLOB
  • Uploading an image stream in the block BLOB
The UploadBlob function would upload an image as a block BLOB and will return newly created CloudBlobBlock.
A BLOB can be deleted by calling the Delete method on the object of CloudBlobBlock. The code listed below will delete on basis of BlobName in a given Azure container.
public void DeleteBlob(string BlobName, string ContainerName)
        {

            CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient();
            CloudBlobContainer container = blobClient.GetContainerReference(ContainerName);
            CloudBlockBlob blockBlob = container.GetBlockBlobReference(BlobName);
            blockBlob.Delete();
        }
A BLOB can be downloaded using the function detailed below:
public CloudBlockBlob DownloadBlob(string BlobName, string ContainerName)
        {
                        CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient();
            CloudBlobContainer container = blobClient.GetContainerReference(ContainerName);
            CloudBlockBlob blockBlob = container.GetBlockBlobReference(BlobName);
            // blockBlob.DownloadToStream(Response.OutputStream);
            return blockBlob;
        }
Keep in mind that public BLOB’s can be downloaded directly by hitting the BLOB URL. So probably, we may not use this function and instead will directly hit the URL to download the BLOB in browser. And when we put everything together, the BlobUtility class will have the following codes:
using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Blob;
using System;
using System.IO;

namespace UserImageUploadAzure.Utilities
{
    public class BlobUtility
    {
        public CloudStorageAccount storageAccount;
        public BlobUtility(string AccountName, string AccountKey)
        {
            string UserConnectionString = string.Format("DefaultEndpointsProtocol=https;AccountName={0};AccountKey={1}", AccountName, AccountKey);
            storageAccount = CloudStorageAccount.Parse(UserConnectionString);
        }

        public CloudBlockBlob UploadBlob(string BlobName, string ContainerName, Stream stream)
        {
         
            CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient();
            CloudBlobContainer container = blobClient.GetContainerReference(ContainerName.ToLower());
            CloudBlockBlob blockBlob = container.GetBlockBlobReference(BlobName);
            // blockBlob.UploadFromByteArray()
            try
            {
                blockBlob.UploadFromStream(stream);
                return blockBlob;
            }
            catch (Exception e)
            {
                var r = e.Message;
                return null; 
            }


        }

        public void DeleteBlob(string BlobName, string ContainerName)
        {

            CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient();
            CloudBlobContainer container = blobClient.GetContainerReference(ContainerName);
            CloudBlockBlob blockBlob = container.GetBlockBlobReference(BlobName);
            blockBlob.Delete();
        }

        public CloudBlockBlob DownloadBlob(string BlobName, string ContainerName)
        {
            CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient();
            CloudBlobContainer container = blobClient.GetContainerReference(ContainerName);
            CloudBlockBlob blockBlob = container.GetBlockBlobReference(BlobName);
            // blockBlob.DownloadToStream(Response.OutputStream);
            return blockBlob;
        }
    }
}
We have written the utility class which will perform the following operations:
  1. Upload image to BLOB
  2. Delete a BLOB
  3. Download a BLOB
Creating Controller and the View
By now, we have created a table and the utility class. Now let us use that in the controller to perform operations. To start with in the HomeController, create the object of BlobUtility class and ApplicationDbContext. We are declaring global variables and creating the object in the constructor of HomeController as shown in the listing below:
   public class HomeController : Controller
    {
        BlobUtility utility;
        ApplicationDbContext db; 
        string accountName = "yourazurestorageaccountname";
        string accountKey = "yourazurestorageaccountkey";
        public HomeController()
        {
            utility = new BlobUtility(accountName, accountKey);
            db = new ApplicationDbContext();
        }
In the Index action of HomeController, we wish to show all the photos of the logged in user. We can do that in the following steps:
  • Fetch Id of the current logged in user
  • From the UserImages table, fetch all the images of logged in user. We are fetching all the images on the basis of UserId.
  • In the ViewBag, passing the total count of the photos
  • Passing a List of UserImages to the view
Putting all of the above together, the Index action will have codes as shown in the listing below:
public ActionResult Index()
        {
            string loggedInUserId = User.Identity.GetUserId();
            List<UserImage> userImages = (from r in db.UserImages where r.UserId == loggedInUserId select r).ToList();
            ViewBag.PhotoCount = userImages.Count;
            return View(userImages);
        }
We have created the action to fetch all the images of a logged-in user. Let us go ahead and next create a View to display the photos. The View is going to be very simple. In the view, we are iterating a List of images and displaying that in a bootstrap column. Also, we have added a delete button with each image. When the user clicks on the delete button, that particular image will be deleted from the UserImages table and the Azure BLOB.
Essentially we are doing the following tasks in the view:
  1. Iterating all the photos and displaying in an-image element
  2. Displaying the number of photos in the header
  3. Adding a delete button to delete all the photos.
The View can be created as shown in the listing below:
@model IEnumerable<UserImageUploadAzure.Models.UserImage>
  <div class="row">
            <div class="col-lg-12">
                
                <div class="alert alert-warning">You have @ViewBag.PhotoCount Photos </div>
        </div>
        @foreach (var item in Model)
        {
            <div class="col-lg-3 col-md-4 col-xs-6 thumb">

                <a class="thumbnail" href="@item.ImageUrl">
                    <img class="img-responsive" src="@item.ImageUrl" style="height: 300px;width:100%;" alt="">
                </a>
                <a href="@Url.Action("DeleteImage", "Home",new { id = item.Id })" class="btn btn-default btn-block">                   
                    <span class="glyphicon glyphicon-remove" aria-hidden="true"></span>
                </a>

            </div>
           
            
        }
    </div>
As you can plainly see, we are using many bootstrap classes to make the view more immersive. Images of a given user will be displayed on the view as shown in the image below:
Great! So far we have listed all the photos of logged in user. Now let us perform the delete operation. As you might have noticed in the view, on the delete button, we are invoking DeleteImage action of Home controller. We are also passing the id to be deleted.
The DeleteImage action performs two tasks:
  1. Delete a row from UserImages table on the given id;
  2. Delete image from the Azure BLOB
Both the operations are pretty straightforward. First we are fetching the row to be deleted from the table, and deleting that. After that, we are extracting the BLOB name from the URL and deleting the BLOB from Azure BLOB storage.  DeleteImage action can be created as shown in the listing below:
    public ActionResult DeleteImage(string  id)
        {
            UserImage userImage = db.UserImages.Find(id);
            db.UserImages.Remove(userImage);
            db.SaveChanges();
            string  BlobNameToDelete = userImage.ImageUrl.Split('/').Last();
            utility.DeleteBlob(BlobNameToDelete, "jsr");
            return RedirectToAction("Index");
        }
So far we have performed tasks of listing all the images and deleting an image. Now let us write an action to upload an image. In the UploadImage action,
  1. Uploading image to Azure BLOB
  2. Inserting new row to UserImages table.
 [HttpPost]
        public ActionResult UploadImage(HttpPostedFileBase file)
        {
            if (file != null)
            {
                string ContainerName = "jsr"; //hardcoded container name. 
                file = file ?? Request.Files["file"];
                string fileName = Path.GetFileName(file.FileName);
                Stream imageStream = file.InputStream;
                var result = utility.UploadBlob(fileName, ContainerName, imageStream);
                if (result != null)
                {
                    string loggedInUserId = User.Identity.GetUserId();
                    UserImage userimage = new UserImage();
                    userimage.Id = new Random().Next().ToString();
                    userimage.UserId = loggedInUserId;
                    userimage.ImageUrl = result.Uri.ToString();
                    db.UserImages.Add(userimage);
                    db.SaveChanges();
                    return RedirectToAction("Index");
                }
                else
                {
                    return RedirectToAction("Index");
                }
            }
            else
            {
                return RedirectToAction("Index");
            }

            
        }
In the UploadImage action, we are passing HttpPostedFileBase as the input parameter and uploading the image as a stream. On the View, we are using HTML file type and uploading that as multipart/formdata. Also, the file upload is inside a form and calling UploadImage action of the Home controller. The View to upload an image can be created as shown in the listing below:
<div class="row">
        @using (Html.BeginForm("UploadImage", "Home", FormMethod.Post, new { enctype = "multipart/form-data" }))
        {


            <div class="panel panel-warning">
                <div class="panel-heading">
                    <h3 class="panel-title">Save Photo to your Album</h3>
                </div>
                <div class="panel-body">
                    <div class="row">
                        <div class="col-md-4 col-md-offset-4">
                            <input type="file" name="file" />
                            <br />
                            <input type="submit" class="btn btn-warning form-control" value="Save Photo" />
                        </div>
                    </div>
                </div>
            </div>
        }

    </div>
 By putting all the pieces together, view, upload, delete and list photos will have codes as shown in the listing below:
@model IEnumerable<UserImageUploadAzure.Models.UserImage>
<div class="container">
    <div class="page-header">
        <h1>MVC User Photo album <small>working with ASP.NET Identity 2.0 user and Azure BLOB Storage</small></h1>
    </div>



    <div class="row">
        @using (Html.BeginForm("UploadImage", "Home", FormMethod.Post, new { enctype = "multipart/form-data" }))
        {


            <div class="panel panel-warning">
                <div class="panel-heading">
                    <h3 class="panel-title">Save Photo to your Album</h3>
                </div>
                <div class="panel-body">
                    <div class="row">
                        <div class="col-md-4 col-md-offset-4">
                            <input type="file" name="file" />
                            <br />
                            <input type="submit" class="btn btn-warning form-control" value="Save Photo" />
                        </div>
                    </div>
                </div>
            </div>
        }

    </div>

    <br />
    
        <div class="row">
            <div class="col-lg-12">
                
                <div class="alert alert-warning">You have @ViewBag.PhotoCount Photos </div>
        </div>
        @foreach (var item in Model)
        {
            <div class="col-lg-3 col-md-4 col-xs-6 thumb">

                <a class="thumbnail" href="@item.ImageUrl">
                    <img class="img-responsive" src="@item.ImageUrl" style="height: 300px;width:100%;" alt="">
                </a>
                <a href="@Url.Action("DeleteImage", "Home",new { id = item.Id })" class="btn btn-default btn-block">                   
                    <span class="glyphicon glyphicon-remove" aria-hidden="true"></span>
                </a>

            </div>
           
            
        }
    </div>

</div>
Conclusion
There you have it! This is all you need to do create a photo album for an ASP.NET user, who is using Azure BLOB storage. In this post we covered the following topics:
  • Using ASP.NET Identity 2.0 user as reference in other table.
  • Creating one to many relationship with ASP.NET Identity 2.0 user table
  • Connecting ASP.NET MVC application to Azure Storage
  • Creating or Uploading a BLOB
  • Deleting a BLOB
  • Downloading a BLOB
  • Uploading file from MVC form to a Azure BLOB
I hope you found this post useful - thanks for reading!

No comments:

Post a Comment