1

I am quite new with MVC Core and trying to add an image to my SQL Server-database through Entity Framework. I have accomplished the database itself and the view. What I cannot really get working is the controller. Can someone help me get the controller in place. Please! Here is the model:

 public class Product
 {
     [Key]
     public int ProductID { get; set; }
     [Required(ErrorMessage = "Please enter an product name")]
     public string Name { get; set; }
     [Required(ErrorMessage = "Please specify a category")]
     public string Category { get; set; }
     public string SubCategory { get; set; }
     [Required(ErrorMessage = "Please enter a description")]
     public string Description { get; set; }
     [Required(ErrorMessage = "Please enter a positive price")]
     public decimal Price { get; set; }
     public byte[] Image { get; set; }
     public string ImageSourceFileName { get; set; }
     public string ImageContentType { get; set; }
}

Here is the database:

 Product ID   int False   
 Category    nvarchar(MAX)   False   
 Description nvarchar(MAX)   False   
 Name    nvarchar(MAX)   False   
 Price   decimal(18,2)   False   
 SubCategory nvarchar(MAX)   True    
 Image   varbinary(MAX)  True    
 ImageContentType    nvarchar(MAX)   True    
 ImageSourceFileName nvarchar(MAX)   True

Here is the view:

    <div class="col-md-4">
    <form asp-action="Create" method="post" enctype="multipart/
     form-data" asp-controller="Products">
        <div asp-validation-summary="ModelOnly" class="text-danger"></div>
        <div class="form-group">
                <label asp-for="Image" class="control-label">File  
      input</label>
                <input asp-for="Image" type="file"
        aria-describedby="fileHelp" class="form-control-file" />
                <span asp-validation-for="Image" class="text-danger"></span>
                <small id="fileHelp" class="form-text text-muted">This is  
     some placeholder block-level help text for the above input. It's a bit
     lighter and easily wraps to a new line.</small>
            </div>
        <div class="form-group">
            <label asp-for="ImageSourceFileName"     
      class= "control-label"></label>
            <input asp-for="ImageSourceFileName" class="form-control" />
            <span asp-validation-for="ImageSourceFileName"   
      class="text-danger"></span>
        </div>
        <div class="form-group">
            <label asp-for="ImageContentType" class="control-label"></label>
            <input asp-for="ImageContentType" class="form-control" />
            <span asp-validation-for="ImageContentType" 
            class="text-danger"></span>
        </div>
        <div class="form-group">
            <input type="submit" value="Create" class="btn btn-default" />
        </div>
    </form>
</div>

