Skip to main content

File upload mechanism to support Local server, File Server and Azure server

Making direct dependency with Azure sounds as a big problem if there is a possibility of switching to other platforms in future. Also, in general, making dependencies with Azure also makes mandatory to run the application through Azure Emulator which itself takes the time to load application whenever need to be debugged. It takes huge amount of development time also based on architecture point it is not a good idea to have any hard dependency in application.

In this article, we will look into creating a mechanism for file upload which could be switched from Azure to local server or vice-versa.

The two key parts that are needed are to access the uploaded file and to upload the file. The starting stage is to create interfaces to access file path and upload files to server. This would allow us to change the implementation based on need.

Interfaces

IFileUpload : Interface for uploading files

 using MyProject.Model.DTO.Upload;  
 using MyProject.Model.Enumeration;  
 using System;  
 using System.Collections.Generic;  
 using System.Threading.Tasks;  
 namespace MyProject.Interface.FileManager  
 {  
   /// <summary>  
   /// File upload  
   /// </summary>  
   public interface IFileUpload  
   {  
     /// <summary>  
     /// Gets a value indicating whether implementation needs a root location.  
     /// </summary>  
     /// <value>  
     ///  <c>true</c> if derived class needs root location; otherwise, <c>false</c>.  
     /// </value>  
     bool NeedsRootLocation { get; }  
     /// <summary>  
     /// Uploads of the files asynchronous.  
     /// </summary>  
     /// <param name="files">The files that need to be uploaded.</param>  
     /// <param name="status">The file upload status.</param>  
     /// <returns>Task as asynchronous upload</returns>  
     IEnumerable<Task> UploadAsync(IEnumerable<FileAttachment> files, IProgress<FilesUploadStatus> status = null);  
   }  
 }  


This has simple NeedsRootLocation for setting up root directory for the application and the other function is for uploading the file with file upload status.

IFilePath : Interface to access file path from the server also accessing files as streams.

 using MyProject.Model.Enumeration;  
 using System;  
 using System.IO;  
 using System.Threading.Tasks;  
 namespace MyProject.Interface.FileManager  
 {  
   /// <summary>  
   /// File location for resources  
   /// </summary>  
   /// <remarks>Since root path won't be applicable in all cases, it's made as anonymous function to execute only if needed.</remarks>  
   public interface IFilePath  
   {  
     /// <summary>  
     /// Gets a value indicating whether implementation needs a root location.  
     /// </summary>  
     /// <value>  
     ///  <c>true</c> if derived class needs root location; otherwise, <c>false</c>.  
     /// </value>  
     bool NeedsRootLocation { get; }  
     /// <summary>  
     /// Gets the file path for requested resource.  
     /// </summary>  
     /// <param name="fileType">The file type.</param>  
     /// <param name="fileName">Name of the file.</param>  
     /// <returns>Path of the file.</returns>  
     string GetFilePath(FileTypes fileType, string fileName);  
     /// <summary>  
     /// Gets the file path for requested resource asynchronously.  
     /// </summary>  
     /// <param name="fileType">The file type.</param>  
     /// <param name="fileName">Name of the file.</param>  
     /// <returns>Path of the file.</returns>  
     Task<string> GetFilePathAsync(FileTypes fileType, string fileName);  
     /// <summary>  
     /// Gets the file stream.  
     /// </summary>  
     /// <param name="fileType">Type of the file.</param>  
     /// <param name="fileName">Name of the file.</param>  
     /// <returns>File content.</returns>  
     Stream GetFileStream(FileTypes fileType, string fileName);  
     /// <summary>  
     /// Gets the file stream asynchronous.  
     /// </summary>  
     /// <param name="fileType">Type of the file.</param>  
     /// <param name="fileName">Name of the file.</param>  
     /// <returns>File content.</returns>  
     Task<Stream> GetFileStreamAsync(FileTypes fileType, string fileName);  
   }  
 }  

This has asynchronous and synchronous methods to access file path as URI or get direct streams from the server.

IRootLocation: This interface is used to set root location in case of local storage on the server for accessing files or on other file server whereever some root location is needed.

 namespace MyProject.Interface.FileManager  
 {  
   public interface IRootLocation  
   {  
     /// <summary>  
     /// Sets the file location.  
     /// </summary>  
     /// <param name="rootLocation">The root location.</param>  
     void SetRootLocation(string rootLocation);  
   }  
 }  

These interfaces are all needed for the file upload and access mechanism. As per given codes we need to have some models and enum values that need to look for.

Models

