Uploading Multiple files from a single form post in MVC

Some time back I wrote a post explaining how to allow File Upload In An ASP.NET MVC Application. Here I am going to expand on the post a little an show how to allow multiple file upload in a single post to the server.

I used bootstrap for styling.

install-package bootstrap -projectname YourProjectName

The layout page is fairly bare, only style sheet links and a Renderbody html helper.

<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>File Upload Demo</title>
    <link href="~/Content/bootstrap.min.css" rel="stylesheet" />
    <link href="~/Content/bootstrap-theme.min.css" rel="stylesheet" />
    <link href="~/Content/root.css" rel="stylesheet" />
</head>
<body>
    <div>
       @RenderBody() 
    </div>
</body>
</html>

The view model contains two list properties. A list of HttpPostedFileBase objects and a list of string.

namespace FileUpload.Models
{
    public class MultipleFileUploadViewModel
    {
        public List<HttpPostedFileBase> UploadFilePaths { get; set; }
        public List<string> Urls { get; set; }
    }
}

When the view is posted to the server the list of posted files (HttpPostedFileBase) will be iterated in a for loop and the files saved to disk. The save locations will added to the model in the list of string.

namespace FileUpload.Controllers
{
    public class HomeController : Controller
    {
        public ActionResult Multiple()
        {
            return View(new MultipleFileUploadViewModel());
        }

        [HttpPost]
        public ActionResult Multiple(MultipleFileUploadViewModel model)
        {
            var selectedPaths = model.UploadFilePaths.Where(f => f != null);
            if (selectedPaths.Count() == 0)
            {
                ModelState.AddModelError(string.Empty, "No files were selected for upload");
                return View(model);
            }

            model.Urls = new List<string>();
            foreach (var path in selectedPaths)
            {
                var fileName = Path.GetFileName(path.FileName);
                var uploadPath = Path.Combine(Server.MapPath("~/uploads/"), fileName);
                path.SaveAs(uploadPath);
                model.Urls.Add(Url.Content(Path.Combine("/uploads/", fileName)));
            }
            return View(model);
        }
    }
}

The view has 4 file input boxes, they all have the same name which matches the name of the posted file list property. Also make sure the form has the multipart/form-data encryption, missing or wrong encryption will prevent the file data being posted to the controller.

@model FileUpload.Models.MultipleFileUploadViewModel
@{
    ViewBag.Title = "Multiple File Upload";
}

<div class="container">
    <h2>Index</h2>
    <div class="clearfix">
        @using (Html.BeginForm("multiple", "home", FormMethod.Post, new { enctype = "multipart/form-data" }))
        {
            @Html.AntiForgeryToken()
            @Html.ValidationSummary()
            <div class="panel panel-default">
                <div class="panel-heading">
                    File Upload
                </div>
                <div class="panel-body">
                    <div class="form-group">
                        <span><strong>Files to upload</strong></span>
                        <div class="input-group">
                            <input type="file" name="UploadFilePaths" class="form-control" />
                            <input type="file" name="UploadFilePaths" class="form-control" />
                            <input type="file" name="UploadFilePaths" class="form-control" />
                            <input type="file" name="UploadFilePaths" class="form-control" />
                        </div>
                        <span>
                            <input type="submit" class="btn btn-default" value="Upload" />
                        </span>
                    </div>

                </div>
            </div>
        }
        @if (Model.Urls != null)
        {
            <ul>
            @foreach (var url in Model.Urls)
            {
                <li><a href="@url" alt="" target="_blank">@url</a></li>
            }
            </ul>
        }
    </div>
</div>

One last thing to mention is file sizes. By default the largest upload size is 4MB. In this case with multiple files the total size of all files must not exceed the 4MB limit. Exceeding the limit will cause a Maximum Request Length Exceeded error. This maximum can be set in the web.config file as below.

<system.web>
    <httpRuntime maxRequestLength="51200" executionTimeout="240" />
</system.web>

The maxRequestLength unit is kilobytes, so in this example 51200 = 50MB. The executionTimeout is set to 4 minutes (240 seconds).

Getting to grips with Agile Practices

Yesterday I went to my first Lean and Agile meetup at madlab in Manchester.  This was actually a number of firsts, first meetup, first time at madlab and first practical foray in to agile practices.  I have been wishing to attend for a while because I have been reading up on agile, scrum, kanban boards and have got to the point where the next step is to solidify that learning with practical experience.  When I checked the confirmed attendees mid afternoon the event was full with 60 confirmed attendees, so all plans were off or so I thought.  While checking out the agenda and reviewing who was going 4 people cancelled allowing me to grab a place.

The evening included “a hands-on workshop to explore how physical card walls are built” by working through practical scenarios using A3 boards and lots of small sticky notes.

It was quickly apparent that other people knew a lot more than me and they all had a different opinion of what the wall should look like. The knowledge shared at events such as these come from years of experience and can taste like sweet nectar to someone who is trying to get to develop skills in the subject.

There is no substitute to practical experience, reading is good but doing is better.