Search code examples
asp.netazureasp.net-identityazure-blob-storage

Updating Image In Azure Blob Storage using asp.net Identity


I'm currently able to upload and display an image from my blob storage but i'm struggling to find a way to 'update/replace' that image if a user would like to change it. I'm happy with either of two methods:

  1. Replace the image and keep the same url
  2. Upload a new image and reference a new url in the database

In the controller i'm using Dependency Injection for the photoService:

MANAGE CONTROLLER

//
    // GET: /Manage/Index
    public async Task<ActionResult> Index(ManageMessageId? message)
    {
        ViewBag.StatusMessage =
            message == ManageMessageId.ChangePasswordSuccess ? "Your password has been changed."
            : message == ManageMessageId.SetPasswordSuccess ? "Your password has been set."
            : message == ManageMessageId.SetTwoFactorSuccess ? "Your two-factor authentication provider has been set."
            : message == ManageMessageId.Error ? "An error has occurred."
            : message == ManageMessageId.AddPhoneSuccess ? "Your phone number was added."
            : message == ManageMessageId.RemovePhoneSuccess ? "Your phone number was removed."
            : "";

        var userId = User.Identity.GetUserId();
        var model = new IndexViewModel
        {
            HasPassword = HasPassword(),
            PhoneNumber = await UserManager.GetPhoneNumberAsync(userId),
            TwoFactor = await UserManager.GetTwoFactorEnabledAsync(userId),
            Logins = await UserManager.GetLoginsAsync(userId),
            BrowserRemembered = await AuthenticationManager.TwoFactorBrowserRememberedAsync(userId)
        };


        // PhotoService
        var user = new ApplicationUser
        {
            PhotoUrl = await _photoService.UploadPhotoAsync(model.Photo)
        };
        await UserManager.CreateAsync(user);
        // PhotoService END



        return View(model);
    }

SERVICE

public class PhotoService : IPhotoService
{
    public async void CreateAndConfigureAsync()
    {
        try
        {
            CloudStorageAccount storageAccount = StorageUtils.StorageAccount;

            // Create a blob client and retrieve reference to images container
            CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient();
            CloudBlobContainer container = blobClient.GetContainerReference("images");

            // Create the "images" container if it doesn't already exist.
            if (await container.CreateIfNotExistsAsync())
            {
                // Enable public access on the newly created "images" container
                await container.SetPermissionsAsync(
                    new BlobContainerPermissions
                    {
                        PublicAccess =
                            BlobContainerPublicAccessType.Blob
                    });

                // Logging
            }
        }
        catch (Exception ex)
        {
            // Logging
        }
    }

    public async Task<string> UploadPhotoAsync(HttpPostedFileBase photoToUpload)
    {
        if (photoToUpload == null || photoToUpload.ContentLength == 0)
        {
            return null;
        }

        string fullPath = null;
        Stopwatch timespan = Stopwatch.StartNew();

        try
        {
            CloudStorageAccount storageAccount = StorageUtils.StorageAccount;

            // Create the blob client and reference the container
            CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient();
            CloudBlobContainer container = blobClient.GetContainerReference("images");

            // Create a unique name for the images we are about to upload
            string imageName = String.Format("task-photo-{0}{1}",
                Guid.NewGuid().ToString(),
                Path.GetExtension(photoToUpload.FileName));

            // Upload image to Blob Storage
            CloudBlockBlob blockBlob = container.GetBlockBlobReference(imageName);
            blockBlob.Properties.ContentType = photoToUpload.ContentType;
            await blockBlob.UploadFromStreamAsync(photoToUpload.InputStream);

            // Convert to be HTTP based URI (default storage path is HTTPS)
            var uriBuilder = new UriBuilder(blockBlob.Uri);
            uriBuilder.Scheme = "http";
            fullPath = uriBuilder.ToString();

            timespan.Stop();
            //log.TraceApi("Blob Service", "PhotoService.UploadPhoto", timespan.Elapsed, "imagepath={0}", fullPath);
        }
        catch (Exception ex)
        {
            //log.Error(ex, "Error upload photo blob to storage");
        }

        return fullPath;
    }
}

