0

In my ASP.NET Core 3.1 web application, I allow users to upload images that are stored in local directories within the application itself. Whilst, this could be better served using blob storage on Azure, this particular project has called for them to be stored locally, so I have to work with that:

wwwroot/images/products/whatever_id/whatever_name.jpg

and

wwwroot/images/companies/whatever_id/whatever_name.jpg

When a user uploads an image, the processing of the image is handled by ImageSharp from SixLabors where the image is resized a few times for use across the platform and saved to the relative directory which is separated by the Id.

using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Processing;

The Problem

The problem I face is that, whilst this process works when I test my application locally, it doesn't work when I deploy my application to Azure and there are no errors of any kind reported back. This has left me high and dry when trying to work out what is going on.

Assumptions

Due to the nature of this issue and the location of these images, I can only assume that it's azure preventing the overwriting of images within the directories for security reasons, or perhaps it's the ImageSharp library itself.

It's important to note that, the actual creation of products and adding of images when the directories don't exist, so, a new product, works perfectly. It's only if you try to overwrite the image that it doesn't work.

Here is my code, I've removed all none essential elements, leaving on the image processing specific code.

Edit(View)

@model Products
<form asp-action="Edit" asp-controller="Products" method="POST" enctype="multipart/form-data">
    <div class="card m-4">
        <div class="card-header">
            <h3 class="card-title">Add Product</h3>
        </div>
        <div class="card-body">
            <div class="container-fluid">
                <div class="row">
                    <div class="col-md-4">
                        <div class="form-group">
                            <label>Product Images</label>                         
                            <kendo-upload name="ProductImages" multiple="true" show-file-list="true">
                            </kendo-upload>
                        </div>
                    </div>                        
                </div>              
                <div class="row">
                    <div class="col">
                        <button type="submit" class="btn btn-purple">Submit</button>
                    </div>
                </div>
            </div>
        </div>
    </div>
</form>

Edit(Controller)

[HttpPost]
public IActionResult Edit(Products product)
{
    if (ModelState.IsValid && product != null)
    {
        try
        {
            //Process the Images
            if (product.ProductImages != null)
            {
                ProcessImages(product, product.Id);
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
        return RedirectToAction("Index");
    }
    return View();
}

ProcessImages(Stream)

private readonly int[] sizeArray = new int[] { 700, 350, 150 };

public Stream ProcessImages(Products model, int? id)
{
    try
    {
        //Get the images and define the directory structure
        var images = model.ProductImages;
        var root = _env.WebRootPath;
        var folderName = Path.Combine(root, "images", "products", id.ToString());

        //If the ID Directory doesn't exist, create it first.
        if (!Directory.Exists(folderName))
        {
            Directory.CreateDirectory(folderName);
        }
        //Interate over the images and process them
        foreach (var item in images)
        {
            foreach (var imageSize in sizeArray)
            {
                string imageSizeName = "image" + imageSize + ".jpg";
                string fullPath = Path.Combine(folderName, imageSizeName);
                //Create the stream and process the image
                using FileStream fileStream = new FileStream(fullPath, FileMode.Create);

                try
                {
                    Stream inStream = item.OpenReadStream();
                    using Image image = Image.Load(inStream);
                    int width = imageSize;
                    int height = 0;
                    var clone = image.Clone(i => i.Resize(width, height));
                    clone.SaveAsJpeg(fileStream);
                    inStream.Position = 0;
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.Message);
                }
            }
        }
    }
    catch (Exception ex)
    {
       Console.WriteLine(ex.Message);
    }
    return (null);
}

As you can see, there is a size array, where the sizes we need are defined and then looped over and processed. We create the file name and save as a jpg. The height is set to 0 so that it automatically sets it when the width is defined and then the stream is reset.

Products.cs(model)

    public class Products : BaseEntity
    {        
        public string Title { get; set; }

        [NotMapped]
        public IFormFileCollection ProductImages { get; set; }

    }

So, the question remains, why can I not overwrite my images once my application is live in Azure? Is it an Azure security concern, something simple like an ImageSharp library issue or is my application not performing this action correctly?

Yanayaya
  • 2,044
  • 5
  • 30
  • 67
  • Maybe the images are cached, and since you are using the same name the old ones are being served from the cache. Try refreshing the page with Ctrl+F5, or change your caching timing in case you have any caching. – LazZiya Feb 22 '21 at 20:42
  • @LazZiya thank you for the suggestion but that has not worked – Yanayaya Feb 23 '21 at 09:18

1 Answers1

1

That code does not look correct. In ProcessImages

  • You're loading the image then cloning it for each size (load outside the loop)
  • You're not disposing of the clone after saving it to the stream.
  • You're always returning null.

@LazZiya is correct here though, regarding caching. Since you're reusing the same name over again, the browser will simply request the same cached image. If you add any querystring parameter in your e.g. v=[PRODUCTRECORD_LASTWRITETIME] you will get the new image.

For simplicities sake I recommend you simply upload the source image and use the ImageSharp.Web middleware to serve your resized images.

This will automatically handle source image changes and reduce storage overheads. You can host your source images on the server and the cached result in blob storage.

requests become as simple as

https://PATH_TO_IMAGE?with=[NUMBER]

James South
  • 10,147
  • 4
  • 59
  • 115