Reputation: 2184
I've had a look around and whilst I can see many guides on how to upload to Azure Blob Storage, they all seem to stop short of retrieving your data once you have done it. In my scenario I have an application where the user can upload an image to a car when they create it, I have created a service for my blob storage which I utilize with dependency injection.
When the user attempts to edit the car, I expect them to see the details and the related image. What I'm struggling with is returning that image, I'm almost there I think but not sure what to return my information as.
Here is my code starting with the submission form.
Create
@model Car
<div class="row">
<div class="col">
<form asp-action="Create" asp-controller="Car" method="post" enctype="multipart/form-data">
<div class="row">
<div class="col">
<div class="md-form form-group">
<label asp-for="Name"></label>
<input type="text" class="form-control" asp-for="Name" />
</div>
<div class="md-form form-group">
<label asp-for="ImageFile" class="active">Image</label>
<!-- Image Upload-->
<kendo-upload name="ImageFile" show-file-list="true">
</kendo-upload>
</div>
</div>
</div>
<div class="row">
<div class="col">
<hr />
<button type="submit" class="btn btn-success">Submit</button>
<button type="reset" class="btn btn-amber">Reset</button>
</div>
</div>
</form>
</div>
</div>
Here are the corresponding methods for that Create view. As you'll see I open with the dependency injection and then utilize it within the method. This is only for the uploading of the blob and it's all working well, this is to provide the full context of my problem.
CarController.cs
private readonly ICarService _carService;
private readonly IWebHostEnvironment _env;
private readonly IConfiguration _configuration;
public CarController(
IWebHostEnvironment env,
ICarService carService,
IConfiguration configuration)
{
_carService = carService;
_env = env;
_configuration = configuration;
}
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Create(Car car)
{
if (ModelState.IsValid)
{
//
//Create Car First
_carService.InsertCar(car);
//
//Get the id if the car just created
int id = car.Id;
//
//If file data is there, prepare and upload to
//blob storage.
if (car.ImageFile != null)
{
string category = "car";
var fileName = "car-image.jpg";
byte[] fileData = new byte[car.ImageFile.Length];
string mimeType = car.ImageFile.ContentType;
BlobStorageService objBlobService = new BlobStorageService(_configuration.GetConnectionString("AzureStorage"));
car.ImagePath = objBlobService.UploadFileToBlob(
category,
id,
fileName,
fileData,
mimeType);
}
return RedirectToAction(nameof(Index));
}
return View(car);
}
The next part is where I'm struggling. I need to show the uploaded image on the edit page. Here is the method I started writing to do this, it's incomplete and I need help from this point onwards.
Edit
[HttpGet]
public IActionResult Edit(int id)
{
//
//Access the service and pass the car ID to it
BlobStorageService objBlob = new BlobStorageService(_configuration.GetConnectionString("AzureStorage"));
objBlob.GetBlobData(id.ToString());
//
//Get car data from the repository
var data = _carService.GetCar(id);
return View(data);
}
Here is my blob storage service which handles most of this. As you can see when the ID is passed from the edit page it is used to form the directory structure on Azure to find the relative image. I'm returning the attributes but that's not right.
using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Blob;
using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.Extensions.Configuration;
namespace MyProject.Services
{
public class BlobStorageService
{
string accessKey = string.Empty;
public BlobStorageService(string endPoint)
{
accessKey = endPoint;
}
public string UploadFileToBlob(string category, int id, string strFileName, byte[] fileData, string fileMimeType)
{
try
{
var _task = Task.Run(() => this.UploadFileToBlobAsync(category, id, strFileName, fileData, fileMimeType));
_task.Wait();
string fileUrl = _task.Result;
return fileUrl;
}
catch (Exception)
{
throw;
}
}
public async void GetBlobData(string id)
{
CloudStorageAccount cloudStorageAccount = CloudStorageAccount.Parse(accessKey);
CloudBlobClient cloudBlobClient = cloudStorageAccount.CreateCloudBlobClient();
string strContainerName = "uploads";
CloudBlobContainer cloudBlobContainer = cloudBlobClient.GetContainerReference(strContainerName);
string pathPrefix = "car/" + id;
CloudBlobDirectory blobDirectory = cloudBlobContainer.GetDirectoryReference(pathPrefix);
// get block blob reference
CloudBlockBlob blockBlob = blobDirectory.GetBlockBlobReference("car-image.jpg");
await blockBlob.FetchAttributesAsync();
}
private async Task<string> UploadFileToBlobAsync(string category, int id, string strFileName, byte[] fileData, string fileMimeType)
{
try
{
string strContainerName = "uploads";
string fileName = category + "/" + id + "/" + strFileName;
CloudStorageAccount cloudStorageAccount = CloudStorageAccount.Parse(accessKey);
CloudBlobClient cloudBlobClient = cloudStorageAccount.CreateCloudBlobClient();
CloudBlobContainer cloudBlobContainer = cloudBlobClient.GetContainerReference(strContainerName);
if (await cloudBlobContainer.CreateIfNotExistsAsync().ConfigureAwait(false))
{
await cloudBlobContainer.SetPermissionsAsync(new BlobContainerPermissions { PublicAccess = BlobContainerPublicAccessType.Blob }).ConfigureAwait(false);
}
if (fileName != null && fileData != null)
{
CloudBlockBlob cloudBlockBlob = cloudBlobContainer.GetBlockBlobReference(fileName);
cloudBlockBlob.Properties.ContentType = fileMimeType;
await cloudBlockBlob.UploadFromByteArrayAsync(fileData, 0, fileData.Length).ConfigureAwait(false);
return cloudBlockBlob.Uri.AbsoluteUri;
}
return "";
}
catch (Exception ex)
{
throw (ex);
}
}
}
}
So, the question is, how do you return the URL for the image so that I can place it in an img src
tag or download it for display? I've got myself a bit stuck and not sure. Whilst the directory structure works fine and it finds the file...I don't know how to utilise it.
Upvotes: 2
Views: 8079
Reputation: 41
You need to generate an SAS token for that. This method will help you
public static string GetBlobUri(string imageUrl)
{
var accessKey = DataHelper.GetValueFromAppSettingsBySectionAndKey("BlobStorage", "AccessKey");
var containerName = DataHelper.GetValueFromAppSettingsBySectionAndKey("BlobStorage", "ContainerName");
var container = new BlobContainerClient(accessKey, containerName);
var blobClient = container.GetBlobClient(imageUrl);
if (blobClient != null)
{
if (blobClient.CanGenerateSasUri)
{
// Create a SAS token that's valid for one minute
BlobSasBuilder sasBuilder = new BlobSasBuilder()
{
BlobContainerName = blobClient.GetParentBlobContainerClient().Name,
BlobName = blobClient.Name,
Resource = "b"
};
sasBuilder.ExpiresOn = DateTimeOffset.UtcNow.AddMinutes(5);
sasBuilder.SetPermissions(BlobContainerSasPermissions.Read);
Uri sasURI = blobClient.GenerateSasUri(sasBuilder);
return sasURI.OriginalString;
}
}
return string.Empty;
}
Upvotes: 0
Reputation: 52699
Typically you do not return a URL at all, you return a huge block of bytes, a content-type saying "image/jpeg" (or similar) and that's what gets put in the browser. Your img src
property just needs to refer to the controller action that returns those bytes.
eg a controller method that returns a file, without using the static files serving that's built into kestrel:
[ResponseCache(Duration = 1200, Location = ResponseCacheLocation.Client)]
public IActionResult GetImage(int id)
{
// determine file path from id and then
return PhysicalFile(path, "image/jpeg");
}
Now you can't return a PhysicalFile, but it only reads the bytes from the disk and returns them - its the same for any file, just change the content type. Try a FileResult, copying the returned data:
return File(bytes, "image/jpeg", fileName);
Upvotes: 1
Reputation: 450
Shortly: CloudBlockBlob
has properties Uri
& StorageUri
. First one directly points to file, second one allows to select primary or secondadry url.
There shouldn't be any problems if allow anonymous read of your blobs (configured on Azure Portal)
Upvotes: 0