INTERFACE

public interface IPhotoService
{
    void CreateAndConfigureAsync();
    Task<string> UploadPhotoAsync(HttpPostedFileBase photoToUpload);
}

MODEL

public class IndexViewModel
{
    public bool HasPassword { get; set; }
    public IList<UserLoginInfo> Logins { get; set; }
    public string PhoneNumber { get; set; }
    public bool TwoFactor { get; set; }
    public bool BrowserRemembered { get; set; }
    public HttpPostedFileBase Photo { get; set; }
    public string PhotoUrl { get; set; }
}

VIEW

@using Microsoft.AspNet.Identity
@model AzureBlobStorageTest.Models.IndexViewModel
@{
ViewBag.Title = "Manage";
}

<h2>@ViewBag.Title.</h2>

 <p class="text-success">@ViewBag.StatusMessage</p>
 <div>
<h4>Change your account settings</h4>
<h5>Image:</h5>
<img src="@(Model.PhotoUrl)" alt="Photo"/>


@using (Html.BeginForm("Index", "Manage", FormMethod.Post, new { role = "form", enctype = "multipart/form-data" }))
{
    @Html.AntiForgeryToken()
    @Html.ValidationSummary("", new { @class = "text-danger" })
    <div class="form-group">
        @Html.LabelFor(m => m.Photo, new { @class = "col-md-2 control-label" })
        <div class="col-md-10">
            @Html.TextBoxFor(m => m.Photo, new { type = "file" })
        </div>
    </div>
    <div class="form-group">
        <div class="col-md-offset-2 col-md-10">
            <input type="submit" class="btn btn-default" value="Register" />
        </div>
    </div>
}
</div>

It would be great if someone could help me with this as I've been trying all sorts of ways but having no luck.

Please let me know if you require any further info.

Thanks


Solution

  • As Thomas commented that CloudBlockBlob.UploadFromStreamAsync would upload a stream to a block blob and if the blob already exists, it will be overwritten. But your blob name is unique under the UploadPhotoAsync method as follows:

    string imageName = String.Format("task-photo-{0}{1}",
                Guid.NewGuid().ToString(),
                Path.GetExtension(photoToUpload.FileName));
    

    I would recommend you define a new method (e.g. DeletePhotoAsync) under IPhotoService, and retrieve the existing image then delete it before invoking the UploadPhotoAsync method for uploading the new image.

    Or you could add a optional parameter names photoUrl for the UploadPhotoAsync method, and if the photoUrl is not null or empty, then you could initialize your imageName as follows:

    imageName = new CloudBlockBlob(new Uri($"{photoUrl}")).Name;
    

    UPDATE:

    Your PhotoService would look like this:

    public class PhotoService:IPhotoService
    {
        CloudBlobContainer container;
        public PhotoService()
        {
            CloudStorageAccount storageAccount = StorageUtils.StorageAccount;
            // Create a blob client and retrieve reference to images container
            CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient();
            container= blobClient.GetContainerReference("images");
        }
    
        //photoUrl: https://<account-name>.blob.core.windows.net/images/task-photo-09e0e292-8df2-4630-81a5-cb4977eef1f9.png
        public async Task<bool> DeletePhotoAsync(string photoUrl)
        {
            string blobName = new CloudBlockBlob(new Uri(photoUrl)).Name;
            var targetBlob = container.GetBlockBlobReference(blobName);
            return await targetBlob.DeleteIfExistsAsync();
        }
    
        public async void CreateAndConfigureAsync()
        {
          try
          {
            // Create the "images" container if it doesn't already exist.
            if (await container.CreateIfNotExistsAsync())
            {
                // Enable public access on the newly created "images" container
                await container.SetPermissionsAsync(
                    new BlobContainerPermissions
                    {
                        PublicAccess =
                            BlobContainerPublicAccessType.Blob
                    });
    
                // Logging
            }
         }
         catch (Exception ex)
         {
            // Logging
         }
      }
    
      //UploadPhotoAsync
    }