How to keep input type=file field value after failed validation in ASP.NET MVC?

Viewed 57313

I've got a simple form in an MVC app I've made. It contains a file field so users can upload an image. It all works great.

Problem is, if the form submit fails validation the content of the file field is lost (other fields remain populated, thx HtmlHelpers!). How do I keep the file field populated after a failed validation?

TIA!

8 Answers

Browsers are designed in such way because of security risks. It's impossible to set value of file input box in HTML source or by Javascript. Otherwise malicious script could steal some private file without user attention.

There is an interesting information about the subject.

As far as I know you cannot set the value of a HTML file input box. I would suggest coupling the file input box with a label or text box.

Then you can populate the it with the value from the file input box to be resubmitted later.

I fail to agree with "impossible" being marked as correct answer. In case anybody is still in search of a possibility, here is the work around that worked for me. I'm using MVC5. The idea is to use a session variable. I got the idea from ASP.Net Form.

My Model/ViewModel (only relevant properties):

public partial class emp_leaves
    {
        public string fileNameOrig { get; set; }
        public byte[] fileContent { get; set; }

        public HttpPostedFileBase uploadFile { get; set; }
    }

In my controller (HttpPost): //Check

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(emp_leaves emp_leaves)
{
    if (emp_leaves.uploadFile != null && emp_leaves.uploadFile.ContentLength>0 && !string.IsNullOrEmpty(emp_leaves.uploadFile.FileName))
    {
        emp_leaves.fileNameOrig = Path.GetFileName(emp_leaves.uploadFile.FileName);
        emp_leaves.fileContent = new byte[emp_leaves.uploadFile.ContentLength];
        emp_leaves.uploadFile.InputStream.Read(emp_leaves.fileContent, 0, emp_leaves.uploadFile.ContentLength);
        Session["emp_leaves.uploadFile"] = emp_leaves.uploadFile; //saving the file in session variable here
    }
    else if (Session["emp_leaves.uploadFile"] != null)
    {//if re-submitting after a failed validation you will reach here.
        emp_leaves.uploadFile = (HttpPostedFileBase)Session["emp_leaves.uploadFile"];
        if (emp_leaves.uploadFile != null && emp_leaves.uploadFile.ContentLength>0 && !string.IsNullOrEmpty(emp_leaves.uploadFile.FileName))
        {
            emp_leaves.fileNameOrig = Path.GetFileName(emp_leaves.uploadFile.FileName);
            emp_leaves.uploadFile.InputStream.Position = 0;
            emp_leaves.fileContent = new byte[emp_leaves.uploadFile.ContentLength];
            emp_leaves.uploadFile.InputStream.Read(emp_leaves.fileContent, 0, emp_leaves.uploadFile.ContentLength);    
        }
    }
//code to save follows here...
}

Finally within my edit view: here, i am conditionally showing the file upload control.

< script type = "text/javascript" >
  $("#removefile").on("click", function(e) {
    if (!confirm('Delete File?')) {
      e.preventDefault();
      return false;
    }
    $('#fileNameOrig').val('');
    //toggle visibility for concerned div
    $('#downloadlrfdiv').hide();
    $('#uploadlrfdiv').show();
    return false;
  }); <
/script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
@model PPMSWEB.Models.emp_leaves @{ HttpPostedFileBase uploadFileSession = Session["emp_leaves.uploadFile"] == null ? null : (HttpPostedFileBase)Session["emp_leaves.uploadFile"]; } @using (Html.BeginForm(null, null, FormMethod.Post, new { enctype = "multipart/form-data"
})) { @Html.AntiForgeryToken()
<div class="row">
  @*irrelevant content removed*@
  <div id="downloadlrfdiv" @((!String.IsNullOrEmpty(Model.fileNameOrig) && (Model.uploadFile==n ull || uploadFileSession !=null)) ? "" : "style=display:none;")>
    <label>Attachment</label>
    <span>
            <strong>
                <a id="downloadlrf" href="@(uploadFileSession != null? "" : Url.Action("DownloadLRF", "emp_leaves", new { empLeaveId = Model.ID }))" class="text-primary ui-button-text-icon-primary" title="Download attached file">
                    @Model.fileNameOrig
                </a>
            </strong>
            @if (isEditable && !Model.readonlyMode)
            {
                @Html.Raw("&nbsp");
                <a id="removefile" class="btn text-danger lead">
                    <strong title="Delete File" class="glyphicon glyphicon-minus-sign">  </strong>
                </a>
            }
            </span>
  </div>
  <div id="uploadlrfdiv" @(!(!String.IsNullOrEmpty(Model.fileNameOrig) && Model.uploadFile==n ull) && !Model.readonlyMode ? "" : "style=display:none;")>
    <label>Upload File</label> @Html.TextBoxFor(model => model.uploadFile, new { @type = "file", @class = "btn btn-default", @title = "Upload file (max 300 KB)" }) @Html.ValidationMessageFor(x => x.uploadFile)
  </div>
</div>
}

I post/upload images on change, with ajax. Then, show the thumbnails in a div by accessing the file field for the paths. This allows the user to upload addition photos without losing the original selected images.

Then I remove the html file field and add it back hidden before page submit with all the src values of the div containing thumbnails.


            $("form:first").submit(function () {

            $("#fileupload").remove(); // Not needed


                for (var i = 1; i <= $("#images img").length; i++) {

                    var imgFileName = $('#thumb'+i).attr('src');

                    console.log("Views/Product/Create.cshtml => Adding Image for Posting /Product/Create: " + imgFileName);
                    $("<input />").attr("type", "hidden").attr("name", "files_uploaded").attr("value", imgFileName).appendTo("form:first");

                }
                return true;
            });

Related