简体   繁体   中英

IFormFile always null (ASP.NET Core with MVC/Razor)

I have an ASP.NET Core MVC app attempting to upload an IFormFile. However, the IFormFile is always null. None of the other solutions I've found have solved this issue. What am I doing wrong?

Model

public class EmailForm
{
    [Display(Name = "Add a picture")]
    [DataType(DataType.Upload)]
    [FileExtensions(Extensions = "jpg,png,gif,jpeg,bmp,svg")]
    public IFormFile SubmitterPicture { get; set; }
}

Controller

public async Task<ActionResult> Contribute([Bind("SubmitterPicture")] EmailForm model)
{
    if (ModelState.IsValid)
    {
        //Do some stuff
    }
}

View

        <form method="post" asp-action="Contribute" asp-antiforgery="true" enctype="multipart/form-data" >
            <div class="form-group" >
                <div class="col-md-2">@Html.LabelFor(m => m.SubmitterPicture)</div>
                <div class="col-md-4">
                    <input type="file" name="SubmitterPicture" id="SubmitterPicture" />
    </div>
            <div class="form-group">
                <div class="col-md-offset-2 col-md-10">
                    <input type="submit" class="btn btn-default" value="Contribute" />
                </div>
            </div>
<form method="post" enctype="multipart/form-data">
</form>

enctype="multipart/form-data"

The multipart form data is the key.

You can alternatively get the file from the HttpContext.Request.Form.Files and get rid of the IFormFile interface in your model. I recommend this method as I believe that files have nothing to do with data models.

The example would be:

public IActionResult Index()
{
    //Please note that if no form data is posted
    // HttpContext.Request.Form will throw an exception
    if (HttpContext.Request.Form.Files[0] != null) {
        var file = HttpContext.Request.Form.Files[0];
        using (FileStream fs = new FileStream("Your Path", FileMode.CreateNew, FileAccess.Write, FileShare.Write)) {
            file.CopyTo(fs);
        }
    }
    return View();
}

If this method also fails, that means there is something wrong with the multipart request.

After sitting with the same problem for hours I found the solution.

The Problem:

Submitting a single input in a form.

The Conclusion:

Basically in your html form this won't work if you only have one input besides the submit button. My property was no longer null as soon I added another property on my viewModel and another input in my form.

I hope this helps someone else.

Razor Page HTML:

@page
@model SomeModel

<form method="post" enctype="multipart/form-data">
    <div class="form-group">
        <div class="col-md-10">
            <p>Upload one or more files using this form:</p>
            <input asp-for="Input.SingleInput" type="file" class="form-control" />
            <input asp-for="Input.AnotherInput" class="form-control" />
        </div>
    </div>
    <div class="form-group">
        <div class="col-md-10">
            <input type="submit" value="Upload" />
        </div>
    </div>
</form>

Razor Page Code Behind:

public class SomeModel: PageModel
    {
        [BindProperty]
        public SomeViewModel Input { get; set; }

        public async Task OnGetAsync()
        {

        }

        public async Task<IActionResult> OnPostAsync()
        {
            if (!ModelState.IsValid)
            {
                return Page();
            }

            // This won't be null.
            var showMeSomething = Input.SingleInput;

            return RedirectToPage();
        }
    }

SomeViewModel:

public class SomeViewModel
    {
        public IFormFile SingleInput{ get; set; }
        public string AnotherInput{ get; set; }
    }

I found that by providing names on my html form, I was able to map to parameters (ASP.NET Core 2.1):

Client side:

<form method="post" enctype="multipart/form-data" action="...">
    <input type="file" name="myFile" required />
    <input type="password" name="myPass" required />
    <input type="submit" value="post" />
</form>

Server side:

[HttpPost()]
public async Task<IActionResult> Post(IFormFile myFile, [FromForm]string myPass)
{
    //...
}

Your code looks perfectly fine and it should work as long as you are using the same version of code you posted on the question. I have a very strong feeling that you are getting false for the ModelState.IsValid expression and hence seeing the some sort of unexpected behavior.

The FileExtensions data annotation is supposed to be used with String type properties, not with IFormFile type properties. Because of this reason, the IsValid returns false.

So remove that from your view model.

public class EmailForm
{
    [Display(Name = "Add a picture")]
    [DataType(DataType.Upload)]
    public IFormFile SubmitterPicture { get; set; }
}

Here is a relevant GH issue and explanation from one of the team member, for your reference.

FileExtensions Data annotation invalid ModelState #5117

Similarly, if anyone working in .Net Core 2.1 and are using asp-for inorder bind the model for your input elements, then do not give name & id properties for that <input> elements. Ideally, the InputTagHelper upon the rendering on the browser generates the name and id properties with their values. If you give the same value for name & id wrt Model class, then everything works fine. Else, system doesn't know to which model property it should bind. Better approach is not to give id and name on the <input> Below is the sample code.

<form id="uploadForm" enctype="multipart/form-data" name="uploadForm" asp-action="UploadExcel" method="post">

                <div class="form-group form-group-lg form-group-sm row ">
                    <div class="col-sm-12 col-md-10 col-lg-10 uploadDiv" style="display: flex !important">
                        <label asp-for="FileName" class="col-sm-12 col-md-10 col-lg-10" style="font-size: 15px; max-width: fit-content ">File Name :</label>
                        <input asp-for="FileName" class="form form-control fileName"
                               type="text"
                               placeholder="Enter your file name" />
<!--File upload control-->
                        <label asp-for="FileName" class="col-sm-12 col-md-10 col-lg-10" style="font-size: 15px; max-width: fit-content ">Attachment :</label>
                        <input asp-for="File" required class="form-control" type="file" placeholder="File Name" />
                    </div>
                </div>
                <div class=" form-group form-group-lg form-group-sm row">
                    <span asp-validation-for="FileName" class="text-danger"></span>
                </div>
                <div class=" form-group form-group-lg form-group-sm row">
                    <small>Please upload .xls or .xlxs or json or xml formatted files only</small>
                </div>
                <div class="form-group form-group-lg form-group-sm row">
                    <div class="col-sm-12 col-md-10 col-lg-10">
                        <input type="submit" class="btn btn-primary" name="submit" id="fileUploadButton" value="Upload" />
                        <input type="reset" class="btn btn-Gray" name="result" id="resetButton" value="Reset" />
                    </div>
                </div>
                <a asp-action="DownloadTemplate" asp-controller="Download" title="Click to download template">Import Batch Transactions Template</a>
            </form>

Model.cs

public class ExcelUploadViewModel
    {

        /// <summary>
        /// Gets or Sets the FileName
        /// </summary>
        [Required(ErrorMessage = "FileName is required")]
        public string FileName { get; set; }

        [Required(ErrorMessage = "File is required")]
        [DataType(DataType.Upload)]
        [FromForm(Name = "File")]
        public IFormFile File { get; set; }
}

Upon Submit在此处输入图像描述

Thank you.

I had this issue and the fix was to make sure the element of "input type="file"" had an id and name set. Then, in the controller, set the IFormFile parameter name to be exactly the same, case as well. G-luck

Adding the below to my controller fixed the issue for me. It was because my file was large. Adjust the limits as needed.

 [RequestFormLimits(ValueLengthLimit = int.MaxValue, MultipartBodyLengthLimit = int.MaxValue)]

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM