How to do a ASP.NET MVC Ajax form post with multipart/form-data?

Solution 1:

It is possible but it's a long way. Step 1: write your form

ex:

@using (Ajax.BeginForm(YourMethod, YourController, new { id= Model.Id }, new AjaxOptions {//needed options }, new { enctype = "multipart/form-data" }))
{
    <input type="file" id="image" name="image" />
    <input type="submit" value="Modify" />
}

Step 2: intercept the request and send it to the server

<script type="text/javascript">
    $(function() {
        $("#form0").submit(function(event) {
            var dataString;
            event.preventDefault();
            var action = $("#form0").attr("action");
            if ($("#form0").attr("enctype") == "multipart/form-data") {
                //this only works in some browsers.
                //purpose? to submit files over ajax. because screw iframes.
                //also, we need to call .get(0) on the jQuery element to turn it into a regular DOM element so that FormData can use it.
                dataString = new FormData($("#form0").get(0));
                contentType = false;
                processData = false;
            } else {
                // regular form, do your own thing if you need it
            }
            $.ajax({
                type: "POST",
                url: action,
                data: dataString,
                dataType: "json", //change to your own, else read my note above on enabling the JsonValueProviderFactory in MVC
                contentType: contentType,
                processData: processData,
                success: function(data) {
                    //BTW, data is one of the worst names you can make for a variable
                    //handleSuccessFunctionHERE(data);
                },
                error: function(jqXHR, textStatus, errorThrown) {
                    //do your own thing
                    alert("fail");
                }
            });
        }); //end .submit()
    });
</script>

Step 3: Because you make an ajax call you probably want to replace some image or something of multipart/form-data

ex:

handleSuccessFunctionHERE(data)
{
    $.ajax({
        type: "GET",
        url: "/Profile/GetImageModified",
        data: {},
        dataType: "text",
        success: function (MSG) {
            $("#imageUploaded").attr("src", "data:image/gif;base64,"+msg);
        },
        error: function (msg) {
            alert(msg);
        }
    });
}

The MSG variable is an base64 encrypted string. In my case it's the source of the image.

In this way I managed to change a profile picture and after that the picture is immediately updated. Also make sure you add in Application_Start (global.asax) ValueProviderFactories.Factories.Add(new JsonValueProviderFactory()); Pretty nice no?

P.S.: This Solution works so don't hesitate to ask more details.

Solution 2:

I came across this little hack, which resolves it nicely

window.addEventListener("submit", function (e) {
    var form = e.target;
    if (form.getAttribute("enctype") === "multipart/form-data") {
        if (form.dataset.ajax) {
            e.preventDefault();
            e.stopImmediatePropagation();
            var xhr = new XMLHttpRequest();
            xhr.open(form.method, form.action);
            xhr.onreadystatechange = function () {
                if (xhr.readyState == 4 && xhr.status == 200) {
                    if (form.dataset.ajaxUpdate) {
                        var updateTarget = document.querySelector(form.dataset.ajaxUpdate);
                        if (updateTarget) {
                            updateTarget.innerHTML = xhr.responseText;
                        } 
                    }
                }
            };
            xhr.send(new FormData(form));
        }
    }
}, true);

Solution 3:

  1. You can use some additional uploaders (e.g. jQuery multiple file uploader) (I prefer this way and I prefer not to use MS Ajax)
  2. Use:

    AjaxHelper.BeginForm("Post", "Entries", new {id=ViewData.Model.MemberDetermination.DeterminationMemberID}, new AjaxOptions(){/*some options*/}, new {enctype="multipart/form-data"})
    

But in second case I'm not sure that it will work.

Solution 4:

The jquery forms plugin supports file uploads in this way.

Solution 5:

Code which I used and it works !! It's a copy of @James 'Fluffy' Burton solution. I just improvising his answer so that people who is new to MVC will be able to quickly understand the consequences.

Following are my View:

@using (Ajax.BeginForm("FileUploader", null, new AjaxOptions { HttpMethod = "POST", UpdateTargetId = "AjaxUpdatePanel" }, new { enctype = "multipart/form-data", id = "frmUploader" })){
<div id="AjaxUpdatePanel">     
    <div class="form-group">
        <input type="file" id="dataFile" name="upload" />
    </div>

    <div class="form-group">
        <input type="submit" value="Upload" class="btn btn-default" id="btnUpload"/>
    </div>

</div>}

<script>
window.addEventListener("submit", function (e) {
    var form = e.target;
    if (form.getAttribute("enctype") === "multipart/form-data") {
        if (form.dataset.ajax) {
            e.preventDefault();
            e.stopImmediatePropagation();
            var xhr = new XMLHttpRequest();
            xhr.open(form.method, form.action);
            xhr.onreadystatechange = function () {
                if (xhr.readyState == 4 && xhr.status == 200) {
                    if (form.dataset.ajaxUpdate) {
                        var updateTarget = document.querySelector(form.dataset.ajaxUpdate);
                        if (updateTarget) {
                            updateTarget.innerHTML = xhr.responseText;
                        }
                    }
                }
            };
            xhr.send(new FormData(form));
        }
    }
}, true);

Following are my controller:

[HttpPost]
    public JsonResult FileUploader(HttpPostedFileBase upload)
    {
        if (ModelState.IsValid)
        {
            if (upload != null && upload.ContentLength > 0)
            {

                if (upload.FileName.EndsWith(".csv"))
                {
                    Stream stream = upload.InputStream;
                    DataTable csvTable = new DataTable();
                    using (CsvReader csvReader = new CsvReader(new StreamReader(stream), true))
                    {
                        csvTable.Load(csvReader);
                    }
                }
                else
                {
                    return Json(new { dataerror = true, errormsg = "This file format is not supported" });
                }
            }
            else
            {
                return Json(new { dataerror = true, errormsg = "Please Upload Your file" });
            }
        }
        return Json(new { result = true });
    }

Following is the quick Note of above code: Through Ajax, I have posted my excel (*.csv) file to Server and read it to an DataTable using a Nuget package (LumenWorksCsvReader).

Hurray! It works. Thanks @James