Upload Multiple Files to Azure Storage and Manage them from an ASP.NET MVC 4 Application

Posted by: Suprotim Agarwal , on 5/3/2013, in Category Microsoft Azure
Views: 73592
Abstract: Build an ASP.NET MVC 4 application that uploads multiple files to Azure Cloud Storage, get the list of items in storage and have a rudimentary view of the files.

Recently we came across the need to store files in a CDN instead of common file shares like SkyDrive or DropBox. There are multiple cloud service providers who provide this service; Amazon S3 and Azure Storage being two of the prominent ones. At the time we manually uploaded files via the management console and distributed the links to the files ad hoc.

But in a real life application, we would be utilizing Storage for storing user files like Image, videos, mp3s and serving them up through a nice content management portal. So today, we will see how to build an ASP.NET MVC application that uploads files to Azure Cloud Storage, get the list of items in storage and have a rudimentary view of the files.

Usually Azure Projects imply testing out online in Azure portal, but for Storage, we have a nice Emulator that we can run locally and test our complete application out. Once tested, we can move to Azure Cloud.

 


The MVC Application

Pre-Requisites

Before we get started, we need to have the Azure SDK installed on the Client. You can install the client using Web Platform Installer as shown below

web-platform-install-azure-components

Once you have the Azure SDK setup, we can get started.

The Application Template

Step 1: We will start off with a new MVC 4 application and use the Basic Template.

basic-template

Step 2: Once the project is setup, we will have to get the Azure Storage client libraries. We can get it using the Nuget Package Management Dialog. As seen below, we did a search for ‘azure storage’ in the ‘Online’ node.

windows-azure-storage-lib

Step 3: Select ‘Windows Azure Storage’ and Install. This will kick off the dependency checker which will resolve all the required dependencies.

azure-storage-dependency

Step 4: Once all the dependencies are resolved, we’ll get the License Acceptance Dialog as follows.

azure-storage-license-agreement

All the components are from Microsoft, so we can safely accept and continue.

Step 5: Once the installation is done, go to the ‘Updates’ node and update all the Azure related components. Once the updates are complete, you are all set to actually get started.

Starting the Storage Emulator

Before we can use the Emulator in our application, we need to run it explicitly. In Windows 8 you can search for ‘Azure’ on the Home screen. In Windows 7 you can type ‘Azure’ in the search textbox on the start button.

azure-command-prompt

Find the ‘Windows Azure Command Prompt’ and launch it.

Kick off the Storage Emulator using the command

csrun /devstore

storagte-command-prompt-execute

Once the emulator starts, you’ll have a Tray Icon that you can use to shut down once you are done

azure-storage-emulator-tray

Now we are all set to start building the MVC App.

Setting up the Model

Under the Models folders add two classes, CloudFile and CloudFilesModel.

The CloudFile class is as follows

public class CloudFile
{
public string FileName { get; set; }
public string URL { get; set; }
public long Size { get; set; }
public static CloudFile CreateFromIListBlobItem(IListBlobItem item)
{
  if (item is CloudBlockBlob)
  {
   var blob = (CloudBlockBlob)item;
   return new CloudFile
    {
     FileName = blob.Name,
     URL = blob.Uri.ToString(),
     Size = blob.Properties.Length
    };
   }
  return null;
}
}

It has three properties to start off with, FileName, URL and the Size of the File. It also has a static method to convert an instance of IListBlobItem into a CloudFile.

The CloudFilesModel class acts as the container for the CloudFile instances, that it exposes through the ‘Files’ property.

public class CloudFilesModel
{
public CloudFilesModel()
        : this(null)
{
  Files = new List<CloudFile>();
}
public CloudFilesModel(IEnumerable<IListBlobItem> list)
{
  Files = new List<CloudFile>();
  if (list != null && list.Count<IListBlobItem>() > 0)
   {
    foreach (var item in list)
    {
     CloudFile info = CloudFile.CreateFromIListBlobItem(item);
     if (info != null)
     {
      Files.Add(info);
     }
    }
   }
  }
  public List<CloudFile> Files { get; set; }
}

Setting up the Connection String to connect to the Azure Blob Storage (emulator)

The client SDK depends on a connection string to connect to Cloud Storage. We’ll save the connection information in Web.config’s AppSettings so that once our testing is done, we can flip it to the Actual connection string in production.

We need to add an App Setting called StorageConnectionString with the value set to “UseDevelopmentStorage=true”. This refers to local emulator.

The Storage Container name need not be in the Web.config because you might want to use different container names depending on the context. For example, you might use a different Container per user or per Company or similar segregations. However in our case today, we will only use one container hence we’ll add it to the Web.config using the “CloudStorageContainerReference”.

