1

I want to upload Images to a database. The database contains url images and images uploads to folder in file system. I have the following tables in the database,

  1. Furniture
  2. MainFileDetails (1-1 relationship with Furniture) where store the main image
  3. FileDetails (1-Many relationship with Furniture) where we store other images associated with Furniture.

Here is my code:

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(Furniture furniture , HttpPostedFileBase fileTwo, HttpPostedFileBase file)
{
    if (ModelState.IsValid)
    {
        for (int i = 0; i < Request.Files.Count; i++)
        {
            fileTwo = Request.Files[i];
            if (fileTwo != null && fileTwo.ContentLength > 0)
            {
                var fileNameTwo = Path.GetFileName(fileTwo.FileName);
                MainFileDetails mainFileDetail = new MainFileDetails()
                {
                    FileName = fileNameTwo,
                    Extension = Path.GetExtension(fileNameTwo),
                    Id = Guid.NewGuid(),
                    FurnitureId = furniture.FurnitureId
                };
                var pathMain = Path.Combine(Server.MapPath("~/Upload/MainPage/"), mainFileDetail.Id + mainFileDetail.Extension);
                fileTwo.SaveAs(pathMain);
                db.Entry(mainFileDetail).State = EntityState.Modified;
                db.SaveChanges();
                FileDetail fileDetail = new FileDetail()
                {
                    NameFile = fileNameTwo, //or mainFileDetail.FileName
                    Extension = Path.GetExtension(fileNameTwo), //or mainFileDetail.Extension
                    Id = Guid.NewGuid(),
                    FurnitureId = furniture.FurnitureId //or mainFileDetail. FurnitureId
                };
                var path = Path.Combine(Server.MapPath("~/Upload/"), fileDetail.Id + fileDetail.Extension);
                file.SaveAs(path);
                db.Entry(fileDetail).State = EntityState.Added;
            }
        }
        db.Entry(furniture).State = EntityState.Modified;
        db.SaveChanges();
        TempData["message"] = string.Format("Changes in \"{0}\" has been saved", furniture.Name);
        return RedirectToAction("Index");
    }
    ViewBag.CategoryId = new SelectList(db.Categories, "CategoryId", "Name", furniture.CategoryId);
    return View(furniture);
}

My View:

