Search code examples
c#asp.net-core-mvciformfile

IFormFileCollection is NULL when choosing 100 images


Why is IFormFileCollection NULL when posting back to the controller action in C# and ASP.NET Core MVC?

It works if I choose 10 images, and fails if I choose 100 - why? Is there a file upload limit in IFormFileCollection?

Here's the code - controller/actions:

using Microsoft.AspNetCore.Mvc;
using IFormFileCollectionImageUploadTest.Models;

namespace IFormFileCollectionImageUploadTest.Controllers;

public class HomeController : Controller
{
    public IActionResult Index()
    {
        return View();
    }

    [HttpPost]
    public IActionResult Upload(IFormFileCollection images)
    {
        var resultsViewModel = new ResultsViewModel();

        if (images != null && images.Count > 0)
        {
            resultsViewModel.Message = $"IFormFileCollection is NOT NULL, with image count of {images.Count}!";
        }
        else
        {
            resultsViewModel.Message = $"IFormFileCollection is NULL with image count of ZERO!";
        }

        return RedirectToAction("Results", "Home", resultsViewModel);
    }

    [HttpGet]
    public IActionResult Results(ResultsViewModel resultsViewModel)
    {
        return View(resultsViewModel);
    }
}

Index view:

@using IFormFileCollectionImageUploadTest.Models;
@model ImageUploadViewModel
@{
    ViewBag.Title = "Upload Images";
}

<style type="text/css">
    .btn-file-display {
        display: none;
    }

    .btn-file {
        position: relative;
        overflow: hidden;
    }

        .btn-file input[type=file] {
            position: absolute;
            top: 0;
            right: 0;
            min-width: 100%;
            min-height: 100%;
            font-size: 100px;
            text-align: right;
            filter: alpha(opacity=0);
            opacity: 0;
            outline: none;
            background: white;
            cursor: inherit;
            display: block;
        }
</style>

<div class="row">
    <div class="col-md-4 col-12 mx-auto">
        <h2 class="text-center">@ViewBag.Title</h2>
        <p class="text-center">Select multiple images,<br />to upload more than one image at once</p>
        <div class="card card-pricing card-raised">
            <div class="card-body">
                @using (Html.BeginForm("Upload", "Home", FormMethod.Post, new { @enctype = "multipart/form-data", @class = "form-horizontal", @role = "form" }))
                {
                    @Html.AntiForgeryToken();

                    <div class="row">
                        <div class="col-12">
                            <label class="btn btn-block btn-outline-primary">
                                Browse...
                                @Html.TextBoxFor(model => model.Images, "", new { @class = "btn-file-display", @type = "file", @multiple = "multiple" })
                            </label>
                            @Html.ValidationMessageFor(model => model.Images, "", new { @class = "text-danger" })
                            <p id="selectedImageFiles" class="text-success text-center"></p>
                        </div>
                    </div>
                    <div class="row">
                        <div class="col-12">
                            <button class="btn btn-success btn-lg btn-block">Upload</button>
                        </div>
                    </div>
                    <div class="row">
                        <div class="col-12">
                            <p class="text-info">@ViewBag.UploadStatus</p>
                        </div>
                    </div>
                }
            </div>
        </div>
    </div>
</div>

@section scripts
    {
    <script type="text/javascript">$(document).on('change', ':file', function () {
            var input = $(this),
                numFiles = input.get(0).files ? input.get(0).files.length : 1,
                label = input.val().replace(/\\/g, '/').replace(/.*\//, '');
            input.trigger('fileselect', [numFiles, label]);
        });

        $(document).ready(function () {
            $(':file').on('fileselect', function (event, numFiles, label) {
                if (numFiles == 1) {
                    $('#selectedImageFiles').text(numFiles + " image chosen for upload");
                }
                else {
                    $('#selectedImageFiles').text(numFiles + " images chosen for upload");
                }
                console.log(numFiles);
                console.log(label);
            });
        });</script>
}

Results view:

@model ResultsViewModel

<div class="text-center">
    @Model.Message
</div>

Upload view model:

using Microsoft.AspNetCore.Http;
using System.ComponentModel.DataAnnotations;

namespace IFormFileCollectionImageUploadTest.Models;

public class ImageUploadViewModel
{
    [Required(ErrorMessage = "Please select images")]
    public IFormFileCollection[] Images { get; set; }
}

Results view model:

namespace IFormFileCollectionImageUploadTest.Models;

public class ResultsViewModel
{
    public string Message { get; set; }
}

Solution

  • Why is IFormFileCollection NULL when posting back to the controller action in C# and ASP.NET Core MVC ?

    Well based on your scenario and description along with the shared code snippet the issue you are having is related to the default file upload size limit in ASP.NET Core.The default request limit (maxAllowedContentLength) is 30,000,000 bytes, which is approximately 28.6 MB.

    As you may know while we submit request from view to controller the reuqest eventually process from the hosting server either IIS or Kestrel web server. Here comes the main point as the both server has the defult request limit so request containing over the limit would ended up with null request. You can check both IIS and Kestrel document.

    It works if I choose 10 images, and fails if I choose 100 - why? Is there a file upload limit in IFormFileCollection?

    Its not IFormFileCollection but in asp.net core or IIS there's a default request limit but we can customize them in order to upload larger file. It can either be handled by increasing the MultipartBodyLengthLimit property size in program.cs file configuration or we can split large uploads into smaller chunks or using chunking libraries. Even, you can compress the uploaded file as well.

    However, I am more keen to increase MultipartBodyLengthLimit in program.cs file. If you want you can try other ways also.

    Let' have a look how we can implement that.

    Customize request limit in Program.cs file:

    builder.Services.Configure<FormOptions>(options =>
    {
        options.MultipartBodyLengthLimit = 52428800; 
    });
    

    Note: I am currently setting 50MB. If you want to set higher than that, you can do that. For 100MB you can do 100 * 1024 * 1024; But remember, there's server timeout issue as well. So keep that in your consideration while extend your limit.

    If you want to have a look more sample, please refer to this official documnet.