locked
ASP.NET MVC 4: Browser looses uploading File after Postback RRS feed

  • Question

  • User-1974654775 posted

    Hello ASP.net Community

    I have a form to add Products. (Productname, ProductVersion, Description, ResponsilbleUser, CreationDate, Editdate)

    In this form you an also add some files. 

    If i add a Product and press "SAVE", the controller validates if the product is already saved in database.

    if so i will add an error  "Product already exists."

    But then the browser looses the uploaded file in <input file>.

    How can I keep that file after postback?

    -----------------------------------------------------------------------------------------------------------------------------

    Controller:

    (CreateViewBagAvailableUser creates a viewbag with all users from db)

    public ActionResult Create()
    {
    CreateViewBagAvailableUser();
    return View();
    }
    

        public void CreateViewBagAvailableUser()
            {
                IEnumerable<SelectListItem> items = DBManager.Instance.ListUsers()
                .Select(u => new SelectListItem
                {
                    Value = u.Username,
                    Text = u.Username,
                }).OrderBy(x => x.Text);
                ViewBag.AvailableUsers = items;
            }

       [HttpPost]
            public ActionResult Create(ProductViewModel productViewModel)
            {
                if (ModelState.IsValid)
                {
                  
                   IEnumerable<Product> products = DBManager.Instance.ListProducts().Where(p => p.Name.ToUpper() == productViewModel.Product.Name.ToUpper());
                   if (products.Count() > 0)
                   {
                       ModelState.AddModelError("Product.Name", "Dieses Produkt gibt es bereits.");
                   }
                   else
                   {
                       productViewModel.Product.Files = SaveFiles(productViewModel);
                       DBManager.Instance.CreateProduct(productViewModel.Product, productViewModel.Product.Name);
                       return RedirectToAction("Index");
                   }
                }
                productViewModel.Product.Username = null;
                CreateViewBagAvailableUser();
                return View(productViewModel);
            }


    Form:

    @model netPRODUCTS.Models.ProductViewModel
    
    
    <form action="/product/create" method="post" enctype="multipart/form-data">
    
        @Html.ValidationSummary(true)
    
        <h1>Produkt hinzufügen</h1>
    
        <div class="left" style="">
            <div class="editor-label">
                @Html.LabelFor(model => model.Product.Name)
            </div>
            <div class="editor-field">
                @Html.EditorFor(model => model.Product.Name)<br />
                @Html.ValidationMessageFor(model => model.Product.Name)
            </div>
            <div class="editor-label">
                @Html.LabelFor(model => model.Product.Status)
            </div>
            <div class="editor-field">
                @Html.EditorFor(model => model.Product.Status)
                @Html.ValidationMessageFor(model => model.Product.Status)
            </div>
            <div class="editor-label">
                @Html.LabelFor(model => model.Product.Description, "Beschreibung")
            </div>
            <div class="editor-field">
                @Html.TextAreaFor(model => model.Product.Description)<br />
                @Html.ValidationMessageFor(model => model.Product.Description, "Das Feld Beschreibung ist erforderlich.")
            </div>
        </div>
        <div class="left">
            <div class="editor-label">
                @Html.LabelFor(model => model.Product.Version)
            </div>
            <div class="editor-field">
                @Html.EditorFor(model => model.Product.Version)
                @Html.ValidationMessageFor(model => model.Product.Version)
            </div>
            <div class="editor-label" style="margin-bottom: 4px;">
                @Html.LabelFor(model => model.Product.Username, "Verantwortlicher")
            </div>
            <div class="editor-field" style="margin-bottom: 24px;">
                @Html.DropDownList("Product.Username", (IEnumerable<SelectListItem>)ViewBag.AvailableUsers)
            </div>
            <div class="editor-label">
                @Html.LabelFor(model => model.Product.Files, "Files hinzufügen")
    
            </div>
            <div class="editor-field">
                <input type="file" multiple="multiple" id="FileAccessProvider" name="FileAccessProvider" class="uploader" />
            </div>
            <h6>Drücken Sie CTRL um mehrere Dateien auszuwählen.
                <br />
                Alternativ können Sie auch die gewünschten Dateien in dieses Feld ziehen.</h6>
        </div>
    
        <div class="clearfix"></div>
        <br />
        <input type="button" class="button darkblue" value="Zurück" onclick="@Library.JSHelper.GetRedirectAction(Url.Action("Index", "Product"))" />
        <input type="submit" value="Produkt erstellen" />
    
    </form>


    Class Product

      public class Product
        {
            public int ID { get; set; }
            [Required]
            public string Name { get; set; }
            public string Version { get; set; }
            public string Description { get; set; }
            public string Status { get; set; }
            public DateTime CreationDate { get; set; }
            public DateTime EditDate { get; set; }
            public string Username { get; set; }
            public IEnumerable<File> Files { get; set; }
    
            public Product(int ID, string Name, string Version, string Description, string Status, DateTime CreationDate, DateTime EditDate, string ResponsibleUser,  IEnumerable<File> files)
            {
                this.ID = ID;
                this.Name = Name;
                this.Version = Version;
                this.Status = Status;
                this.CreationDate = CreationDate;
                this.EditDate = EditDate;
                this.Description = Description;
                this.Username = ResponsibleUser;
                this.Files = files;
            }

    Class File

    public class File
        {
            public int FileID { get; set; }
            public string FileName { get; set; }
            public int ProductID { get; set; }
    
            public File(int FileID, string FileName, int ProductID)
            {
                this.FileID = FileID;
                this.FileName = FileName;
                this.ProductID = ProductID;
            }

    Class ProductViewModel

     public class ProductViewModel
        {
            public Product Product { get; set; }
            public IEnumerable<HttpPostedFileWrapper> FileAccessProvider { get; set; }
        }





    Friday, April 5, 2013 2:43 AM

Answers

  • User197322208 posted

    then the browser looses the uploaded file in <input file>.

    How can I keep that file after postback?

    Put into the Session.

    • Marked as answer by Anonymous Thursday, October 7, 2021 12:00 AM
    Friday, April 5, 2013 2:49 AM
  • User-1404016747 posted

    <input type="file" /> is populated with a path to a file on the visitors machine when they select a file.
    When the form is submitted this path is used by the browser to submit the file along with other form data to the server.
    That being said <input type="file" /> expects a path to a file existing on the visitors machine not the server.
    This makes it pointless to prepoppulate an <input type="file" /> form element.
    Not to mention being able to do so would make for a huge security risk to the client.

    In order to show the user that they have uploaded an image your best bet is to show the image/images associated with the product using the data from your model which contains the uploaded image's information in an element such as an <img /> tag or as a list of file names.

    If you want to check if a new image was selected or not use the code I posted above to check if a new file was uploaded and if not do nothing as far as the file is concenred but if a file was uploaded use the appropriate code to handle the newly uploaded file.

    The above code is how I generally to check if files have been selected to upload and should give you an idea on how to handle empty <input type="file" /> tags when a form has been submitted.

    • Marked as answer by Anonymous Thursday, October 7, 2021 12:00 AM
    Friday, April 5, 2013 3:30 AM
  • User-1974654775 posted

    i found a better way:::

    i validate product name. if product exist, the create button will not work. the files won't be uploaded :)

    I share my solution for others ==>

     

    Class Product
    
    [Remote("IsProductNameAvailable", "Product", "Product.Name")] // Function from Controller, URL, Routing-names
    public string Name { get; set; }   
    ...
    Controller:  
    public JsonResult IsProductNameAvailable(Model model)
            {
                bool alreadyExist = DBManager.Instance.IsProductnameAvailable(model.Name); // valiadtes if product exists
               if (!alreadyExist)
                {
                    return Json(true, JsonRequestBehavior.AllowGet); // no, it doesn't exist.
                }
                else
                    return Json("Produkt existiert bereits!", JsonRequestBehavior.AllowGet); // yes it exists.
                }
            }
        }
    
    
    View:
    <div class="editor-label">
                @Html.LabelFor(model => model.Product.Name)
            </div>
            <div class="editor-field">
                @Html.EditorFor(model => model.Product.Name)<br />
                @Html.ValidationMessageFor(model => model.Product.Name)
            </div>
    </div>
    

    • Marked as answer by Anonymous Thursday, October 7, 2021 12:00 AM
    Tuesday, April 16, 2013 3:10 AM