<appSettings>
… snipped …
<add key="StorageConnectionString" value="UseDevelopmentStorage=true"/>
<add key="CloudStorageContainerReference" value="dnc-demo" />
</appSettings>

Notes on Naming Conventions for Cloud Storage Continer

The Name we select for our containers need to abide by URL naming conventions. To recap these requirements:

- With the exception of the dash, no other symbols including white space are allowed.

- They must start with a lowercase letter or number.

- All letters must be lowercase. Uppercase letters are not allowed.

- They may contain a dash (-), but if a dash is used is, it must be preceded and followed by either a lowercase letter or a number.

- They must be at least three characters long.

- They can be no more than 63 characters.

If we don’t follow these guidelines, Azure throws a generic runtime Http 400 runtime error. The only hint is a message saying, one of the inputs is out of range.

The HomeController

Once the model and connection strings are setup, we create our HomeController. Use the Add New Controller dialog and select “MVC Controller with empty read/write sections”.

home-controller

Keep the Index() action method and delete the rest.

Listing all Files in the container

Let’s build an Index page and the Action method that will show files in the Container. To get this, we need to retrieve the list of files from the Container and convert them into CloudFile item and store it the CloudFilesModel. The view will use the CloudFilesModel to render the items.

Controller

The Action method therefore does the following:

1. Connect to the storage Account using the Connection String -

CloudStorageAccount storageAccount = CloudStorageAccount.Parse(
    CloudConfigurationManager.GetSetting("StorageConnectionString"));

2. Create a StorageClient -

CloudBlobClient storageClient = storageAccount.CreateCloudBlobClient();

3. Create a StorageContainer from the StorageClient -

CloudBlobContainer storageContainer = storageClient.GetContainerReference(
    ConfigurationManager.AppSettings.Get("CloudStorageContainerReference"));

4. Create a CloudFilesModel by passing the ListBlobs in the Container -

CloudFilesModel blobsList = new
    CloudFilesModel(storageContainer.ListBlobs(useFlatBlobListing: true));

The complete listing is as follows

public ActionResult Index()
{
CloudStorageAccount storageAccount = CloudStorageAccount.Parse(
  CloudConfigurationManager.GetSetting("StorageConnectionString"));
CloudBlobClient storageClient = storageAccount.CreateCloudBlobClient();
CloudBlobContainer storageContainer = storageClient.GetContainerReference(
  ConfigurationManager.AppSettings.Get("CloudStorageContainerReference"));
CloudFilesModel blobsList = new
  CloudFilesModel(storageContainer.ListBlobs(useFlatBlobListing: true));
return View(blobsList);
}

View

Once the controller is ready to return the view model, we’ll go ahead and build the view. We add Index.cshtml and the following markup + razor code to simply loop through each Cloud File and add it as a list item.

At the bottom of the page, we have an Action Link that points to another Action Method called UploadFile. The final listing is as follows:

@model MvcAzureBlogStorage.Models.CloudFilesModel
@{
ViewBag.Title = "File List";
}
<h2>File List</h2>
<ul>
@foreach (var item in Model.Files)
{
  <li>
   <a href="@item.URL">@item.FileName</a> (@item.Size bytes)
  </li>
}
</ul>
@Html.ActionLink("Upload Another File", "UploadFile")

If we run the application now, we’ll not have anything in the Index View only the “Upload Another File” link that is linked to an non-existing Action Method. Let’s fix that first.

Upload File Action
Controller

The Upload File Action method does the following

- Gets the Storage Account using the Connection String

- Create a StorageClient instance using the Account

- Retrieve the StorageContainer from the StorageClient. If the container doesn’t exist, it is created.

- Create a CloudFilesModel by passing the ListBlobs in the Container

- Then for each file in the Request get a CloudBlockBlob instance using the file name of the source file.

- Use the CloudBlockBlob to upload from the request stream.

The entire code list is as follows

public ActionResult UploadFile()
{
if (Request.Files.Count > 0)
{
  CloudStorageAccount storageAccount =
   CloudStorageAccount.Parse(
    CloudConfigurationManager.GetSetting("StorageConnectionString"));
  var storageClient = storageAccount.CreateCloudBlobClient();
  var storageContainer = storageClient.GetContainerReference(
   ConfigurationManager.AppSettings.Get("CloudStorageContainerReference"));
  storageContainer.CreateIfNotExists();
  for (int fileNum = 0; fileNum < Request.Files.Count; fileNum++)
  {
   string fileName = Path.GetFileName(Request.Files[fileNum].FileName);
   if (
    Request.Files[fileNum] != null &&
   Request.Files[fileNum].ContentLength > 0)
   {
    CloudBlockBlob azureBlockBlob = storageContainer.GetBlockBlobReference(fileName);
    azureBlockBlob.UploadFromStream(Request.Files[fileNum].InputStream);
   }
  }
  return RedirectToAction("Index");
}
return View("UploadFile");
}

