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

I am working on a ASP.NET MVC web site which has a form that allows for the upload of files using the multipart/form data enctype option on the form tag like so

<form enctype="multipart/form-data" method="post" action='<%= Url.Action("Post","Entries",new {id=ViewData.Model.MemberDetermination.DeterminationMemberID})  %>'>

How would I write this to do an ASP.NET MVC Ajax form post instead?

Answers:

Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.

Method 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.

Method 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);

Method 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.

Method 4

The jquery forms plugin supports file uploads in this way.

Method 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

Method 6

I actually answered the question myself…

<% using (Ajax.BeginForm("Post", "Entries", new { id = ViewData.Model.MemberDetermination.DeterminationMemberID }, new AjaxOptions { UpdateTargetId = "dc_goal_placeholder" }, new { enctype = "multipart/form-data" }))

Method 7

For those who still have problems using @Ajax.BeginForm for multipart enctypes / file uploads in MVC

Diagnosis and proposed solution

Running the “Inspect element” tool on a form element generated by the @Ajax.BeginForm helper reveals that the helper, rather inexplicably, overrides the controller parameter specified. This is the case if you implemented a separate controller for your partial postback.

A quick-fix for the problem is to explicitly specify your html action attribute value as /<yourcontrollername>/<youractionname>.

Example

@using (Ajax.BeginForm("", "", new AjaxOptions() { HttpMethod = "POST", UpdateTargetId = "<TargetElementId>", InsertionMode = InsertionMode.Replace }, new { enctype = "multipart/form-data", action = "/<Controller>/<Action>" }))

Method 8

If you need to use the OnSuccess AjaxOption and/or use Request.IsAjaxRequest() in the controller to check the request type i.e.

@using (Ajax.BeginForm("FileUploader", null, new AjaxOptions { HttpMethod = "POST", UpdateTargetId = "elementToUpdate", OnSuccess = "mySuccessFuntion(returnedData)", OnFailure = "myFailureFuntion(returnedData)"}, new { enctype = "multipart/form-data" }))

Then you can use the following code (I’ve modified @James ‘Fluffy’ Burton’s answer). This will also convert the response text to JSON object if it can (you can omit this if you want).
<script>
if(typeof window.FormData === 'undefined') {
    alert("This browser doesn't support HTML5 file uploads!");
}
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.setRequestHeader("x-Requested-With", "XMLHttpRequest"); // this allows 'Request.IsAjaxRequest()' to work in the controller code
            xhr.onreadystatechange = function () {
                if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
                    var returnedData; //this variable needs to be named the same as the parameter in the function call specified for the AjaxOptions.OnSuccess
                    try {
                        returnedData = JSON.parse(xhr.responseText); //I also want my returned data to be parsed if it is a JSON object
                    }catch(e){
                        returnedData = xhr.responseText;
                    }
                    if (form.dataset.ajaxSuccess) {
                        eval(form.dataset.ajaxSuccess); //converts function text to real function and executes (not very safe though)
                    }
                    else if (form.dataset.ajaxFailure) {
                        eval(form.dataset.ajaxFailure);
                    }
                    if (form.dataset.ajaxUpdate) {
                        var updateTarget = document.querySelector(form.dataset.ajaxUpdate);
                        if (updateTarget) {
                            updateTarget.innerHTML = data;
                        }
                    }
                }
            };
            xhr.send(new FormData(form));
        }
    }
}, true);
</script>

N.B. I use the javascript function eval() to convert the string in to a function… if anyone has a better solution please comment.
I also use JQuery JSON.parse() so this isn’t a vanilla javascript solution but it isn’t required for the script to function so it could be removed.

Method 9

I mixed Brad Larson answer with Amirhossein Mehrvarzi, because Brad answer wasn’t providing any way to handle the response and Amirhossein was causing 2 postbacks.
I just added ($(‘#formBacklink’).valid()) to call model validation before send.

window.addEventListener("submit", function (e) {
        if ($('#formBacklink').valid()) {
            var form = e.target;
            if (form.getAttribute("enctype") === "multipart/form-data") {
                if (form.dataset.ajax) {
                    e.preventDefault();
                    e.stopImmediatePropagation();

                    var dataString;
                    event.preventDefault();
                    var action = $("#formBacklink").attr("action");
                    if ($("#formBacklink").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($("#formBacklink").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       
                        }
                    });
                }
            }
        }
    }, true);

Method 10

Ajax.BegineForm() works with multipart form data and here’s the working code example for the same:

View:

@using(Ajax.BeginForm("UploadFile","MyPOC",
        new AjaxOptions { 
            HttpMethod = "POST"
        }, 
        new 
        {
            enctype = "multipart/form-data"
        }))
    {
        <input type="file" name="files" id="fileUploaderControl" />
        <input type="submit" value="Upload" id="btnFileUpload" />
    }

Controller Action Method:
public void UploadFile(IEnumerable<HttpPostedFileBase> files)
    {
        HttpPostedFileBase file = files.FirstOrDefault(); //Attach a debugger here and check whether you are getting your file on server side or null.
        if (file != null && file.ContentLength > 0)
        {
            //Do other validations before saving the file

            //Save File
            file.SaveAs(path);
        }
    }

P.S. Make sure the “name” attribute of the file uploader control and the name of the parameter passed to Action method UploadFile() has to be same (i.e. “files” in this case).

Method 11

From my little investigation. All the answers above seems to be correct depending on the problem one is having with the Ajax.BeginForm. However, I have just observe that the problem is with the ~/Scripts/jquery.unobtrusive-ajax.min.js javascript library in some case. So in my case I just removed it from the view model and sort of decided to use JQuery Form plugin for my required need along with the HTML Form instead. This has been suggested above.

Method 12

You can use this code instead of eval

  var body = "function(a){ " + form.dataset.ajaxSuccess + "(a) }";
  var wrap = s => "{ return " + body + " };"
  var func = new Function(wrap(body));
  func.call(null).call(null, returnedData);


All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

0 0 votes
Article Rating
Subscribe
Notify of
guest

0 Comments
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x