Add a file upload to an ASP.NET project

Hi, I've kind of messed up and mistook a deadline on a project for anther, and suddenly found myself very behind on work, and I will need some help. We have a web store project which lets you add products as a seller and then buy them as a buyer. This works fine, but we want to add pictures of the products aswell. This is in our controller:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("Id,Name,Description,Price")] Product product)
{
product.SellerId = _userManager.GetUserId(User);

if (ModelState.IsValid)
{
_context.Add(product);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
return View(product);
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("Id,Name,Description,Price")] Product product)
{
product.SellerId = _userManager.GetUserId(User);

if (ModelState.IsValid)
{
_context.Add(product);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
return View(product);
}
This is our model:
public class Product
{
public int Id { get; set; }
[Display(Name = "Nazwa")]
public string Name { get; set; }
[Display(Name = "Opis")]
public string Description { get; set; }
//public int CategoryId { get; set; }
[Display(Name = "Cena")]
public decimal Price { get; set; }

[ForeignKey(nameof(Seller))]
[ValidateNever]
public string SellerId { get; set; }
[ValidateNever]
public virtual ApplicationUser Seller { get; set; }

public int PictureId { get; set; }
}
public class Product
{
public int Id { get; set; }
[Display(Name = "Nazwa")]
public string Name { get; set; }
[Display(Name = "Opis")]
public string Description { get; set; }
//public int CategoryId { get; set; }
[Display(Name = "Cena")]
public decimal Price { get; set; }

[ForeignKey(nameof(Seller))]
[ValidateNever]
public string SellerId { get; set; }
[ValidateNever]
public virtual ApplicationUser Seller { get; set; }

public int PictureId { get; set; }
}
Now, I'm somewhat sure PictureId will not be good enough in this case? I would either need picture data itself in order to throw a data:image sort of thing into the page, or a path to the picture. I am attempting to follow https://learn.microsoft.com/en-us/aspnet/core/mvc/models/file-uploads?view=aspnetcore-6.0#upload-small-files-with-buffered-model-binding-to-a-database, but as with most Microsoft guides, I sadly find it too technical to really follow. I get lost easily with what part of the code is what '^^
Upload files in ASP.NET Core
How to use model binding and streaming to upload files in ASP.NET Core MVC.
1 Reply
RoboticOperatingBuddy
okay scratch the above, I didn't go with saving to database, I went with saving locally instead. I know local saving from another project I did with PHP, so I'm much more comfy with this concept. I did still run into an issue however, while the new view does show the proper picture field, uploading it doesn't work. I'm receiving "The picture field is required" on clicking "Create", with no apparent way to continue. Any ideas? Current model:
public class Product
{
public int Id { get; set; }
[Display(Name = "Nazwa")]
public string Name { get; set; }
[Display(Name = "Opis")]
public string Description { get; set; }
//public int CategoryId { get; set; }
[Display(Name = "Cena")]
public decimal Price { get; set; }

[ForeignKey(nameof(Seller))]
[ValidateNever]
public string SellerId { get; set; }
[ValidateNever]
public virtual ApplicationUser Seller { get; set; }

public string PictureLink { get; set; }

[Display(Name = "Zdjęcie")]
[NotMapped]
public IFormFile PictureFile { get; set; }
}
public class Product
{
public int Id { get; set; }
[Display(Name = "Nazwa")]
public string Name { get; set; }
[Display(Name = "Opis")]
public string Description { get; set; }
//public int CategoryId { get; set; }
[Display(Name = "Cena")]
public decimal Price { get; set; }

[ForeignKey(nameof(Seller))]
[ValidateNever]
public string SellerId { get; set; }
[ValidateNever]
public virtual ApplicationUser Seller { get; set; }

public string PictureLink { get; set; }

[Display(Name = "Zdjęcie")]
[NotMapped]
public IFormFile PictureFile { get; set; }
}
Current controller:
public async Task<IActionResult> Create([Bind("Id,Name,Description,Price,PictureFile")] Product product)
{
product.SellerId = _userManager.GetUserId(User);

if (ModelState.IsValid)
{
//New filename for uploaded picture
var fileName = Guid.NewGuid().ToString() + Path.GetExtension(product.PictureFile.FileName);
var filePath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot/images/", fileName);

//Save file locally
using (var fileStream = new FileStream(filePath, FileMode.Create))
{
await product.PictureFile.CopyToAsync(fileStream);
}

product.PictureLink = fileName;

_context.Add(product);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
return View(product);
}
public async Task<IActionResult> Create([Bind("Id,Name,Description,Price,PictureFile")] Product product)
{
product.SellerId = _userManager.GetUserId(User);

if (ModelState.IsValid)
{
//New filename for uploaded picture
var fileName = Guid.NewGuid().ToString() + Path.GetExtension(product.PictureFile.FileName);
var filePath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot/images/", fileName);

//Save file locally
using (var fileStream = new FileStream(filePath, FileMode.Create))
{
await product.PictureFile.CopyToAsync(fileStream);
}

product.PictureLink = fileName;

_context.Add(product);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
return View(product);
}
View:
@model Web_Store.Models.Product

@{
ViewData["Title"] = "Create";
}

<h1>Create</h1>

<h4>Product</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Create">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Name" class="control-label"></label>
<input asp-for="Name" class="form-control" />
<span asp-validation-for="Name" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Description" class="control-label"></label>
<input asp-for="Description" class="form-control" />
<span asp-validation-for="Description" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Price" class="control-label"></label>
<input asp-for="Price" class="form-control" />
<span asp-validation-for="Price" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="PictureFile" class="control-label"></label>
<input asp-for="PictureFile" class="form-control" type="file"/>
<span asp-validation-for="PictureFile" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary" />
</div>
</form>
</div>
</div>

<div>
<a asp-action="Index">Back to List</a>
</div>

@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
@model Web_Store.Models.Product

@{
ViewData["Title"] = "Create";
}

<h1>Create</h1>

<h4>Product</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Create">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Name" class="control-label"></label>
<input asp-for="Name" class="form-control" />
<span asp-validation-for="Name" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Description" class="control-label"></label>
<input asp-for="Description" class="form-control" />
<span asp-validation-for="Description" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Price" class="control-label"></label>
<input asp-for="Price" class="form-control" />
<span asp-validation-for="Price" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="PictureFile" class="control-label"></label>
<input asp-for="PictureFile" class="form-control" type="file"/>
<span asp-validation-for="PictureFile" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary" />
</div>
</form>
</div>
</div>

<div>
<a asp-action="Index">Back to List</a>
</div>

@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
It worked! I noticed that the ModelView.IsValid returned false, and the errors were pointing to PictureLink. So I just made that Nullable, and now the picture has successfully been uploaded :D Sadly now I'm struggling to show the picture, in a way. I have 2 records, one where PictureLink is NULL, one where it's a filename. Seems that no matter what condition I put into the if statement, it always seems to return the same value for both...
@if (String.IsNullOrEmpty(Html.DisplayFor(modelItem => item.PictureLink).ToString()))
{
<td>
Brak obrazka
</td>
}
else
{
<td>
<a href="images/@Html.DisplayFor(modelItem => item.PictureLink)">Podgląd obrazka</a>
</td>
}
@if (String.IsNullOrEmpty(Html.DisplayFor(modelItem => item.PictureLink).ToString()))
{
<td>
Brak obrazka
</td>
}
else
{
<td>
<a href="images/@Html.DisplayFor(modelItem => item.PictureLink)">Podgląd obrazka</a>
</td>
}
swear to god I need a rubber ducky, because I keep finding solutions for my problems like right after I post here '^^ swapped the if statement for @if (String.IsNullOrEmpty(item.PictureLink)) works I will have another go, this time I'm properly out of ideas My create function adds the picture fine, but for some reason the Edit doesn't work? Here's the controller for Edit:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("Id,Name,Description,Price,PictureFile")] Product product)
{
if (id != product.Id)
{
return NotFound();
}

if (ModelState.IsValid)
{
try
{
product.SellerId = _userManager.GetUserId(User);
_context.Update(product);
if (product.PictureFile != null && product.PictureFile.Length > 0)
{
//New filename for uploaded picture
var fileName = Guid.NewGuid().ToString() + Path.GetExtension(product.PictureFile.FileName);
var filePath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot/images/", fileName);

//Save file locally
using (var fileStream = new FileStream(filePath, FileMode.Create))
{
await product.PictureFile.CopyToAsync(fileStream);
}

product.PictureLink = fileName;
_context.Update(product);
}
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!ProductExists(product.Id))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction(nameof(Index));
}
return View(product);
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("Id,Name,Description,Price,PictureFile")] Product product)
{
if (id != product.Id)
{
return NotFound();
}

if (ModelState.IsValid)
{
try
{
product.SellerId = _userManager.GetUserId(User);
_context.Update(product);
if (product.PictureFile != null && product.PictureFile.Length > 0)
{
//New filename for uploaded picture
var fileName = Guid.NewGuid().ToString() + Path.GetExtension(product.PictureFile.FileName);
var filePath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot/images/", fileName);

//Save file locally
using (var fileStream = new FileStream(filePath, FileMode.Create))
{
await product.PictureFile.CopyToAsync(fileStream);
}

product.PictureLink = fileName;
_context.Update(product);
}
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!ProductExists(product.Id))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction(nameof(Index));
}
return View(product);
}