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
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.
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.
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.
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.
FilesUploadStatus: File upload status to gets all valid statistics for file uploads on the server.
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.
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.
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.
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.
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.
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.
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
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.
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
Post a Comment