FileAttachment : This model is meant to be used for data transfer to upload streams in created upload mechanism. It can accept streams or byte arrays to upload file.

 using MyProject.Model.Enumeration;  
 using System.IO;  
 namespace MyProject.Model.DTO.Upload  
 {  
   /// <summary>  
   /// Files for uploading  
   /// </summary>  
   public class FileAttachment  
   {  
     /// <summary>  
     /// The data length backing field.  
     /// </summary>  
     private long length;  
     /// <summary>  
     /// Gets or sets the identifier.  
     /// </summary>  
     /// <value>  
     /// The identifier.  
     /// </value>  
     public int Id { get; set; }  
     /// <summary>  
     /// Gets or sets the name.  
     /// </summary>  
     /// <value>  
     /// The name.  
     /// </value>  
     public string Name { get; set; }  
     /// <summary>  
     /// Gets or sets the type of the MIME.  
     /// </summary>  
     /// <value>  
     /// The type of the MIME.  
     /// </value>  
     public string MimeType { get; set; }  
     /// <summary>  
     /// Gets or sets the type of the file.  
     /// </summary>  
     /// <value>  
     /// The type of the file.  
     /// </value>  
     public FileTypes FileType { get; set; }  
     #region " File Data "  
     /// <summary>  
     /// Gets or sets the data as stream.  
     /// </summary>  
     /// <value>  
     /// The data stream.  
     /// </value>  
     public Stream DataStream { get; set; }  
     /// <summary>  
     /// Gets or sets the byte array data.  
     /// </summary>  
     /// <value>  
     /// The byte array data.  
     /// </value>  
     public byte[] DataByteArray { get; set; }  
     /// <summary>  
     /// Gets a value indicating whether this instance has stream.  
     /// </summary>  
     /// <value>  
     /// <c>true</c> if this instance has stream; otherwise, <c>false</c>.  
     /// </value>  
     public bool HasStream  
     {  
       get  
       {  
         return DataStream != null;  
       }  
     }  
     /// <summary>  
     /// Gets a value indicating whether this instance has byte array.  
     /// </summary>  
     /// <value>  
     /// <c>true</c> if this instance has byte array; otherwise, <c>false</c>.  
     /// </value>  
     public bool HasByteArray  
     {  
       get  
       {  
         return DataByteArray != null;  
       }  
     }  
     #endregion " File Data "  
     /// <summary>  
     /// Gets the length of the data.  
     /// </summary>  
     /// <value>  
     /// The length of the data.  
     /// </value>  
     public long DataLength  
     {  
       get  
       {  
         if (length > 0)  
         {  
           return length;  
         }  
         length = HasStream ? DataStream.Length : (HasByteArray ? DataByteArray.Length : 0);  
         return length;  
       }  
     }  
   }  
 }  

FileTypes: The other major item is to depend on enum to operate on entire files. Through this only everything would be centralized and accessed through.

   /// <summary>  
   /// File types for project  
   /// </summary>  
   public enum FileTypes  
   {  
     /// <summary>  
     /// The user profile image  
     /// </summary>  
     UserProfileImage,  
     /// <summary>  
     /// The attachment photos  
     /// </summary>  
     AttachmentPhoto,  
   }  

FilesUploadStatus: File upload status to gets all valid statistics for file uploads on the server.

 using System.Collections.Generic;  
 using System.Linq;  
 namespace MyProject.Model.DTO.Upload  
 {  
   /// <summary>  
   /// Whole uploading file upload status  
   /// </summary>  
   public class FilesUploadStatus  
   {  
     /// <summary>  
     /// Initializes a new instance of the <see cref="FilesUploadStatus"/> class.  
     /// </summary>  
     /// <param name="status">The status.</param>  
     public FilesUploadStatus(IList<FileUploadStatus> status)  
     {  
       Status = status;  
     }  
     /// <summary>  
     /// Gets or sets the current file.  
     /// </summary>  
     /// <value>  
     /// The current file.  
     /// </value>  
     public FileUploadStatus CurrentFile { get; set; }  
     /// <summary>  
     /// Gets the size of the file.  
     /// </summary>  
     /// <value>  
     /// The size of the file.  
     /// </value>  
     public long FileSize  
     {  
       get  
       {  
         return Status.Select(stat => stat.FileSize).Sum();  
       }  
     }  
     /// <summary>  
     /// Gets the percentage.  
     /// </summary>  
     /// <value>  
     /// The percentage.  
     /// </value>  
     public double Percentage  
     {  
       get  
       {  
         return Status.Select(stat => stat.Percentage).Sum();  
       }  
     }  
     /// <summary>  
     /// Gets the status.  
     /// </summary>  
     /// <value>  
     /// The status.  
     /// </value>  
     public IList<FileUploadStatus> Status { get; private set; }  
     /// <summary>  
     /// Gets the uploaded.  
     /// </summary>  
     /// <value>  
     /// The uploaded.  
     /// </value>  
     public long Uploaded  
     {  
       get  
       {  
         return Status.Select(stat => stat.Uploaded).Sum();  
       }  
     }  
   }  
   /// <summary>  
   /// Single file upload status  
   /// </summary>  
   public class FileUploadStatus  
   {  
     /// <summary>  
     /// Gets or sets the identifier.  
     /// </summary>  
     /// <value>  
     /// The identifier.  
     /// </value>  
     public int Id { get; set; }  
     /// <summary>  
     /// Gets or sets the size of the file.  
     /// </summary>  
     /// <value>  
     /// The size of the file.  
     /// </value>  
     public long FileSize { get; set; }  
     /// <summary>  
     /// Gets or sets the name.  
     /// </summary>  
     /// <value>  
     /// The name.  
     /// </value>  
     public string Name { get; set; }  
     /// <summary>  
     /// Gets the percentage.  
     /// </summary>  
     /// <value>  
     /// The percentage.  
     /// </value>  
     public double Percentage  
     {  
       get  
       {  
         return (double)Uploaded / (double)FileSize;  
       }  
     }  
     /// <summary>  
     /// Gets or sets the uploaded.  
     /// </summary>  
     /// <value>  
     /// The uploaded.  
     /// </value>  
     public long Uploaded { get; set; }  
   }  
 }  

