Using Telerik UI for ASP.NET MVC with Azure Blob Storage
The Azure Blob Storage is Microsoft's object storage solution for the cloud. The Blob storage is optimized for storing massive amounts of unstructured data (data that doesn't adhere to a particular data model or definition, such as text or binary data).
This article demonstrates how to upload images with the Upload component for ASP.NET MVC to Azure Blob Storage and view the uploaded images in a ASP.NET MVC ListView.
Prerequisites
To use Telerik UI for ASP.NET MVC with Azure Blob Storage, you need:
- An Azure account
- An Azure Storage Account
- An Azure Storage Container
- A web application configured to use Telerik UI for ASP.NET MVC components
Setting up Container Permissions
After you create the Storage Container, set the permission level. This allows you to access the uploaded images:
- Navigate to your storage account and select the container under Data Storage > Containers.
- Click on Change access level and select the desired access level:
Configuring the Web Application
Add the Azure account and the Azure Storage Container details to the appsettings.json
of the application as they will be used to access the Container.
{
"ApplicationInsights": {
"InstrumentationKey": ""
},
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Debug",
"System": "Information",
"Microsoft": "Information"
}
},
"AzureStorageConfig": {
"AccountName": "<Your Account Name>",
"AccountKey": "<Your Account Key>",
"ImageContainer": "<Name of the created container>",
"ConnectionString": "<Connention string>"
}
}
For clarity, this tutorial demonstrates how to store the endpoint URI and account Key in the
appsettings.json
. For production application hosted on Azure, it is strongly recommended to store sensitive data in Azure Key Vault.
You can find the account key and connection string by navigating to your storage account—under Security + networking > Access Keys
Adding the Upload and ListView on the Client
The following configuration demonstrates how to add an Upload component with enabled validation for jpg
and png
files. The Success event handler of the Upload is used to refresh the ListView.
<script type="text/x-kendo-tmpl" id="template">
<div class="img">
<img src="#:Url#" width="150" height="150" alt="#:Name# image" />
<h4>#:Name#</h4>
</div>
</script>
@(Html.Kendo().ListView<ImageViewModel>()
.Name("listView")
.TagName("div")
.ClientTemplateId("template")
.DataSource(dataSource => dataSource
.Ajax()
.Read(read => read.Action("GetImageDetails", "Images"))
.PageSize(5)
)
.Pageable(pageable => pageable
.Refresh(true)
.ButtonCount(5)
.PageSizes(new[] { 5, 15, 21 })
)
)
@(Html.Kendo().Upload()
.Name("files")
.Async(a=>a
.Save("Save","Images")
.Remove("Remove","Images")
.AutoUpload(false)
)
.Validation(validation => validation.AllowedExtensions(new string[]{".jpg", ".png"}))
.Events(ev=>ev.Success("onSuccess"))
)
<script>
function onSuccess(e){
var listView = $("#listView").getKendoListView();
listView.dataSource.read();
}
</script>
<style>
.k-listview-content{
display:flex;
flex-direction:row;
gap:20px;
}
.img h4 {
line-height: 1.1em;
font-size: .9em;
font-weight: normal;
text-transform: uppercase;
color: #999;
}
</style>
Configuring the Server-Side
The following server-side logic demonstrates the server-side configuration of the Upload and the Listview components:
- The
Save
andRemove
endpoints of the Upload component demonstrate the configuration required to save uploaded images in the Azure Storage Container or remove them, respectively. - The
GetImageDetails
action method demonstrates the configuration for the ListView endpoint.
public class ImagesController : Controller
{
private readonly AzureStorageConfig storageConfig = null;
public ImagesController(IOptions<AzureStorageConfig> config)
{
storageConfig = config.Value;
}
[HttpPost]
public async Task<IActionResult> Save(ICollection<IFormFile> files)
{
bool isUploaded = false;
try
{
if (files.Count == 0)
return BadRequest("No files received from the upload");
if (storageConfig.AccountKey == string.Empty || storageConfig.AccountName == string.Empty)
return BadRequest("sorry, can't retrieve your azure storage details from appsettings.js, make sure that you add azure storage details there");
if (storageConfig.ImageContainer == string.Empty)
return BadRequest("Please provide a name for your image container in the azure blob storage");
foreach (var formFile in files)
{
if (formFile.Length > 0)
{
using (Stream stream = formFile.OpenReadStream())
{
isUploaded = await UploadFileToStorage(stream, formFile.FileName, storageConfig);
}
}
}
if (isUploaded)
{
return new AcceptedResult();
}
else
return BadRequest("Look like the image couldnt upload to the storage");
}
catch (Exception ex)
{
return BadRequest(ex.Message);
}
}
public static async Task<bool> UploadFileToStorage(Stream fileStream, string fileName,
AzureStorageConfig _storageConfig)
{
// Create a URI to the blob
Uri blobUri = new Uri("https://" +
_storageConfig.AccountName +
".blob.core.windows.net/" +
_storageConfig.ImageContainer +
"/" + fileName);
// Create StorageSharedKeyCredentials object by reading
// the values from the configuration (appsettings.json)
StorageSharedKeyCredential storageCredentials =
new StorageSharedKeyCredential(_storageConfig.AccountName, _storageConfig.AccountKey);
// Create the blob client.
BlobClient blobClient = new BlobClient(blobUri, storageCredentials);
var fileBytes = ReadFully(fileStream);
var data = new BinaryData(fileBytes);
// Upload the file
await blobClient.UploadAsync(data);
return await Task.FromResult(true);
}
private static byte[] ReadFully(Stream input)
{
using (MemoryStream ms = new MemoryStream())
{
input.CopyTo(ms);
return ms.ToArray();
}
}
[HttpPost]
public async Task<ActionResult> Remove(string[] fileNames)
{
// The parameter of the Remove action must be called "fileNames"
if (fileNames != null)
{
foreach (var fullName in fileNames)
{
await RemoveFileFileFromStorage(fullName, storageConfig);
}
}
// Return an empty string to signify success
return Content("");
}
public static async Task<bool> RemoveFileFileFromStorage(string fileName,
AzureStorageConfig _storageConfig)
{
// Create BlobServiceClient from the account URI
BlobServiceClient blobServiceClient = new BlobServiceClient(_storageConfig.ConnectionString);
// Get reference to the container
BlobContainerClient container = blobServiceClient.GetBlobContainerClient(_storageConfig.ImageContainer);
//Remove blob matching the fileName
await container.DeleteBlobAsync(fileName, DeleteSnapshotsOption.None);
return await Task.FromResult(true);
}
public async Task<ActionResult> GetImageDetails([DataSourceRequest] DataSourceRequest request)
{
var data = await GetUploadedImages(storageConfig);
return Json(data.ToDataSourceResult(request));
}
public static async Task<List<ImageViewModel>> GetUploadedImages(AzureStorageConfig _storageConfig)
{
List<ImageViewModel> images = new List<ImageViewModel>();
// Create BlobServiceClient from the account URI
BlobServiceClient blobServiceClient = new BlobServiceClient(_storageConfig.ConnectionString);
// Get reference to the container
BlobContainerClient container = blobServiceClient.GetBlobContainerClient(_storageConfig.ImageContainer);
if (container.Exists())
{
var data = container.GetBlobs();
foreach (BlobItem blobItem in container.GetBlobs())
{
images.Add(new ImageViewModel() { Name = blobItem.Name, Url = container.Uri + "/" + blobItem.Name });
}
}
return await Task.FromResult(images);
}
}
public class AzureStorageConfig
{
public string AccountName { get; set; }
public string AccountKey { get; set; }
public string ImageContainer { get; set; }
public string ConnectionString { get; set; }
}