@model FurnitureStore.Entities.Furniture
....
@using (Html.BeginForm("Edit", "Furnitures", FormMethod.Post, new { enctype = "multipart/form-data"}))
{
    ....
    @Html.HiddenFor(model => model.FurnitureId)
    @Html.LabelFor(model => model.Name)
    @Html.EditorFor(model => model.Name)
    @Html.ValidationMessageFor(model => model.Name)
    .... // more inputs for properties of the model
    @Html.LabelFor(model => model.CategoryId, "Category")
    @Html.DropDownList("CategoryId", null)
    @Html.ValidationMessageFor(model => model.CategoryId, "", new { @class = 
    // Main file
    <input type="file" name="fileTwo" />
    @if(Model.MainFileDetails.Id == null)
    {
        <div class="form-control-static">No image</div>
    }
    else
    {
        <img src="~/Upload/MainPage/@(Model.MainFileDetails.Id + Model.MainFileDetails.Extension)"  width="240" height="240" />
    }
    // Other files
    <input type="file" name="file" multiple="multiple" />
    <ul class="attachment">
        @foreach (var item in Model.FileDetails)
        {
            <li style="list-style-type:none; display:inline;">
                <img src="~/Upload/@(item.Id + item.Extension)" />
                <a href="javascript:void(0);" data-id="@item.Id" class="deleteItem">X</a>
            </li>
        }
    </ul>
    <input type="submit" value="Save" class="btn btn-primary"  />
}

But I have a problem. All photo just uploads to the Upload/MainPage folder. I want to upload photo separately to MainPage in one table and separately to gallery (other table).

How can I fix my code, because last uploaded photo in gallery table replaces photo in MainPage?

M-Misha-M
  • 33
  • 9
  • 1
    Not clear what you asking. You have 2 parameters for `HttPostedFileBase` in you method, each accepts a single file, but you indicate you want a single file plus multiple files, that should be `IEnumerable`. But then you ignore them and just loop through `Request.Files[i]` so you do not even know which files belongs in the main table and the other table. What are the 2 inputs in your view for selecting files? –  Feb 08 '17 at 20:44
  • @StephenMuecke Hi , update my question , add my view , i want to upload separately in different tables photo , but my program "thinks" that two different input files is same thing – M-Misha-M Feb 08 '17 at 20:59
  • Yo have inputs named `file` and `fileTwo` so you parameter should be `IEnumerable fileTwo, HttpPostedFileBase file`). So just save `file` to the main table and loop through each file in `fileTwo` and save those to the other table (do not use `Request.Files`) –  Feb 08 '17 at 21:02
  • @StephenMuecke "(do not use Request.Files") Explain this , please , I don't understand what you mean. Can u show please, some part of code that fix y problem? – M-Misha-M Feb 08 '17 at 21:05
  • You have 2 parameters in your method that are bound to your file inputs. Why in the world would you ignore them and use `Request.Files` –  Feb 08 '17 at 21:07
  • @StephenMuecke what I have to do instread Request.Files? It must be two different loops , yes? – M-Misha-M Feb 08 '17 at 21:12
  • Read my previous comment –  Feb 08 '17 at 21:13
  • @StephenMuecke can u move please our discussion to chat , because I don't have enough reputation and have questions to u – M-Misha-M Feb 08 '17 at 21:26
  • You do not have enough rep to use chat so I can't. No time now, but a I will add an answer in a few hours (there are lots of other issues in your code that you need to address as well. –  Feb 08 '17 at 21:32
  • @StephenMuecke okay thanks a lot, i'll wait – M-Misha-M Feb 08 '17 at 21:35
  • I have added an answer that solves the immediate issue, however there are numerous other issues with your code. I will update the answer later showing what your code should be. –  Feb 09 '17 at 00:05
  • @StephenMuecke okay i'll try it – M-Misha-M Feb 09 '17 at 00:07

1 Answers1

0

The foreach loop in your controller method is looping through every file you have uploaded, and then you create a new MainFileDetails for each file and save it. You need to get the 'main file' from the first file input and save it to MainFileDetails and then loop through the files in the second file input and save each of them to FileDetails.

To solve the immediate problem, change your method to

public ActionResult Edit(Furniture furniture , HttpPostedFileBase fileTwo, IEnumerable<HttpPostedFileBase> file)

and those parameters will be bound with the values for the file inputs. Then its simply