I have created the concept for status but not actually implemented stats on actual file upload status.

Now, our specification is ready and need to implement concrete classes based on same.

Local File or File server upload implementation

LocalFileUpload: Local File upload implementation.

 using MyProject.Interface.FileManager;  
 using MyProject.Model.DTO.Upload;  
 using System;  
 using System.Collections.Generic;  
 using System.IO;  
 using System.Threading.Tasks;  
 namespace MyProject.Core.FileManager.Local  
 {  
   /// <summary>  
   /// Local file upload  
   /// </summary>  
   public class LocalFileUpload  
   : IFileUpload, IRootLocation  
   {  
     /// <summary>  
     /// Basic initialization for root location.  
     /// </summary>  
     private string applicationRootLocation;  
     /// <summary>  
     /// Gets a value indicating whether implementation needs a root location.  
     /// </summary>  
     /// <value>  
     /// <c>true</c> if derived class needs root location; otherwise, <c>false</c>.  
     /// </value>  
     public bool NeedsRootLocation  
     {  
       get  
       {  
         return true;  
       }  
     }  
     /// <summary>  
     /// Sets the file location.  
     /// </summary>  
     /// <param name="fileType">Type of the file.</param>  
     /// <param name="rootLocation">The root location.</param>  
     public void SetRootLocation(string rootLocation)  
     {  
       applicationRootLocation = rootLocation;  
     }  
     /// <summary>  
     /// Uploads files asynchronous.  
     /// </summary>  
     /// <param name="files">The files that need to be uploaded.</param>  
     /// <param name="status">The upload status.</param>  
     /// <returns>Tasks for uploading</returns>  
     public IEnumerable<Task> UploadAsync(IEnumerable<FileAttachment> files, IProgress<FilesUploadStatus> status)  
     {  
       foreach (var fileData in files)  
       {  
         var rootLocation = LocalCommon.GetDirectoryLocation(fileData.FileType, applicationRootLocation);  
         string fullFilePath = null;  
         if (!LocalCommon.TryGettingFilePath(rootLocation, fileData.Name, out fullFilePath))  
         {  
                          // TODO: Log info  
           String.Format("Could not process file upload for location {0} having file name {1}.",  
             rootLocation, fileData.Name);  
         }  
         if (fileData.HasStream)  
         {  
           yield return Task.Factory.StartNew(() =>  
           {  
             byte[] buffer = new byte[16345];  
             using (var fs = new FileStream(fullFilePath,  
               FileMode.Create, FileAccess.Write, FileShare.None))  
             {  
               int read;  
               while ((read = fileData.DataStream.Read(buffer, 0, buffer.Length)) > 0)  
               {  
                 fs.Write(buffer, 0, read);  
               }  
             }  
           });  
         }  
         else if (fileData.HasByteArray)  
         {  
           yield return Task.Factory.StartNew(() =>  
           {  
             File.WriteAllBytes(fullFilePath, fileData.DataByteArray);  
           });  
         }  
       }  
     }  
   }  
 }  


The local or file server implementation needs to have root path for the application to store files. So, implemented IRootLocation interface to assign root file path. This can be avoided if want to integrate root location directly on the class.

The actual logic is pretty straight forward to understand, will give class implementation of LocalCommon.cs later stage which has actual logic to generate file paths.

