web-dev-qa-db-fra.com

Comment faire un post ASP.NET MVC Ajax avec multipart/form-data?

Je travaille sur un site Web ASP.NET MVC dont le formulaire permet le téléchargement de fichiers à l'aide de l'option en-type multipart/form data sur la balise de formulaire,

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

Comment pourrais-je écrire ceci pour faire un post de formulaire ASP.NET MVC Ajax à la place?

22
dswatik
  1. Vous pouvez utiliser des outils de téléchargement supplémentaires (par exemple, un outil de téléchargement de fichiers multiples )
  2. Utilisation:

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

Mais dans le second cas, je ne suis pas sûr que cela fonctionnera. 

5
zihotki

C'est possible mais c'est un long chemin .. 1ère étape: écrivez votre formulaire

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" />
}

Étape 2: intercepter la demande et l'envoyer au serveur

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

Étape 3: Comme vous effectuez un appel ajax, vous souhaiterez probablement remplacer une image ou un élément de type 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);
        }
    });
}

La variable MSG est une chaîne cryptée en base64. Dans mon cas, c'est la source de l'image.

De cette façon, j'ai réussi à changer une photo de profil et ensuite, la photo est immédiatement mise à jour. Assurez-vous également d’ajouter dans Application_Start (global.asax) ValueProviderFactories.Factories.Add(new JsonValueProviderFactory()); Pretty Nice no?

P.S .: Cette solution fonctionne donc n'hésitez pas à demander plus de détails.

33
Demian Flavius

Je suis tombé sur ce petit bidouillage qui le résout bien

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

Le plugin jquery forms prend en charge le téléchargement de fichiers de cette manière.

5
Marc Gravell

Code que j'ai utilisé et ça marche !! C'est une copie de la solution @James 'Fluffy' Burton. Je viens d’improviser sa réponse pour que les nouveaux venus chez MVC soient en mesure de comprendre rapidement les conséquences.

Voici mon point de vue:

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

Voici mon contrôleur:

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

Voici la note rapide du code ci-dessus: Grâce à Ajax, j’ai posté mon fichier Excel (* .csv) sur le serveur et je l'ai lu sur un DataTable à l'aide d'un paquet Nuget (LumenWorksCsvReader).

Hourra! Ça marche. Merci @James

4
Karthick Jayaraman

J'ai effectivement répondu à la question moi-même ...

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

Si vous devez utiliser OnSuccess AjaxOption et/ou utiliser Request.IsAjaxRequest() dans le contrôleur pour vérifier le type de demande, par exemple i.e.

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

Ensuite, vous pouvez utiliser le code suivant (j'ai modifié la réponse de @James 'Fluffy' Burton). Cela convertira également le texte de la réponse en objet JSON s'il le peut (vous pouvez l'omettre si vous le souhaitez).

<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. J'utilise la fonction javascript eval() pour convertir la chaîne en une fonction ... si quelqu'un a une meilleure solution, merci de commenter . J'utilise aussi JQuery JSON.parse() donc ce n'est pas une solution javascript Vanilla mais elle n'est pas obligatoire le script doit fonctionner pour pouvoir être supprimé.

1
oli_taz

Pour ceux qui ont encore des problèmes d'utilisation de @Ajax.BeginForm pour les enctypes multiparties/les téléchargements de fichiers dans MVC

Diagnostic et solution proposée

L'exécution de l'outil «Inspecter l'élément» sur un élément de formulaire généré par l'assistant @Ajax.BeginForm révèle que celui-ci remplace, de manière plutôt inexplicable, le paramètre de contrôleur spécifié. C'est le cas si vous avez implémenté un contrôleur distinct pour votre publication partielle.

Une solution rapide au problème consiste à spécifier explicitement votre valeur d'attribut d'action HTML en tant que /<yourcontrollername>/<youractionname>.

Exemple

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

J'ai mélangé la réponse de Brad Larson à Amirhossein Mehrvarzi, car celle-ci ne fournissait aucun moyen de la traiter et qu'Amirhossein causait 2 publications. validation du modèle avant envoi.

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

Ajax.BegineForm () fonctionne avec des données de formulaire en plusieurs parties et voici l'exemple de code de travail correspondant:

Vue:

@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" />
    }

Méthode d'action du contrôleur:

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. Assurez-vous que l'attribut "name" du contrôle de téléchargement de fichier et le nom du paramètre transmis à la méthode Action UploadFile () doivent être identiques (dans ce cas, "fichiers"). 

0
Rachit Pandey

De ma petite enquête. Toutes les réponses ci-dessus semblent être correctes en fonction du problème rencontré avec Ajax.BeginForm. Cependant, je viens d’observer que le problème vient de la bibliothèque javascript ~/Scripts/jquery.unobtrusive-ajax.min.js. Donc, dans mon cas, je l'ai simplement retiré du modèle d'affichage et j'ai en quelque sorte décidé d'utiliser le plug-in JQuery Form pour mon besoin requis, ainsi que le formulaire HTML. Cela a été suggéré ci-dessus. 

0
Godwin Larry