View

The UploadFile View provides a HTML file input element and an Upload button to trigger the Upload Action method. The complete markup is as follows:

@{
ViewBag.Title = "UploadFile";
}

<h1>Upload a File</h1>

@using (Html.BeginForm("UploadFile", "Home", FormMethod.Post,

new { enctype = "multipart/form-data" }))
{
<input type="file" id="FileUploader" name="FileUploader" multiple="multiple" />
<input type="submit" name="Submit" id="Submit" value="Upload" />
}

Running the Application

Now that our controllers and views are ready, we can run the application end-to-end and add some files to the Storage Emulator.

The Index Page

On first run, the Index page is empty.

empty-index-page

 

The Upload Page

We click on the ‘Upload Another File’ link to navigate to the Upload file page. We click on Browser and select a file and the click ‘Upload’ to send it to the Blob storage.

single-files-selected

Once Upload completes, the application navigates back to the File List

index-after-file-upload

Accessing the Uploaded Files

Once files have been uploaded, they get a URL and as we can see above, the files are hyperlinked to this URL. When we click the URL, we should be able to see or download the file. However if we click on the file, we are likely to see this instead:

file-navigation-failure

Huh! Why?

Well, by default, the Blob Storage Containers provide no permissions for external access. This results in the 404. How to provide access?

Step 1: In Visual Studio, open the Database explorer, expand the Windows Azure Storage Note, expand the Blobs node and Select the container (dnc-demo) as shown below.

blob-storage-permissions

Step 2: Now bring up the properties dialog for the Blob Container. You’ll see there is a property called PublicAccess under Permissions. This is set to ‘Off’. The other options are ‘Container’ and ‘Blob’. We don’t want our entire Blob to be public, but we could have make the container public so that the files in it are visible to others. On change Azure Storage will warn you that this might take some time to take effect. In dev environment, the time is negligible but you should keep it in mind in Production.

Step 3: Now navigate back to the Index view and click on the Link for 1.jpg. As we can see below, this time we navigate to the file correctly! Sweet!

file-navigation-success

Multiple File Upload

With HTML5 compliant browsers multiple file upload is actually easier than before. All we have to do is add the multiple attribute to the input as follows

<input type="file" id="File01" name="File01" multiple="multiple" />

Now if you navigate to Upload file page and click on Browser, the file picker will allow you to select multiple files.

 multi-file-select-dialog

Click on Open and the Input is actually populated with all the file names comma separated.

multiple-files-selected

Click on Upload and all the files will get uploaded and the app will navigate back to the Index

index-multiple-files-uploaded

As we can see all our files were uploaded successfully.

Notes: The Multi file upload support is a little spotty across browsers. While Chrome and Firefox have been good for a bit, the above example is with IE v10. So unless you are on the latest and greatest browser, your mileage may vary with respect to Multiple File Upload.

One Big Limitation

Before we conclude grandly, it will be unfair if we didn’t put out the most obvious shortcoming in this technique. That’s the default file size limit of 4MB. This limit is imposed by IIS to prevent super large posts DOSing your server. Increasing the default limit though possible is not the desired solution. The ideal solution for this is to upload files ‘chunked’ meaning in pieces.

In the current technique, files are streamed into memory before written out to the output stream, in chunked upload files are streamed in pre-defined sized pieces (or chunks). The actual process is a little involved to start at this point. It deserves a dedicated effort for itself so I won’t go into details now. Suffice to you say that you have seen it action when you are uploading multiple files to cloud stores like SkyDrive or DropBox.

Conclusion

Now we can conclude the article with the takeaway that we saw how to upload files into Azure Blob storage. We also figured out how to setup permissions for the blob container.

Going live now is a matter of setting up our storage account in Azure and replacing the development connection string with the Production connection string!

Download the entire source code of this article (Github)

This article has been editorially reviewed by Suprotim Agarwal.

Absolutely Awesome Book on C# and .NET

C# and .NET have been around for a very long time, but their constant growth means there’s always more to learn.

We at DotNetCurry are very excited to announce The Absolutely Awesome Book on C# and .NET. This is a 500 pages concise technical eBook available in PDF, ePub (iPad), and Mobi (Kindle).