LocalFilePath: Local or file server file path resides on the server.

 using MyProject.Interface.FileManager;  
 using MyProject.Model.Enumeration;  
 using System;  
 using System.IO;  
 using System.Threading.Tasks;  
 namespace MyProject.Core.FileManager.Local  
 {  
   /// <summary>  
   /// Managing file path for local system  
   /// </summary>  
   /// <remarks>Since root path won't be applicable in all cases, it's made as anonymous function to execute only if needed.</remarks>  
   public class LocalFilePath  
   : IFilePath, IRootLocation  
   {  
     /// <summary>  
     /// Basic initialization for root location.  
     /// </summary>  
     private string applicationRootLocation;  
     /// <summary>  
     /// Gets a value indicating whether implementation needs a root location.  
     /// </summary>  
     /// <value>  
     /// <c>true</c> if derived class needs root location; otherwise, <c>false</c>.  
     /// </value>  
     public bool NeedsRootLocation  
     {  
       get  
       {  
         return true;  
       }  
     }  
     /// <summary>  
     /// Gets the file path.  
     /// </summary>  
     /// <param name="fileType">Type of the file.</param>  
     /// <param name="fileName">Name of the file.</param>  
     /// <returns>File location</returns>  
     public string GetFilePath(FileTypes fileType, string fileName)  
     {  
       if (String.IsNullOrEmpty(applicationRootLocation))  
       {  
         throw new ArgumentException("rootLocation");  
       }  
       string filePath;  
       LocalCommon.TryGettingFullPath(fileType, applicationRootLocation, fileName, out filePath);  
       return filePath;  
     }  
     /// <summary>  
     /// Gets the file path for requested resource asynchronously.  
     /// </summary>  
     /// <param name="fileType">The file type.</param>  
     /// <param name="fileName">Name of the file.</param>  
     /// <returns>Path of the file.</returns>  
     public Task<string> GetFilePathAsync(FileTypes fileType, string fileName)  
     {  
       return Task.FromResult(GetFilePath(fileType, fileName));  
     }  
     /// <summary>  
     /// Gets the file stream.  
     /// </summary>  
     /// <param name="fileType">Type of the file.</param>  
     /// <param name="fileName">Name of the file.</param>  
     /// <returns>File content.</returns>  
     public Stream GetFileStream(FileTypes fileType, string fileName)  
     {  
       var filePath = GetFilePath(fileType, fileName);  
       if (!String.IsNullOrEmpty(filePath) && File.Exists(filePath))  
       {  
         return File.OpenRead(filePath) as Stream;  
       }  
       return null;  
     }  
     /// <summary>  
     /// Gets the file stream asynchronous.  
     /// </summary>  
     /// <param name="fileType">Type of the file.</param>  
     /// <param name="fileName">Name of the file.</param>  
     /// <returns>File content.</returns>  
     public Task<Stream> GetFileStreamAsync(FileTypes fileType, string fileName)  
     {  
       return Task.FromResult(GetFileStream(fileType, fileName));  
     }  
     /// <summary>  
     /// Sets the file location.  
     /// </summary>  
     /// <param name="fileType">Type of the file.</param>  
     /// <param name="rootLocation">The root location.</param>  
     public void SetRootLocation(string rootLocation)  
     {  
       applicationRootLocation = rootLocation;  
     }  
   }  
 }  

The implementation give information about file path of get file streams directly from the server. Through the streams file paths can be abstracted for the security reasons that we will look on usage section.

LocalCommon: The two main interface is done, but the application mostly has logic under LocalCommon class.

 using MyProject.Model.Enumeration;  
 using System;  
 using System.IO;  
 namespace MyProject.Core.FileManager.Local  
 {  
   /// <summary>  
   /// Local file helper class  
   /// </summary>  
   public static class LocalCommon  
   {  
     #region " Public Methods "  
     /// <summary>  
     /// Tries getting full path for file name.  
     /// </summary>  
     /// <param name="fileType">Type of the file.</param>  
     /// <param name="rootLocation">The base location for files.</param>  
     /// <param name="fileName">Name of the file.</param>  
     /// <param name="fullPath">The full path of the file.</param>  
     /// <returns><c>true</c>, if retrieving file is successful.</returns>  
     public static bool TryGettingFullPath(FileTypes fileType, string rootLocation, string fileName, out string fullPath)  
     {  
       return TryGettingFilePath(GetDirectoryLocation(fileType, rootLocation), fileName, out fullPath);  
     }  
     #endregion " Public Methods "  
     #region " Internal scope functions "  
     /// <summary>  
     /// Tries getting full file path.  
     /// </summary>  
     /// <param name="directoryLocation">The directory location.</param>  
     /// <param name="fileName">Name of the file.</param>  
     /// <param name="fullFilePath">The full file path.</param>  
     /// <returns><c>true</c>, if retrieving file is successful.</returns>  
     internal static bool TryGettingFilePath(string directoryLocation, string fileName, out string fullFilePath)  
     {  
       if (String.IsNullOrEmpty(directoryLocation))  
       {  
         throw new ArgumentNullException();  
       }  
       fullFilePath = null;  
       long fileId;  
       if (!long.TryParse(Path.GetFileNameWithoutExtension(fileName), out fileId))  
       {  
         return false;  
       }  
       string folderName = Path.Combine(directoryLocation, GetFolderNameForFile(fileId).ToString());  
       try  
       {  
         Directory.CreateDirectory(folderName);  
       }  
       catch (Exception)  
       {  
         return false;  
       }  
       fullFilePath = Path.Combine(folderName, fileName);  
       return true;  
     }  
     /// <summary>  
     /// Gets the directory location.  
     /// </summary>  
     /// <param name="fileType">Type of the file.</param>  
     /// <param name="rootLocation">The root location.</param>  
     /// <returns>Directory for the file based on <c>fileType</c>.</returns>  
     internal static string GetDirectoryLocation(FileTypes fileType, string rootLocation)  
     {  
       if (String.IsNullOrEmpty(rootLocation))  
       {  
         throw new ArgumentNullException("domainName");  
       }  
       string enumValue = Enum.GetName(typeof(FileTypes), fileType);  
       switch (fileType)  
       {  
         case FileTypes.UserProfileImage:  
           return Path.Combine(rootLocation, "Images", enumValue);  
         case FileTypes.AttachmentPhoto:  
           return Path.Combine(rootLocation, "Attachment", "Photo");  
         default:  
           return Path.Combine(rootLocation, enumValue);  
       }  
     }  
     #endregion " Internal scope functions "  
     /// <summary>  
     /// Get folder name for Image Id  
     /// </summary>  
     /// <param name="fileId">Image Id</param>  
     /// <param name="maxFilesPerFolder">The maximum numbers of files allowed in folder</param>  
     /// <returns>Folder name for ImageId</returns>  
     private static long GetFolderNameForFile(long fileId, int maxFilesPerFolder = 8000)  
     {  
       long folderName = ((fileId / maxFilesPerFolder) * maxFilesPerFolder) + maxFilesPerFolder;  
       return folderName == 0 ? maxFilesPerFolder : folderName;  
     }  
   }  
 }  


