32

I have a simple model with 1 string property which I render on a simple view.

the view looks like the following:

@using (Html.BeginForm("UploadFile", "Home", FormMethod.Post, new { encType="multipart/form-data" }))
{
    @Html.TextBoxFor(m => m.FirstName)
    <br /><br />

    <input type="file" name="fileUpload" /><br /><br />
    <input type="submit" value="submit me" name="submitme" id="submitme" />
}

Controller is this:

[HttpPost]
public ActionResult UploadFile(UploadFileModel model, HttpPostedFileBase file)
{
   // DO Stuff
   return View(model);
}

Now, when I submit, the model DOES get populated but the second parameter being HttpPostedFileBase is null. However when doing Request.Files - it does seem to show there is a file in the Request being posted. How can I actually get the second parameter to bind?

hutchonoid
  • 32,982
  • 15
  • 99
  • 104
Ahmed ilyas
  • 5,722
  • 8
  • 44
  • 72
  • Binding happens by name, and since you have `name="fileUpload"` on the input markup, your action method's second parameter should be named the same i.e. `HttpPostedFileBase fileUpload`. – SNag Apr 19 '21 at 18:01

4 Answers4

49

Why not add the uploaded files to your model like this:

public class UploadFileModel 
{
    public UploadFileModel()
    {
        Files = new List<HttpPostedFileBase>();
    }

    public List<HttpPostedFileBase> Files { get; set; }
    public string FirstName { get; set; }
    // Rest of model details
}

Then change your view to this:

@using (Html.BeginForm("UploadFile", "Home", FormMethod.Post, new { encType="multipart/form-data" }))
{
    @Html.TextBoxFor(m => m.FirstName)
    <br /><br />

    @Html.TextBoxFor(m => m.Files, new { type = "file", name = "Files" })<br /><br />
    <input type="submit" value="submit me" name="submitme" id="submitme" />
}

Then your files will be posted back as follows:

public ActionResult UploadFile(UploadFileModel model)
{
    var file = model.Files[0];
    return View(model);
}
PBMe_HikeIt
  • 659
  • 8
  • 24
hutchonoid
  • 32,982
  • 15
  • 99
  • 104
  • 1
    Interesting. This worked. Thanks! But still makes no sense why it will not bind to the 2nd parameter. I would like an explanation for that though. – Ahmed ilyas Feb 10 '14 at 12:29
  • was just waiting for it to allow me to accept the answer - all done. I agree with you, the VM approach. It's just... mind boggling on why it wont bind the 2nd parameter. – Ahmed ilyas Feb 10 '14 at 12:35
  • Why do you use HttpPostedFileBase instead of HttpPostedFile? – DFTR Apr 28 '16 at 02:50
  • reference m.Files.Files is incorrect. Submitted edit – PBMe_HikeIt Oct 05 '16 at 17:16
  • need to add HTML5 multiple attribute like so if you want to choose more than 1 file with same input control `@Html.TextBoxFor(m => m.Files, new { type = "file", multiple = "multiple", name = "Files" })` – PBMe_HikeIt Oct 05 '16 at 17:20
  • @hutchonoid what change I should make in my table in sql? It get error key not found? I think I should add a column to UploadFileModel table in database named Files, but don't know the type of column. Am I write? – Reza Akraminejad Jun 30 '19 at 17:07
15

For dealing with a single file input you can define a HttpPostedFileBase property within the ViewModel:

public class SomeModel() 
{ 
    public SomeModel() 
    {
    }

    public HttpPostedFileBase SomeFile { get; set; }
}

And then implement it in the following way:

View:

@model SomeModel

@using (Html.BeginForm(
    "Submit", 
    "Home", 
    FormMethod.Post, 
    new { enctype="multipart/form-data" }))
{
    @Html.TextBoxFor(m => m.SomeFile, new { type = "file" })
    <input type="submit" value="Upload" 
        name="UploadButton" id="UploadButton" />
}

Controller:

[HttpPost]
public ActionResult Submit(SomeModel model)
{
    // do something with model.SomeFile

    return View();
}

In case you need to deal with multiple files, you can either:

  • create multiple properties and implement them separately just like the one above;
  • change the public HttpPostedFileBase SomeFile property to something like public List<HttpPostedFileBase> SomeFiles and then span multiple @Html.TextBoxFor(m => m.SomeFile, new { type = "file" }) controls to have them all within that list.

In case you need additional info check out this blog post that I wrote on the topic.

Darkseal
  • 9,205
  • 8
  • 78
  • 111
10

Change your name file to fileUpload and enctype it's work

@using (Html.BeginForm("UploadFile", "Home", FormMethod.Post, new { enctype="multipart/form-data" }))
{
    @Html.TextBoxFor(m => m.FirstName)
    <br /><br />

    <input type="file" name="fileUpload" /><br /><br />
    <input type="submit" value="submit me" name="submitme" id="submitme" />
}

[HttpPost]
public ActionResult UploadFile(UploadFileModel model, HttpPostedFileBase fileUpload)
{
   // DO Stuff
   return View(model);
}
Jaimin
  • 7,964
  • 2
  • 25
  • 32
  • @Ahmedilyas I don't know why it's not working for you.It's perfectly working for me. – Jaimin Feb 10 '14 at 12:34
  • This approach was what I was looking for, direct approach, the missing part in my problematic solution was "enctype="multipart/form-data" }" in BeginForm :-/. – Honza P. May 28 '21 at 13:42
2

Alternatively, (if acceptable) remove the [Required] validation annotation for your file from your model and check for the file in your Controller action, and add an error if not found:

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> ActionWithFileUpload(ViewModel viewModel)
{
    if (ModelState.IsValid)
    {
        if (Request.Files.Count > 0)
        {
            var postedFile = Request.Files[0];
            if (postedFile != null && postedFile.ContentLength > 0)
            {
                string imagesPath = HttpContext.Server.MapPath("~/Content/Images"); // Or file save folder, etc.
                string extension = Path.GetExtension(postedFile.FileName);
                string newFileName = $"NewFile{extension}";
                string saveToPath = Path.Combine(imagesPath, newFileName);
                postedFile.SaveAs(saveToPath);
            }
        }
        else
        {
            ModelState.AddModelError(string.Empty, "File not selected.");
        }
    }

    return RedirectToAction("Index"); // Or return view, etc.
}
Tim Tyler
  • 2,380
  • 2
  • 16
  • 13