Here is the controller:

    public class ProductsController : Controller
    {
    private readonly ApplicationDbContext _context;

    public ProductsController(ApplicationDbContext context)
    {
        _context = context;
    }

    // GET: Products
    public async Task<IActionResult> Index()
    {
        return View(await _context.Products.ToListAsync());
    }

    // GET: Products/Details/5
    public async Task<IActionResult> Details(int? id)
    {
        if (id == null)
        {
            return NotFound();
        }

        var product = await _context.Products
            .SingleOrDefaultAsync(m => m.ProductID == id);
        if (product == null)
        {
            return NotFound();
        }

        return View(product);
    }

    // GET: Products/Create
    public IActionResult Create()
    {
        return View();
    }

            [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> Create
        ([Bind("ProductID,Name,Category,SubCategory,
         Description,Price,Image,ImageSourceFileName,ImageContentType")]
        Product product)
    {
        if (ModelState.IsValid)
        {
            _context.Add(product);
            await _context.SaveChangesAsync();
            return RedirectToAction(nameof(Index));
        }
        return View(product);
    }

    // GET: Products/Edit/5
    public async Task<IActionResult> Edit(int? id)
    {
        if (id == null)
        {
            return NotFound();
        }

        var product = await _context.Products.SingleOrDefaultAsync(m =>
         m.ProductID == id);
        if (product == null)
        {
            return NotFound();
        }
        return View(product);
    }
    [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> 
        Edit(int id, 
        [Bind 
    ("ProductID,Name,Category,SubCategory,Description,Price,
     Image,ImageSourceFileName,ImageContentType")]
        Product product)
    {
        if (id != product.ProductID)
        {
            return NotFound();
        }

        if (ModelState.IsValid)
        {
            try
            {
                _context.Update(product);
                await _context.SaveChangesAsync();
            }
            catch (DbUpdateConcurrencyException)
            {
                if (!ProductExists(product.ProductID))
                {
                    return NotFound();
                }
                else
                {
                    throw;
                }
            }
            return RedirectToAction(nameof(Index));
        }
        return View(product);
    }

    // GET: Products/Delete/5
    public async Task<IActionResult> Delete(int? id)
    {
        if (id == null)
        {
            return NotFound();
        }

        var product = await _context.Products
            .SingleOrDefaultAsync(m => m.ProductID == id);
        if (product == null)
        {
            return NotFound();
        }

        return View(product);
    }

    // POST: Products/Delete/5
    [HttpPost, ActionName("Delete")]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> DeleteConfirmed(int id)
    {
        var product = await _context.Products.SingleOrDefaultAsync(m =>  
     m.ProductID == id);
        _context.Products.Remove(product);
        await _context.SaveChangesAsync();
        return RedirectToAction(nameof(Index));
    }

    private bool ProductExists(int id)
    {
        return _context.Products.Any(e => e.ProductID == id);
    }
}

I like to store the image in the database. Now all text info goes into the database nice and neat, but no image/bytes...

The space is empty in the index-view where the image is supposed to be.

Harry_C
  • 179
  • 1
  • 1
  • 14
  • 1
    *"The space is empty in the index-view"* - What space exactly? Is that the only confirmation you have that the image supposedly isn't saving, or has there been any other debugging on that? Do you not have a controller at all? How is the text being saved to the database? – David Dec 11 '17 at 13:53
  • Maybe will help https://code.msdn.microsoft.com/How-to-save-Image-to-978a7b0b – Leszek P Dec 11 '17 at 13:54
  • Share your action method code. – Shyju Dec 11 '17 at 14:31
  • Consider the size of your DB as the list of product grows. You should host your images on a CDN for better performance – JConstantine Nov 20 '19 at 10:08

3 Answers3

1

In asp.net core, to send a file from your browser to your action method, you should use the IFormFile type.

If you do not prefer to create a view model (I strongly advise you to create a view model and use that), you can add a new parameter to your httppost action method of IFormFile type and convert that to a byte array and store that in the Image property on your Product entity.

Also there is no need to have input elements for ImageContentType and ImageSourceFileName properties/columns. You can read this meta information from the uploaded file.

[HttpPost]
public IActionResult Create(Product model, IFormFile img)
{
    if (img != null)
    {
        model.Image = GetByteArrayFromImage(img);
        model.ImageSourceFileName = System.IO.Path.GetFileName(img.FileName);
        model.ImageContentType = img.ContentType;
    }
    _context.Products.Add(model);
    _context.SaveChanges();
    return RedirectToAction("Index");
}
private byte[] GetByteArrayFromImage(IFormFile file)
{
    using (var target = new MemoryStream())
    {
        file.CopyTo(target);
        return target.ToArray();
    }
}

Now make sure you are using a file input element with same name as the new method parameter we added (img) in your form.

<form asp-action="Create" method="post"
      enctype="multipart/form-data" asp-controller="Product">
    <div asp-validation-summary="ModelOnly" class="text-danger"></div>
    <input asp-for="Name" />
    <input asp-for="Category" />
    <input asp-for="Price" />
    <input  type="file" name="img"  />
    <input type="submit" value="Create" class="btn" />
</form>

As i mentioned earlier, It is a good idea to use a view model for your create view and use that. This example uses a view model to transfer the uploaded file from the browser to the action method.

How to upload files in asp.net core?

Shyju
  • 214,206
  • 104
  • 411
  • 497
  • Thank you Shyju! I am quite new to MVC and I am not really sure how to proceed here. I create a view model in a viewmodel-map. But you have changed the name of the object in the database to img, no? I get a blank page when I load the site now and its create page. – Harry_C Dec 12 '17 at 08:25
  • No. I did not change the entity property name to `I'mg`. It is just the parameter name in your action method (and the input element name in the form) If you have a view model, see the link to other post in my answer. specifically the last part where it reads the property value of your view model, convert to byte array and set in your entity. – Shyju Dec 12 '17 at 13:23
0

At the view, you should assign the image input tag with name like following:

name="@Model.Image"

And at the Controller, add a parameter for image upload and use MemoryStream class to convert it into bytes:

public virtual ActionResult yourController(Product prod, HttpPostedFileBase imgUpload)
{
  Product prod = new Product();
  var imgT = new MemoryStream();
  if(imgUpload!=null){
    imgUpload.InputStream.CopyTo(imgT);
    prod.Image = imgT.ToArray();
  }
}

Hope it helps!

0

This way let you to save the file in a folder and to save the path the DB

following code for the Entity Dto

 public string ImagePath { get; set; }
 public IFormFile ImageFile { get; set; }

following code for the controller

var file = EntityDto.ImageFile;
if (file != null && file.Length > 0)
    EntityDto.ImagePath = $"\\images\\folderName\\{EntityDto.Code}{Path.GetExtension(file.FileName)}";

if (AddAsync(EntityDto, $"{nameof(EntityDto)}."))
{
    if (file != null && file.Length > 0)
    {
       var uploads = Path.Combine(_environment.WebRootPath, @"images\employees");
       var filePath = Path.Combine(uploads, $"{EntityDto.Code}{Path.GetExtension(file.FileName)}");

       using var fileStream = new FileStream(filePath, FileMode.Create);
       await file.CopyToAsync(fileStream);
     }

following code for the UI

<form asp-action="New" method="post" enctype="multipart/form-data">
  <input asp-for="@Model.EntityDto.ImageFile" type="file" />
</form>
Tanveer Badar
  • 5,438
  • 2
  • 27
  • 32
R J
  • 475
  • 3
  • 14