There are few techniques implemented in this class to get file path for different file access. The GetFolderNameForFile is simple solution to handle large number of the files. This design is supposed to store files based on id generated on DB. So, every files would be stored as number value along with the extension. As per the windows file system, it can store around 8000 files properly based on each folder. The logic would create/use folders based on 8000 files by calculating based on given file id or associated number to the same.

The GetDirectoryLocation function is mapped with FileTypes enum. This would allow us to easily manage folder location based on our need by just changing a few values.

These were all the implementations for local/file server access and upload of files. Let's look into the implementation for the Azure access for managing files.

Azure

The Azure blob system would work as same as implemented flow for Local server/File server approach, just that implementation for the concrete classes would be changed.

AzureFileUpload: The Azure file upload implementation.

 using MyProject.Interface.FileManager;  
 using MyProject.Model.DTO.Upload;  
 using MyProject.Model.Enumeration;  
 using Microsoft.WindowsAzure.Storage.Blob;  
 using System;  
 using System.Collections.Generic;  
 using System.IO;  
 using System.Linq;  
 using System.Text;  
 using System.Threading;  
 using System.Threading.Tasks;  
 namespace MyProject.Core.FileManager.Azure  
 {  
   /// <summary>  
   /// File upload on Azure cloud storage  
   /// </summary>  
   public class AzureFileUpload  
     : IFileUpload  
   {  
     /// <summary>  
     /// Gets or sets a value indicating whether implementation needs a root location.  
     /// </summary>  
     /// <value>  
     /// <c>true</c> if derived class needs root location; otherwise, <c>false</c>.  
     /// </value>  
     public bool NeedsRootLocation  
     {  
       get  
       {  
         return false;  
       }  
     }  
     /// <summary>  
     /// Uploads of the files asynchronous to Azure.  
     /// </summary>  
     /// <param name="files">The files that need to be uploaded.</param>  
     /// <param name="status">The file upload status.</param>  
     /// <returns>  
     /// Task as asynchronous upload  
     /// </returns>  
     /// <exception cref="System.ArgumentException">files</exception>  
     public IEnumerable<Task> UploadAsync(IEnumerable<FileAttachment> files, IProgress<FilesUploadStatus> status = null)  
     {  
       if (files == null || !files.Any())  
       {  
         throw new ArgumentException("files");  
       }  
       var wholeStatus = new FilesUploadStatus(  
          (from file in files  
          select new FileUploadStatus  
          {  
            Id = file.Id,  
            Name = file.Name,  
            Uploaded = 0,  
            FileSize = file.DataLength  
          }  
          ).ToList()  
         );  
       // Upload the files to azure blob storage and remove them from local disk  
       foreach (var fileData in files)  
       {  
         if (status != null)  
         {  
           wholeStatus.CurrentFile = wholeStatus.Status.SingleOrDefault(stat => stat.Id == fileData.Id);  
           // Avoid processing if file size is not sufficient  
           if (wholeStatus.CurrentFile.FileSize < 1)  
           {  
             continue;  
           }  
           status.Report(wholeStatus); // Report at initial  
         }  
         // Retrieve reference to a blob  
         var blob = GetContainer(fileData.FileType).GetBlockBlobReference(fileData.Name.ToLower());  
         // Set header  
         blob.Properties.ContentType = fileData.MimeType;  
         if (status == null)  
         {  
           if (fileData.HasStream)  
           {  
             yield return blob.UploadFromStreamAsync(fileData.DataStream).ContinueWith(tsk => fileData.DataStream.Dispose());  
           }  
           else if (fileData.HasByteArray)  
           {  
             yield return blob.UploadFromByteArrayAsync(fileData.DataByteArray, 0, fileData.DataByteArray.Length);  
           }  
         }  
         else  
         {  
           if (fileData.HasStream)  
           {  
             yield return SingleFileUploadAsyc(blob, fileData.DataStream, wholeStatus, status)  
               .ContinueWith(tsk => fileData.DataStream.Dispose());  
           }  
           else if (fileData.HasByteArray)  
           {  
             // TODO: File task status for upload.  
             yield return blob.UploadFromByteArrayAsync(fileData.DataByteArray, 0, fileData.DataByteArray.Length);  
           }  
         }  
       }  
     }  
     /// <summary>  
     /// Gets the container.  
     /// </summary>  
     /// <param name="fileType">Type of the file.</param>  
     /// <returns>Blob container based on file type and id.</returns>  
     /// <exception cref="System.InvalidCastException">Unable to get mapped version of + fileType</exception>  
     private CloudBlobContainer GetContainer(FileTypes fileType)  
     {  
       AzureContainerNames azureContainerName;  
       if (AzureCommon.TryGettingAzureContainer(fileType, out azureContainerName))  
       {  
                     // Retrieve storage account from connection-string  
                     var storageAccount = CloudStorageAccount.Parse(  
                          CloudConfigurationManager.GetSetting(BLOB_STORAGE_KEY));  
                     // Create the blob client  
                     var blobClient = storageAccount.CreateCloudBlobClient();  
                     // Retrieve a reference to a container  
                     // Container name must use lower case  
                     var container = blobClient.GetContainerReference(azureContainerName.ToString().ToLower());  
                     // Create the container if it doesn't already exist  
                     container.CreateIfNotExists();  
                     // return container  
                     return container;  
       }  
       else  
       {  
         throw new InvalidCastException("Unable to get mapped version of " + fileType);  
       }  
     }  
     /// <summary>  
     /// Asynchronous Single file upload to Azure with status.  
     /// </summary>  
     /// <param name="blob">The BLOB storage.</param>  
     /// <param name="data">The data that need to be uploaded.</param>  
     /// <param name="wholeStatus">The whole status for entire content.</param>  
     /// <param name="status">The status for triggering report.</param>  
     /// <returns>Task of single file upload.</returns>  
     private Task SingleFileUploadAsyc(CloudBlockBlob blob, Stream data, FilesUploadStatus wholeStatus,  
       IProgress<FilesUploadStatus> status)  
     {  
       int index = 1;  
       long startPosition = 0;  
       var blockSize = 256 * 1024;  
       var bytesToUpload = wholeStatus.CurrentFile.FileSize;  
       var bytesToRead = Math.Min(blockSize, bytesToUpload);  
       var blockIds = new List<string>();  
       do  
       {  
         using (var mre = new ManualResetEvent(false))  
         {  
           var blockId = Convert.ToBase64String(Encoding.UTF8.GetBytes(index.ToString("d6")));  
           blockIds.Add(blockId);  
           var ado = blob.PutBlockAsync(blockId, data, null);  
           ado.ContinueWith(t =>  
           {  
             wholeStatus.CurrentFile.Uploaded += bytesToRead;  
             status.Report(wholeStatus);  
             bytesToUpload -= bytesToRead;  
             startPosition += bytesToRead;  
             index++;  
             mre.Set();  
           });  
           mre.WaitOne();  
         }  
       }  
       while (bytesToUpload > 0);  
       return blob.PutBlockListAsync(blockIds);  
     }  
   }  
 }  