Organized around concepts, this Book aims to provide a concise, yet solid foundation in C# and .NET, covering C# 6.0, C# 7.0 and .NET Core, with chapters on the latest .NET Core 3.0, .NET Standard and C# 8.0 (final release) too. Use these concepts to deepen your existing knowledge of C# and .NET, to have a solid grasp of the latest in C# and .NET OR to crack your next .NET Interview.

Click here to Explore the Table of Contents or Download Sample Chapters!

What Others Are Reading!
Was this article worth reading? Share it with fellow developers too. Thanks!
Share on LinkedIn
Share on Google+

Author
Suprotim Agarwal, MCSD, MCAD, MCDBA, MCSE, is the founder of DotNetCurry, DNC Magazine for Developers, SQLServerCurry and DevCurry. He has also authored a couple of books 51 Recipes using jQuery with ASP.NET Controls and The Absolutely Awesome jQuery CookBook.

Suprotim has received the prestigious Microsoft MVP award for Sixteen consecutive years. In a professional capacity, he is the CEO of A2Z Knowledge Visuals Pvt Ltd, a digital group that offers Digital Marketing and Branding services to businesses, both in a start-up and enterprise environment.

Get in touch with him on Twitter @suprotimagarwal or at LinkedIn



Page copy protected against web site content infringement 	by Copyscape




Feedback - Leave us some adulation, criticism and everything in between!
Comment posted by Joel on Thursday, May 16, 2013 1:18 PM
This is just what I needed, thanks.
Comment posted by Francisco on Wednesday, June 5, 2013 3:11 AM
Great article. One little detail, I had to add storageContainer.CreateIfNotExists() on the Index action menthod too in order to get it running on the first time.
Comment posted by Worawut Boontan on Saturday, July 20, 2013 1:44 AM
Nice article
Thanks.
Comment posted by anil on Thursday, August 29, 2013 2:41 AM
This is my code for Uploading multiple images to azure,

This is my code,I am running this code Getting the following Error:

Error 2 foreach statement cannot operate on variables of type 'System.Web.HttpPostedFileBase' because 'System.Web.HttpPostedFileBase' does not contain a public definition for 'GetEnumerator' D:\DRIVE(D)\mindstick(Practice\NotousProducts\MvcWebRole1\Controllers\ProductsController.cs 64 21 MvcWebRole1

View:
@model MvcWebRole1.Models.Product

@{
ViewBag.Title = "Create";
}

<h2>Create</h2>

@using (Html.BeginForm("Create","Products",FormMethod.Post, new { enctype = "multipart/form-data" })) {
@Html.AntiForgeryToken()
@Html.ValidationSummary(true)

<fieldset>
<legend>Product</legend>

<div class="editor-label">
@Html.LabelFor(model => model.Name)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.Name)
@Html.ValidationMessageFor(model => model.Name)
</div>

<div class="editor-label">
@Html.LabelFor(model => model.Description)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.Description)
@Html.ValidationMessageFor(model => model.Description)
</div>

<div class="editor-label">
@Html.LabelFor(model => model.Price)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.Price)
@Html.ValidationMessageFor(model => model.Price)
</div>

<div class="editor-label">
@Html.LabelFor(model => model.Image)
</div>
<div class="editor-field">
@*@Html.EditorFor(model => model.Image)*@
<input type="file" name="fileBase" id="filebase1" />
@Html.ValidationMessageFor(model => model.Image)
</div>

<div class="editor-label">
@Html.LabelFor(model => model.Image1)
</div>
<div class="editor-field">
@*@Html.EditorFor(model => model.Image1)*@
<input type="file" name="fileBase" id="filebase2" />
@Html.ValidationMessageFor(model => model.Image1)
</div>

<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}

<div>
@Html.ActionLink("Back to List", "Index")
</div>

@section Scripts {
@Scripts.Render("~/bundles/jqueryval")
}

[HttpPost]
public ActionResult Create(Product p,HttpPostedFileBase fileBase)
{
if (fileBase.ContentLength > 0)
{
foreach (IEnumerable<HttpPostedFileBase> image in fileBase)
{
// Retrieve a reference to a container
CloudBlobContainer blobContainer = _myBlobStorageService.GetCloudBlobContainer();
CloudBlob blob = blobContainer.GetBlobReference(fileBase.FileName);

// Create or overwrite the "myblob" blob with contents from a local file
blob.UploadFromStream(fileBase.InputStream);
}
}
}
//secode
CloudBlobContainer blobContainer1 =_myBlobStorageService.GetCloudBlobContainer();
CloudBlob blob1 = blobContainer1.GetBlobReference(fileBase.FileName);