All replies

  • User197322208 posted

    then the browser looses the uploaded file in <input file>.

    How can I keep that file after postback?

    Put into the Session.

    • Marked as answer by Anonymous Thursday, October 7, 2021 12:00 AM
    Friday, April 5, 2013 2:49 AM
  • User-1404016747 posted

    in your view your form tag should be created like so adding the enctype in the view you can also show the image saved within the model so the visitor knows what image is currently there:

    @using (Html.BeginForm("Create", "Product", FormMethod.Post, new { enctype = "multipart/form-data" }))
    {
        @Html.ValidationSummary(true)
    
        <h1>Produkt hinzufügen</h1>
    
        ... The rest of your page code ...
    
        <input type="button" class="button darkblue" value="Zurück" onclick="@Library.JSHelper.GetRedirectAction(Url.Action("Index", "Product"))" />
        <input type="submit" value="Produkt erstellen" />
    }

    To access the file uploaded in your controller you need to add "HttpPostedFileBase file":

       [HttpPost]
       public ActionResult Create(ProductViewModel productViewModel, HttpPostedFileBase files)
       {
            if (ModelState.IsValid)
    
            ... The rest of your code ...


    The file being uploaded will now be exposed to your controller as "files".  You can then access the HttpPostedFileBase to check if a file or files were selected to be uploaded:

            if (files != null && files.ContentLength > 0)
            {
                // Handle the files however you wish...
                // You can add, replace, etc. by adding code to check here...
                // Insert the new image data into your model...
            }
    

     

    Friday, April 5, 2013 2:55 AM
  • User-1974654775 posted

    @ignatandrei

    okay... is so correct?

    [HttpPost]
            public ActionResult Create(ProductViewModel productViewModel)
            {
                if (ModelState.IsValid)
                {
                  
                   IEnumerable<Product> products = DBManager.Instance.ListProducts().Where(p => p.Name.ToUpper() == productViewModel.Product.Name.ToUpper());
                   if (products.Count() > 0)
                   {
                       ModelState.AddModelError("Product.Name", "Dieses Produkt gibt es bereits.");
                       Session["file"] = productViewModel.Product.Files;
                   }
                   else
                   {
                       productViewModel.Product.Files = SaveFiles(productViewModel);
                       DBManager.Instance.CreateProduct(productViewModel.Product, productViewModel.Product.Name);
                       return RedirectToAction("Index");
                   }
                }
                productViewModel.Product.Username = null;
                CreateViewBagAvailableUser();
                return View(productViewModel);
            }

    But i have to set the uploaded file in <input type="file"> again. If i don't do that the browser shows no file in this box and the user means, his file is gone. 

    How do I set the uploaded file to that input?

    Friday, April 5, 2013 2:58 AM
  • User-1404016747 posted

    As mentioned above you will need to declare HttpPostedFileBase as a name such as file and use this variable to access the file posted by the form. You will then need to check if the file exists on the server or if a new file was uploaded.

    Prepopulating a file element would not make sense being your expecting the image the person uploaded to be in the same spot on their computer as when they initially uploaded it plus you would need to store the path to the file on their machine...

    Friday, April 5, 2013 3:00 AM
  • User-1974654775 posted

    @jprochazka

    i already have an enctype defined.

    Why "HttpPostedFileBase"?

    I have an HttpPostedFileWrapper and it works fine.

    public class ProductViewModel
        {
            public Product Product { get; set; }
            public IEnumerable<HttpPostedFileWrapper> FileAccessProvider { get; set; }
        }

     I already can acces the file. Saving files isn't a problem.

    I just can't return back the file to form. And the user means his file is gone.

    Friday, April 5, 2013 3:06 AM
  • User-1404016747 posted

    HttpPostedFileBase exposes the uploaded content to your controller.

    I was editing my post above when I made this comment.
    Why would you wish to show such information?

    If you wish to show an image that has been uploaded simply show the image uploaded within your view from data in your model.
    When the form is submitted check if the user selected a new file if so replace the old one.
    If they didnt upload an image and HttpPostedFileBase contains nothing keep the image already uploaded.

    if (file != null && file.ContentLength > 0)

    Will tell you if they selected a new file or if they left the file form element blank. 

    Friday, April 5, 2013 3:07 AM
  • User-1974654775 posted

    Before:

    Before

    After submitting (the product already exists.)

    After

    Friday, April 5, 2013 3:20 AM
  • User-1404016747 posted

    <input type="file" /> is populated with a path to a file on the visitors machine when they select a file.
    When the form is submitted this path is used by the browser to submit the file along with other form data to the server.
    That being said <input type="file" /> expects a path to a file existing on the visitors machine not the server.
    This makes it pointless to prepoppulate an <input type="file" /> form element.
    Not to mention being able to do so would make for a huge security risk to the client.

    In order to show the user that they have uploaded an image your best bet is to show the image/images associated with the product using the data from your model which contains the uploaded image's information in an element such as an <img /> tag or as a list of file names.

    If you want to check if a new image was selected or not use the code I posted above to check if a new file was uploaded and if not do nothing as far as the file is concenred but if a file was uploaded use the appropriate code to handle the newly uploaded file.

    The above code is how I generally to check if files have been selected to upload and should give you an idea on how to handle empty <input type="file" /> tags when a form has been submitted.

    • Marked as answer by Anonymous Thursday, October 7, 2021 12:00 AM
    Friday, April 5, 2013 3:30 AM
  • User-1974654775 posted

    ahh i see. there is no solution for putting back the files to form.

    i think its the best solution is, when i show the uploaded files below the fileinput.

    thank you all for helping me :)

    output

    Friday, April 5, 2013 4:38 AM
  • User-1974654775 posted

    i found a better way:::

    i validate product name. if product exist, the create button will not work. the files won't be uploaded :)

    I share my solution for others ==>

     

    Class Product
    
    [Remote("IsProductNameAvailable", "Product", "Product.Name")] // Function from Controller, URL, Routing-names
    public string Name { get; set; }   
    ...
    Controller:  
    public JsonResult IsProductNameAvailable(Model model)
            {
                bool alreadyExist = DBManager.Instance.IsProductnameAvailable(model.Name); // valiadtes if product exists
               if (!alreadyExist)
                {
                    return Json(true, JsonRequestBehavior.AllowGet); // no, it doesn't exist.
                }
                else
                    return Json("Produkt existiert bereits!", JsonRequestBehavior.AllowGet); // yes it exists.
                }
            }
        }
    
    
    View:
    <div class="editor-label">
                @Html.LabelFor(model => model.Product.Name)
            </div>
            <div class="editor-field">
                @Html.EditorFor(model => model.Product.Name)<br />
                @Html.ValidationMessageFor(model => model.Product.Name)
            </div>
    </div>
    

    • Marked as answer by Anonymous Thursday, October 7, 2021 12:00 AM
    Tuesday, April 16, 2013 3:10 AM