26

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?

dswatik
  • 9,129
  • 10
  • 38
  • 53

12 Answers12

34

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.

Amirhossein Mehrvarzi
  • 18,024
  • 7
  • 45
  • 70
Demian Flavius
  • 785
  • 1
  • 10
  • 17
  • You could also make the first call to be a fake one [ ex: going to a void method in the controller]. And then where you see: 'var action = $("#form0").attr("action");' >you can put your actually desired action. – Demian Flavius Nov 23 '12 at 13:05
  • 1
    pass a dummy method from razor and from javascript submit it to the right method.:) – Demian Flavius Aug 10 '13 at 10:43
  • 1
    Hello, you can see the code in my answer. What I can tell you is that html5 support file uploading and if you develop an app that should work only on html5 there is an easier way. – Demian Flavius Aug 13 '13 at 20:06
  • 2
    Add event.stopPropagation(); to keep it from submitting twice. – BillD Sep 14 '13 at 16:11
  • Hi I am trying to this but for some reason the submit function does not fire at all..see my question here http://stackoverflow.com/questions/21259147/jquery-dialogue-submit-function-not-firing – Zaki Jan 21 '14 at 13:24
  • @DemianFlavius, Can you please post the controller coding. – Golda Mar 28 '14 at 13:11
  • @DemianFlavius, I want to upload a file – Golda Mar 28 '14 at 13:18
  • 2
    [HttpPost] public ActionResult YourAction(HttpPostedFileBase file) { /*save file here */ return RedirectToAction(YourAction); } – Demian Flavius Mar 28 '14 at 15:02
34

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);
Brad Larson
  • 170,088
  • 45
  • 397
  • 571
7
  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.

matsjoyce
  • 5,744
  • 6
  • 31
  • 38
zihotki
  • 5,201
  • 22
  • 26
  • 2
    Actually your option 2 works..I came across another question on here that was similar but with the normal beginform not the ajax beginform helper ...with the new enctype object so I figured I would try it out and it worked.. – dswatik Feb 24 '09 at 13:41
  • I been trying to avoid adding too much jquery as it is I been having some compatibility issues with that... i.e. some plug-ins causing others to break etc etc – dswatik Feb 24 '09 at 13:43
  • 3
    @dswatik--Can you post an example of #2? I have tried it, but can't get it to work. – Jim Mar 23 '09 at 02:54
  • 4
    Yes please dswatik post an example, cos everything posts fine but I don't receive any images files on the server ! – Storm Dec 15 '09 at 08:34
  • 4
    Yeah, no files sent to the server for me either. – RayLoveless Jul 13 '11 at 21:44
  • AjaxHelper does not have the BeginForm method?? – Stuart Dobson Jan 27 '14 at 09:56
  • Ajax.BeginForm can not upload file, plz check [File upload using AJAX.BeginForm](http://forums.asp.net/t/1445179.aspx) – Hamid Bahmanabady Jul 29 '15 at 15:29
  • Ajax.BeginForm() works, just make sure that the value for "name" attribute of the control is same as the parameter passed to the action method. I was consistently getting null at server side until I changed the same. – Rachit Pandey Feb 16 '16 at 07:28
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

  • do u know how to do the callback on this ? – Aswin Arshad Aug 16 '17 at 02:22
  • 1
    You check https://stackoverflow.com/questions/5485495/how-can-i-take-advantage-of-callback-functions-for-asynchronous-xmlhttprequest here or you can add another event listener for form unload like here https://stackoverflow.com/questions/9907867/javascript-onload-and-onunload – Karthick Jayaraman Aug 17 '17 at 05:28
5

The jquery forms plugin supports file uploads in this way.

Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
2

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>" }))
Community
  • 1
  • 1
2

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.

oli_taz
  • 197
  • 1
  • 4
  • 18
2

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" }))
dswatik
  • 9,129
  • 10
  • 38
  • 53
1

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

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

Rachit Pandey
  • 346
  • 2
  • 12
  • İ don't understand how this works. This guy here http://stackoverflow.com/a/19044689/751796 also suggests exactly the same but doesn't work for me. Maybe you have a plugin that does that seamlessly, like jquery form plugin? – Mikayil Abdullayev May 13 '16 at 12:01
  • Hey Mikayil, what's the specific problem when you say it's not working for you. I mean is it that the controller's method is never hit or the files param is null? Probable solution for the first case (where controller method is never hit), check that the upload button's input type is submit. For the second one (when files param is received as null at controller) I will reiterate my foot-note in the answer above which says that the name attribute to the file uploader control should be same as the param name at the controller side. Hope this helps. – Rachit Pandey May 24 '16 at 12:34
  • The problem is the second case and the name attribute matches the parameter name – Mikayil Abdullayev May 24 '16 at 12:43
  • Could you please share your view code, where you have used file uploader. Also the code in my answer above has been directly taken from a working application. So you can also compare it with yours. – Rachit Pandey May 24 '16 at 15:34
  • 1
    The probable reason this "works" for some people is their Ajax.BeginForm is behaving like Html.BeginForm and not posting asynchronously. This usually happens when you are not correctly loading jquery.unobtrusive-ajax.js. So this is not a correct answer. – Jason Honingford Nov 29 '18 at 14:49
0

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.

0

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