CloudBlobContainer blobContainer112 = _myBlobStorageService.GetCloudBlobContainer();
CloudBlob blob112 = blobContainer112.GetBlobReference(fileBase.FileName);

List<string> blobs = new List<string>();
// Loop over blobs within the container and output the URI to each of them
foreach (var blobItem in blobContainer1.ListBlobs())
blobs.Add(blobItem.Uri.ToString());

// TODO: Add insert logic here
//Product p1 = new Product();
p.Image = blob1.Uri.ToString();
p.Image1 = blob112.Uri.ToString();
db.Products.InsertOnSubmit(p);
db.SubmitChanges();
return RedirectToAction("Index");

}
Comment posted by Sumit on Monday, October 7, 2013 5:38 AM
Anil, What version of .NET are you using?
Comment posted by anil on Friday, January 3, 2014 3:08 AM
I am getting this error
ERROR:'Microsoft.WindowsAzure.CloudStorageAccount' does not contain a definition for 'CreateCloudBlobClient' and no extension method 'CreateCloudBlobClient' accepting a first argument of type 'Microsoft.WindowsAzure.CloudStorageAccount' could be found (are you missing a using directive or an assembly reference?)
Comment posted by anil on Wednesday, January 29, 2014 7:45 AM
<div class="editor-label">
Select must 10 images***
</div>
<div class="editor-field">
<input type="file" id="MultipleFiles" name="MultipleFiles" multiple="multiple" />
</div>
CS Code:
public ActionResult Create(HttpPostedFileBase[] MultipleFiles, FormCollection collection, demoproduct p)
        {
          try
            {
                // TODO: Add insert logic here

             var Tagid = collection["TagNo"];
             var qry = (from c in db.demoproducts where c.TagNo == Tagid select c).Count();
             if (qry != 1)

              {
            foreach (var fileBase in MultipleFiles)
            {
                if (fileBase.ContentLength > 0)
                {                      Microsoft.WindowsAzure.StorageClient.CloudBlobContainer blobContainer = _myBlobStorageService.GetCloudBlobContainer();

                      CloudBlob blob = blobContainer.GetBlobReference(fileBase.FileName);

                      blob.UploadFromStream(fileBase.InputStream);                  

                }
            }
            //Retrive Images from blob

            List<string> blobs = new List<string>();
            foreach (var fileBase in MultipleFiles)

            {
                Microsoft.WindowsAzure.StorageClient.CloudBlobContainer blobContainer1 = _myBlobStorageService.GetCloudBlobContainer();

                CloudBlob blob1 = blobContainer1.GetBlobReference(fileBase.FileName);

                blobs.Add(blob1.Uri.ToString());
            }
            p.ProductImage = blobs.ElementAt(0).ToString();

            p.ProductImage1 = blobs.ElementAt(1).ToString();

            p.ProductImage2 = blobs.ElementAt(2).ToString();

            p.ProductImage3 = blobs.ElementAt(3).ToString();

            p.ProductImage4 = blobs.ElementAt(4).ToString();

            p.ProductImage5 = blobs.ElementAt(5).ToString();      
db.demoproducts.InsertOnSubmit(p);
            db.SubmitChanges();
            return RedirectToAction("Index");

                        

              }
             else
             {
                 return RedirectToAction("EventCreationError");
             }
            }
          catch
          {             //return View();
            return RedirectToAction("Create");
          }
        }
This is working fine for aploading AND save to database That image urls to database,
Supose i want to i want to select 2mages only at the time of saving to database not working
Comment posted by daljeet dhaliwal on Sunday, May 25, 2014 3:35 AM
hey!
what if i want to delete some images can you help me with this....
great code by the way
thaks
Comment posted by Dorienne Grech on Thursday, April 2, 2015 9:25 AM
I know it's rather late.

But for some strange reason, the list in the CloudFileModel primarily
IEnumerable<IListBlobItem> list is giving me a 404 error.

the line in concern is:
if (list != null && list.Count<IListBlobItem>() > 0) ....

any idea why? Some files are already present in the Blob
Comment posted by Dorienne Grech on Friday, April 3, 2015 8:06 AM
I know it's rather late.

But for some strange reason, the list in the CloudFileModel primarily
IEnumerable<IListBlobItem> list is giving me a 404 error.

the line in concern is:
if (list != null && list.Count<IListBlobItem>() > 0) ....

any idea why? Some files are already present in the Blob
Comment posted by Dorienne Grech on Friday, April 3, 2015 8:06 AM
no need found the error. Thank you so much for the tutorial
Comment posted by Gilik on Sunday, April 5, 2015 5:58 AM
Dorienne Grech What was the error and how did you fix it