The SingleFileUploadAsyc is concept code to have file status for the uploads.

AzureContainerNames is enum for all container names available on Azure Storage to do mappings between FileTypes and AzureContainerNames.

AzureCommon: Azure common code, just contains mappings of enums.

 using MyProject.Core.Azure;  
 using MyProject.Model.Enumeration;  
 namespace MyProject.Core.FileManager.Azure  
 {  
   /// <summary>  
   /// Common functionality for Azure operation  
   /// </summary>  
   public static class AzureCommon  
   {  
     /// <summary>  
     /// Try to get the azure container name from given file type.  
     /// </summary>  
     /// <param name="fileType">Type of the file.</param>  
     /// <param name="azureContainerName">Name of the azure container.</param>  
     /// <returns>True, If successfully mapped and found</returns>  
     public static bool TryGettingAzureContainer(FileTypes fileType, out AzureContainerNames azureContainerName)  
     {  
       // Set some dummy value for container name  
       azureContainerName = AzureContainerNames.AgencyProfileImage;  
       var isSuccessful = false;  
       switch (fileType)  
       {  
         case FileTypes.UserProfileImage:  
           azureContainerName = AzureContainerNames.UserProfileImage;  
           isSuccessful = true;  
           break;  
         case FileTypes.AttachmentPhoto:  
           azureContainerName = AzureContainerNames.AttachmentPhoto;  
           isSuccessful = true;  
           break;  
         default:  
           isSuccessful = false;  
           break;  
       }  
       return isSuccessful;  
     }  
   }  
 }  


We are all set for the use of created system.

File references usage