if (ModelState.IsValid)
{
    if (fileTwo != null && fileTwo.ContentLength > 0)
    {
        // create an instance of MainFileDetails and set its properties based on
        // the values of fileTwo and save
    }
    foreach (HttpPostedFileBase image in file)
    {
        // create an instance of FileDetails and set its details based on 
        // the values of image and save
    }
    db.Entry(furniture).State = EntityState.Modified;
    db.SaveChanges();
    ....

There are however numerous other potential issues with your code.

  • Rule 1. Your editing data, so always use a view model. What is ViewModel in MVC?
  • Its unclear why you need a separate 1 to 1 table for the main image. The properties for that could be stored in the Furniture table. Alteratively you could just have the 1 to Many table, and have a bool IsMainImage property which would allow you to easily swap which is the main image.
  • You images table (FurnitureImages would be a more appropriate name) should include a auto-incremented int ID for the PK and properties for the file path and file display name
  • If ModelState is invalid, you return the view, but all the files are lost and the poor user needs to re-select them all again

Your view models should be

public class FurnitureVM
{
    public int? ID { get; set; }
    [Required( ... )]
    public string Name { get; set; }
    .... // other properties of Furniture that you need in the view
    [Required( ... )]
    public int? Category { get; set; }
    public IEnumerable<SelectListItem> CategoryList { get; set; }
    public HttpPostedFileBase MainFile { get; set; }
    public IEnumerable<HttpPostedFileBase> SecondaryFiles { get; set; }
    public ImageVM  MainImage { get; set; }
    public List<ImageVM> SecondaryImages { get; set; }
}
public class ImageVM
{
    public int? ID { get; set; }
    public string Path { get; set; }
    public string DisplayName { get; set; }
}

And the view

@model yourAssembly.FurnitureVM
....
@using (Html.BeginForm("Edit", "Furnitures", FormMethod.Post, new { enctype = "multipart/form-data"}))
{
    // Note: no need for a hidden input for the ID property assuming your using the default routing
    @Html.LabelFor(m => m.Name)
    @Html.EditorFor(m => m.Name)
    @Html.ValidationMessageFor(m => m.Name)
    .... // more inputs for properties of the model
    @Html.LabelFor(m => m.Category)
    @Html.DropDownListFor(m => m.Category, Model.CategoryList, "Please select")
    @Html.ValidationMessageFor(m => m.Category)
    // Main file
    @Html.TextBoxFor(m => m.MainFile, new { type = "file" })
    @Html.ValidationMessageFor(m => m.MainFile)
    if (Model.MainImage != null)
    {
        @Html.HiddenFor(m => m.MainImage.ID)
        @Html.HiddenFor(m => m.MainImage.Path)
        @Html.HiddenFor(m => m.MainImage.DisplayName)
        <img src="@MainImage.Path" alt="@MainImage.DisplayName" />
    }
    // Secondary files
    @Html.TextBoxFor(m => m.SecondaryFiles, new { type = "file", multiple = "multiple" })
    @Html.ValidationMessageFor(m => m.SecondaryFiles)
    for (int i = 0; i < Model.SecondaryImages.Count; i++)
    {
        @Html.HiddenFor(m => m.SecondaryImages[i].ID)
        ...
    }
    <input type="submit" value="Save" />
}

And the controller POST method

public ActionResult Edit(FurnitureVM model)
{
    // Save the files
    if (model.MainFile != null && model.MainFile.ContentLength > 0)
    {
        string displayName = model.MainFile.FileName;
        string extension = Path.GetExtension(displayName)
        string fileName = string.Format("{0}{1}", Guid.NewGuid(), extension)
        string path = Path.Combine("~/Images/", fileName);
        model.MainFile.SaveAs(Server.MapPath(path));
        model.MainImage = new ImageVM() { Path = path, DisplayName = displayName };
    }
    foreach (HttpPostedFileBase file in model.SecondaryFiles)
    {
        ....
    }
    if (!ModelState.IsValid)
    {
        model.CategoryList = new SelectList(....); // repopulate the SelectList
        return View(model);
    }
    // Get the data model from the database
    Furniture furniture = db.Furniture.Where(x => x.FurnitureId == model.ID).FirstOrDefault()
    // Update properties based on view model
    furniture.Name = modell.Name;
    ...
    // Update the main image
    if (model.MainImage != null && !model.MainImage.ID.HasValue)
    {
        FurnitureImages image = new FurnitureImages()
        {
            image.Path = model.MainImage.Path;
            image.DisplayName = model.MainImage.DisplayName;
            image.IsMainImage = true;
            furniture.Images.Add(image);
    }
    // Update secondary images
    IEnumerable<ImageVM> newImages = model.SecondaryImages.Where(x => x.ID == null);
    foreach (ImageVM image in newImages)
    {
        .... // add to the collection
    }
    // Save and redirect
}
Community
  • 1
  • 1
  • okay thanks for answer , firstly i tried not change a model just use your first recommendation with foreach , i write this code http://pastebin.com/eTjeZPRS just for testing , and it doesn't work , can u review it please? Now I have Exception in View @if(Model.MainFileDetails.Id == null) "object reference not set to an instance of an object " , i will try to change my model – M-Misha-M Feb 11 '17 at 01:48
  • The error means that either `Model` or `MainFileDetails` is `null`You need to check it first - `if(Model != null && Model.MainFileDetails != null) { ....` But that is completely unrelated to the question you asked (and I assure that the the answer I gave does solve the issue in your question) –  Feb 11 '17 at 02:03
  • In any case the error is caused only when you return the view in the GET method because `MainFileDetails` is `null` - nowhere in your view have you generated any inputs for properties of `MainFileDetails` so in the POST method its `null`. I pointed out that your had multiple errors, and I even fixed that one as you can see in the view code of my answer (note the hidden inputs for `MainImage` and `SecondaryImages`) –  Feb 11 '17 at 02:43
  • explain please structure of database looks like? I use code First and it create database something like this [table1](http://imgh.us/1_3137.png) and this [table2](http://imgh.us/2_762.png). You can see another props in tables (Album) I just test your code I don't sure if structure is right – M-Misha-M Feb 11 '17 at 20:01
  • Those tables do not even relate to the code in your question (which is `Furniture`, `MainFileDetails` and `FileDetails`) so I have no idea what your talking about. If you have another question, then ask another question. –  Feb 11 '17 at 22:54
  • it's your code , i create tables based on your code , you said that i don't need separate tables , so i create ImageVM , and delete other Tables (Filedetals and MainFileDetails) and create this one , or I understand your table incorrect?? – M-Misha-M Feb 11 '17 at 23:18
  • `ImageVM` is a view model, not a data model. I suggested you have a table named `FurnitureImages` with a one-many relation ship with the `Furniture` table (which would have a similar structure to the `table 1` you showed. –  Feb 11 '17 at 23:22
  • But in my answer I showed you how to correct your existing code in the 1st two code snippets which works and will save you images correctly. Are you saying that it is not saving your images? –  Feb 11 '17 at 23:24
  • oh it's view model , my bad , sorry , i miss it , but if i create this class view model , does entity framework asks me to add this moidel to database or it will ignored? okay i got it ,Sorry for my ignorance, – M-Misha-M Feb 11 '17 at 23:38
  • View models have no relationship to data models. Create a separate folder (say `ViewModels`) and add you view model classes there. –  Feb 11 '17 at 23:41
  • okay , i got it , can i ask u something? I don't understand what i have to write in if (model.MainImage != null && !model.MainImage.ID.HasValue) and what i have to do in foreach , can u write 1 line code to understand what to write , please – M-Misha-M Feb 11 '17 at 23:44
  • That will depend on how your real database tables and models are set up. I am about to sign off for a couple of hours but I will add some sample code later. But the first part of my question answers your question and the 2nd part is just extra code to show you the correct way to approach this. If the first part did not solve result in saving the images (which I assume it did not since you have not accepted the answer), then you should let me know what failed. –  Feb 11 '17 at 23:58
  • I have added some sample code in side that `if` block. It assumes you have one table named `FurnitureImages` that has a one-many relationship with `Furniture` –  Feb 12 '17 at 01:45
  • thanks , yes , i create FurnitureImages as you say and read about viewmodels , now it's night in my timezone :) i'll try your code , if something go wrong i write here , thanks for helping and Thank you for your time – M-Misha-M Feb 12 '17 at 01:49
  • okay hello again , i have a several questions to you , i'm stuck in foreach (HttpPostedFileBase file in model.SecondaryFiles) , is it right in my code? Or i must create an instance in this block?? because you explain about instance , but is it possible do it? Look at my code please http://pastebin.com/qt9UNGB7 – M-Misha-M Feb 12 '17 at 23:15
  • I gave your an answer which solved the problem in your question (the first part of my answer). But still you refuse to accept it. The second part of my question was extra code showing the outline of the code you should be using and is not directly related to the question. If you have questions about that, then ask a new question with the code you have tried. –  Feb 12 '17 at 23:22
  • okay i asked new question if i need , I know i make you seek , but in last i have an exception The model item is of type System.Data.Entity.DynamicProxies.Furniture_6E0427CB015001BD6A45794C4DBA1423DB21DD5574A267E56C626A0B7606AB8C but requires a model item of type FurnitureStore.ModelView.FurnitureVM. Thanks a lot , i accept your answer for this long time – M-Misha-M Feb 12 '17 at 23:35
  • The reason for the exception is explained in [this answer](http://stackoverflow.com/questions/40373595/the-model-item-passed-into-the-dictionary-is-of-type-but-this-dictionary-requ). But I cannot keep flipping between incomplete code in pastebin links which is why you need to ask a new question –  Feb 12 '17 at 23:40
  • No time just now. Will take a look in about 2 hours. –  Feb 14 '17 at 07:40