Search code examples
c#asp.net-mvcfileasp.net-coreweb

I want to pre-fill an input of type IFormFile


I'm doing an Edit page for some products that have an ImagePath property. When I create the product, I use an input of type IFormFile to download the image in the /img/products folder of the website and it works perfectly fine. Here it is for context:

[HttpPost]
public async Task<IActionResult> CreateProduct(AdminCreateProductVM vm)
{
    if (!ModelState.IsValid)
        return View(vm);

    bool productAlreadyExists = _context.Products.Any(u => u.SKU == vm.SKU);

    if (productAlreadyExists)
    {
        ModelState.AddModelError(string.Empty, "Un produit avec le même SKU existe déjà.");
        return View(vm);
    }

    if (vm.Image is not null && vm.Image.Length > 0)
    {
        var webRootPath = _hostingEnvironment.WebRootPath;
        var filePath = Path.Combine(webRootPath, "img/products", vm.SKU! + ".jpg");

        using (var stream = new FileStream(filePath, FileMode.Create))
        {
            await vm.Image.CopyToAsync(stream);
        }
    }
    else
    {
        ModelState.AddModelError(string.Empty, "Veuillez sélectionner une image.");
        return View(vm);
    }

    var product = new Product()
    {
        SKU = vm.SKU,
        Name = vm.Name,
        Brand = vm.Brand,
        Model = vm.Model,
        ImagePath = "~/img/products/" + vm.SKU + ".jpg",
        WeightInGrams = vm.WeightInGrams,
        Size = vm.Size,
        Quantity = vm.Quantity,
        RetailPrice = vm.RetailPrice,
        DiscountedPrice = vm.DiscountedPrice,
        ShortDescription = vm.ShortDescription,
        FullDescription = vm.FullDescription,
        PublicationDate = (DateTime?)DateTime.Now,
        LastUpdated = (DateTime?)DateTime.Now,
        IsArchived = false
    };

    _context.Products.Add(product);
    _context.SaveChanges();

    return RedirectToAction(nameof(ManageProduct));
}

The problem is when I want to modify the product, I want to fill the IFormFile input with the image that is currently associated with the product by default, but leave the possibiliy to change it by uploading a new one. I've tried multiple things so that the ViewModel contains an IFormFile when loading the new page, and it all didn't work. The code that I have currenlty (see bellow) create the IFormFile object with everything needed property-wise, but when the page is loading, the input isn't pre-filled.

public IActionResult EditProduct(Guid id)
{
    ViewBag.Id = id;

    var toEdit = _context.Products.Find(id);

    if (toEdit is null)
        throw new ArgumentOutOfRangeException(nameof(id));

    IFormFile? toEditImage = null;
    string toEditFullImagePath = _hostingEnvironment.WebRootPath + toEdit.ImagePath![1..];

    if (System.IO.File.Exists(toEditFullImagePath))
    {
        using (FileStream fileStream = new FileStream(toEditFullImagePath, FileMode.Open, FileAccess.Read))
        {
            var fileName = Path.GetFileName(toEditFullImagePath);

            var provider = new FileExtensionContentTypeProvider();
            if (!provider.TryGetContentType(fileName, out var contentType))
            {
                contentType = "application/octet-stream"; // Utilisez un type MIME par défaut si l'extension n'est pas reconnue.
            }

            var headers = new HeaderDictionary
            {
                ["Content-Disposition"] = new StringValues("form-data; name=\"Image\"; filename=\"" + fileName + "\""),
                ["Content-Type"] = new StringValues(contentType)
            };

            toEditImage = new FormFile(fileStream, 0, fileStream.Length, fileName, fileName)
            {
                Headers = headers
            };
        }
    }
    else
    {
        ModelState.AddModelError(string.Empty, "L'image du produit n'a pas été trouvée.");
    }

    var vm = new AdminEditProductVM
    {
        SKU = toEdit.SKU,
        Name = toEdit.Name,
        Brand = toEdit.Brand,
        Model = toEdit.Model,
        Image = toEditImage,
        WeightInGrams = toEdit.WeightInGrams,
        Size = toEdit.Size,
        Quantity = toEdit.Quantity,
        RetailPrice = toEdit.RetailPrice,
        DiscountedPrice = toEdit.DiscountedPrice,
        ShortDescription = toEdit.ShortDescription,
        FullDescription = toEdit.FullDescription
    };

    return View(vm);
}

If you need any code from the html input or the ViewModel, let me know. Thank you in advance!


Solution

  • You cannot programmatically set default chosen file in browser for security reasons. It would be a serious security flaw. The user must manually select a file through the file input field.

    You can set a hidden input for ImagePath, when you do not want to change the product image, you can do not select a file and then post to the backend to judge the file is null or not. If the file input is not null, you can do something like CreateProduct did, to update the ImagePath value. If it is null, just invoke database update query.