In this, stream based approach is used, same way file path approach could be also used.

 /// <summary>  
     /// Gets the requested file asynchronously.  
     /// </summary>  
     /// <param name="fileType">Type of the file.</param>  
     /// <param name="fileName">Name of the file.</param>  
     /// <returns>File content through stream.</returns>  
     private async Task<ActionResult> GetFile(FileTypes fileType, string fileName)  
     {  
       if (FilePath.NeedsRootLocation)  
       {  
         (FilePath as IRootLocation).SetRootLocation(RaptorSetting.System.RootStoragePath);  
       }  
       var blobStream = await FilePath.GetFileStreamAsync(fileType, fileName);  
       if (blobStream == null)  
       {  
         return HttpNotFound();  
       }  
       return File(blobStream, MimeMapping.GetMimeMapping(fileName));  
     }  

The FilePath is came from dependency injection.
The MimeMapping is inbuilt class under System.Web which judges mime based on file extension.

File upload usage

 public async Task<ActionResult> ProfileImageUpload(IEnumerable<HttpPostedFileBase> profileImageUpload, int userId)  
     {  
       if (FileUpload.NeedsRootLocation)  
       {  
         (FileUpload as IRootLocation).SetRootLocation(ROOT_PATH);  
       }  
       var files = (from file in profileImageUpload  
           let id = 1  
              select new FileAttachment  
           {  
             Id = 1,  
             Name = userId + Path.GetExtension(file.FileName),  
             MimeType = file.ContentType,  
             FileType = FileTypes.UserProfileImage,  
             DataStream = file.InputStream,  
           });  
       await FileUpload.UploadAsync(files).FirstOrDefault();  
       return new EmptyResult();  
     }  

The code based on Telerik File upload Action but can be modified based on need. Also the upload code seems to big but can be shortened with some extension methods.

With these system in place, we can have dependency injection based on deployment environment. The glimpse of Ninject XML injection is on http://vikutech.blogspot.in/2014/08/dependency-injection-through-xml.html

NOTE: This is just the concept for the extendible file upload system based on file id and accessing the same. It can be further extended to support Amazon AWS or any other cloud storage.

Comments

Popular posts from this blog

Making FluentValidation compatible with Swagger including Enum or fixed List support

FluentValidation is not directly compatible with Swagger API to validate models. But they do provide an interface through which we can compose Swagger validation manually. That means we look under FluentValidation validators and compose Swagger validator properties to make it compatible. More of all mapping by reading information from FluentValidation and setting it to Swagger Model Schema. These can be done on any custom validation from FluentValidation too just that proper schema property has to be available from Swagger. Custom validation from Enum/List values on FluentValidation using FluentValidation.Validators; using System.Collections.Generic; using System.Linq; using static System.String; /// <summary> /// Validator as per list of items. /// </summary> /// <seealso cref="PropertyValidator" /> public class FixedListValidator : PropertyValidator { /// <summary> /// Gets the valid items /// <...

Elegantly dealing with TimeZones in MVC Core / WebApi

In any new application handling TimeZone/DateTime is mostly least priority and generally, if someone is concerned then it would be handled by using DateTime.UtcNow on codes while creating current dates and converting incoming Date to UTC to save on servers. Basically, the process is followed by saving DateTime to UTC format in a database and keep converting data to native format based on user region or single region in the application's presentation layer. The above is tedious work and have to be followed religiously. If any developer misses out the manual conversion, then that area of code/view would not work. With newer frameworks, there are flexible ways to deal/intercept incoming or outgoing calls to simplify conversion of TimeZones. These are steps/process to achieve it. 1. Central code for storing user's state about TimeZone. Also, central code for conversion logic based on TimeZones. 2. Dependency injection for the above class to ...

Handling JSON DateTime format on Asp.Net Core

This is a very simple trick to handle JSON date format on AspNet Core by global settings. This can be applicable for the older version as well. In a newer version by default, .Net depends upon Newtonsoft to process any JSON data. Newtonsoft depends upon Newtonsoft.Json.Converters.IsoDateTimeConverter class for processing date which in turns adds timezone for JSON data format. There is a global setting available for same that can be adjusted according to requirement. So, for example, we want to set default formatting to US format, we just need this code. services.AddMvc() .AddJsonOptions(options => { options.SerializerSettings.DateTimeZoneHandling = "MM/dd/yyyy HH:mm:ss"; });

Storing and restoring Kendo Grid state from Database

There is no any built in way to store entire grid state into database and restore back again with all filters, groups, aggregates, page and page size. At first, I was trying to restore only filters by looking through DataSourceRequest. DataSourceRequest is kind of communication medium between client and server for the operation we do on grid. All the request comes via DataSourceRequest. In previous approach, I was trying to store IFileDescriptor interface which come with class FileDescriptor by looping through filters and serializing into string for saving into database but this IFileDescriptor can also contain CompositeFilterDescriptor which can be nested in nested object which are very tricky to handle. So, I had decompiled entire Kendo.MVC library and found out that all Kendo MVC controls are derived from “JsonObject”. It is there own implementation with ”Serialize” abstract function and “ToJson” function. In controls they are overriding “Serialize” method which depicts t...

