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?