Trim text in MVC Core through Model Binder

Trimming text can be done on client side codes, but I believe it is most suitable on MVC Model Binder since it would be at one place on infrastructure level which would be free from any manual intervention of developer. This would allow every post request to be processed and converted to a trimmed string. Let us start by creating Model binder using Microsoft.AspNetCore.Mvc.ModelBinding; using System; using System.Threading.Tasks; public class TrimmingModelBinder : IModelBinder { private readonly IModelBinder FallbackBinder; public TrimmingModelBinder(IModelBinder fallbackBinder) { FallbackBinder = fallbackBinder ?? throw new ArgumentNullException(nameof(fallbackBinder)); } public Task BindModelAsync(ModelBindingContext bindingContext) { if (bindingContext == null) { throw new ArgumentNullException(nameof(bindingContext)); } var valueProviderResult = bindingContext.ValueProvider.GetValue(bin...

LDAP with ASP.Net Identity Core in MVC with project.json

Lightweight Directory Access Protocol (LDAP), the name itself explain it. An application protocol used over an IP network to access the distributed directory information service. The first and foremost thing is to add references for consuming LDAP. This has to be done by adding reference from Global Assembly Cache (GAC) into project.json "frameworks": { "net461": { "frameworkAssemblies": { "System.DirectoryServices": "4.0.0.0", "System.DirectoryServices.AccountManagement": "4.0.0.0" } } }, These  System.DirectoryServices  and  System.DirectoryServices.AccountManagement  references are used to consume LDAP functionality. It is always better to have an abstraction for irrelevant items in consuming part. For an example, the application does not need to know about PrincipalContext or any other dependent items from those two references to make it extensible. So, we can begin wi...

Configuring Ninject, Asp.Net Identity UserManager, DataProtectorTokenProvider with Owin

It can be bit tricky to configure both Ninject and Asp.Net Identity UserManager if some value is expected from DI to configure UserManager. We will look into configuring both and also use OwinContext to get UserManager. As usual, all configuration need to be done on Startup.cs. It is just a convention but can be used with different name, the important thing is to decorate class with following attribute to make it Owin start-up: [assembly: OwinStartup(typeof(MyProject.Web.Startup))] Ninject configuration Configuring Ninject kernel through method which would be used to register under Owin. Startup.cs public IKernel CreateKernel() { var kernel = new StandardKernel(); try { //kernel.Bind<IHttpModule>().To<HttpApplicationInitializationHttpModule>(); // TODO: Put any other injection which are required. return kernel; } catch { kernel.Dispose(); thro...

Data seed for the application with EF, MongoDB or any other ORM.

Most of ORMs has moved to Code first approach where everything is derived/initialized from codes rather than DB side. In this situation, it is better to set data through codes only. We would be looking through simple technique where we would be Seeding data through Codes. I would be using UnitOfWork and Repository pattern for implementing Data Seeding technique. This can be applied to any data source MongoDB, EF, or any other ORM or DB. Things we would be doing. - Creating a base class for easy usage. - Interface for Seed function for any future enhancements. - Individual seed classes. - Configuration to call all seeds. - AspNet core configuration to Seed data through Seed configuration. Creating a base class for easy usage public abstract class BaseSeed<TModel> where TModel : class { protected readonly IMyProjectUnitOfWork MyProjectUnitOfWork; public BaseSeed(IMyProjectUnitOfWork MyProjectUnitOfWork) { ...

A wrapper implementation for Kendo Grid usage

A wrapper implementation for any heavily used item is always a good practice. Whatever is not written by us and used at a lot of places should be wrapped within specific functionality to keep it future proof and easily changeable. This also encourages DRY principle to keep our common setting at a central place. Kendo UI items are enormous in configuration, one of an issue I find people keep repeating codes for Kendo Grid configuration. They have built very flexible system to have any configuration, but in most of the cases, we do not need all of those complicated configuration. We would try to see a simpler configuration of same. The actual core implementation is bit complex, but we do not have to bother about it once done since the focus is just on usage only. I recommend doing this practice for as simple as jQuery events, form handling or as simple as any notification system. This just won't make things simple but makes codes much more manageable, easy understand, read or open f...

Using Redis distributed cache in dotnet core with helper extension methods

Redis cache is out process cache provider for a distributed environment. It is popular in Azure Cloud solution, but it also has a standalone application to operate upon in case of small enterprises application. How to install Redis Cache on a local machine? Redis can be used as a local cache server too on our local machines. At first install, Chocolatey https://chocolatey.org/ , to make installation of Redis easy. Also, the version under Chocolatey supports more commands and compatible with Official Cache package from Microsoft. After Chocolatey installation hit choco install redis-64 . Once the installation is done, we can start the server by running redis-server . Distributed Cache package and registration dotnet core provides IDistributedCache interface which can be overrided with our own implementation. That is one of the beauties of dotnet core, having DI implementation at heart of framework. There is already nuget package available to override IDistributedCache i...