如何使用multipart/form-data格式进行ASP.NET MVC Ajax表单提交?

26

我正在开发一个 ASP.NET MVC 网站,其中有一个表单,可以使用表单标记上的 multipart/form data enctype 选项上传文件。像这样:

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

我该如何编写代码,以实现 ASP.NET MVC Ajax 表单提交呢?

12个回答

34
很有可能,但是需要走一段漫长的路。 步骤1:编写你的表格。

例如:


步骤1:编写您的表单。
@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" />
}

步骤2:拦截请求并将其发送到服务器

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

步骤3:因为您进行了ajax调用,所以您可能想要替换一些multipart/form-data的图像或其他内容。

例如:

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

MSG变量是一个base64加密的字符串。在我的情况下,它是图像的源。

通过这种方式,我成功地更改了个人资料图片,之后图片立即更新。 同时,请确保在Application_Start(global.asax)中添加以下代码: ValueProviderFactories.Factories.Add(new JsonValueProviderFactory()); 非常好用吧?

P.S.: 这个解决方案可行,所以不要犹豫,可以询问更多细节。


1
从Razor传递一个虚拟方法,并从JavaScript提交给正确的方法。 :) - Demian Flavius
1
你好,你可以在我的回答中看到代码。我能告诉你的是,HTML5支持文件上传,如果你开发的应用程序只需要在HTML5上运行,那么有一种更简单的方法。 - Demian Flavius
2
添加 event.stopPropagation(); 以防止重复提交。 - BillD
@DemianFlavius,我想上传一个文件。 - Golda
2
[HttpPost] public ActionResult YourAction(HttpPostedFileBase file) { /* 在此处保存文件 */ return RedirectToAction(YourAction); } - Demian Flavius
显示剩余3条评论

34
我遇到了这个小技巧,它可以很好地解决这个问题。

我遇到了这个小技巧,它可以很好地解决这个问题。

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

1
将此内容复制并粘贴到 document.ready 中,它会立即生效。 - Remco Nonhebel
1
对我来说也一样。只是复制/粘贴了这段代码,它非常有效。我曾经为解决问题而苦恼得掉头发,谢谢。 - Hellcat8
1
这应该是被选中的答案。 - Anup Sharma
在花费数小时尝试弄清楚一切后,这个解决方案起作用了。 - kerl
太棒了!在微软Edge浏览器中也能运行。实际上,这应该被标记为答案。 - Pavan G R

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

但是在第二种情况下,我不确定它是否有效。


2
实际上,你的第二个选项可行。我在这里遇到了另一个类似的问题,但是使用的是普通的beginform而不是ajax beginform helper...使用新的enctype对象,所以我想尝试一下,结果它起作用了。 - dswatik
3
@dswatik,你能发一个关于#2的例子吗?我尝试过了,但无法让它起作用。 - Jim
4
好的,请dswatik提供一个示例,因为一切都发布得很好,但我没有收到服务器上的任何图像文件! - Storm
4
我也没有将任何文件发送到服务器。 - RayLoveless
AjaxHelper没有BeginForm方法吗? - Stuart Dobson
显示剩余3条评论

5

5

以下是我使用并且可行的代码!!这是@James 'Fluffy' Burton方案的复制。我仅仅对他的答案进行了改进,以便新手在MVC上能够快速理解后果。

以下是我的视图:

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

以下是我的控制器:
[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 });
    }

以下是上述代码的简要说明: 通过Ajax,我已将我的Excel (*.csv)文件发布到服务器,并使用Nuget包(LumenWorksCsvReader)将其读取到DataTable中。
太好了!它很有效。感谢@James。

你知道如何在这个上面进行回调吗? - Aswin Arshad
1
你可以在这里查看 https://dev59.com/NW035IYBdhLWcg3wVefn 或者像这里 https://dev59.com/5WLVa4cB1Zd3GeqPsg3F 一样为表单卸载添加另一个事件监听器。 - Karthick Jayaraman

2

对于那些在MVC中使用@Ajax.BeginForm进行multipart enctypes / file uploads仍有问题的人

诊断和解决方案

在由@Ajax.BeginForm助手生成的表单元素上运行“检查元素”工具会发现,该助手不可思议地覆盖了指定的控制器参数。如果您为部分Postback实现了一个单独的控制器,那么就会出现这种情况。

修复问题的快速方法是将html action属性值明确指定为/<yourcontrollername>/<youractionname>

示例

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

2

实际上我自己回答了这个问题...

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

2
如果您需要使用OnSuccess AjaxOption并/或在控制器中使用Request.IsAjaxRequest()来检查请求类型,即...
@using (Ajax.BeginForm("FileUploader", null, new AjaxOptions { HttpMethod = "POST", UpdateTargetId = "elementToUpdate", OnSuccess = "mySuccessFuntion(returnedData)", OnFailure = "myFailureFuntion(returnedData)"}, new { enctype = "multipart/form-data" }))

然后您可以使用以下代码(我修改了@James 'Fluffy' Burton的答案)。如果可能的话,这也将把响应文本转换为JSON对象(如果您不想要,可以省略此步骤)。

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

注意:我使用javascript函数eval()将字符串转换为函数……如果有更好的解决方案,请留言评论。 我还使用了JQuery JSON.parse(),因此这不是一个纯javascript解决方案,但它并不是脚本运行所必须的,因此可以删除。


1

我将Brad Larson的答案与Amirhossein Mehrvarzi的答案结合起来,因为Brad的答案没有提供任何处理响应的方法,而Amirhossein的答案会导致2次postback。 在发送之前,我只是添加了($('#formBacklink').valid())来调用模型验证。

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() 适用于多部分表单数据,下面是相同功能的工作代码示例:

视图:

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

控制器操作方法:

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

顺便提一下,确保文件上传控件的“name”属性和传递给Action方法UploadFile()的参数名称相同(在本例中为“files”)。


我不明白这是如何工作的。这位先生在这里 https://dev59.com/yGMk5IYBdhLWcg3wtQBd#19044689 也建议完全相同,但对我来说不起作用。也许你有一个可以无缝实现这一点的插件,像jQuery表单插件? - Mikayil Abdullayev
嗨,Mikayil,你说它对你不起作用的具体问题是什么?我的意思是控制器的方法从未被触发还是文件参数为空?第一种情况的可能解决方案(当控制器方法从未被触发时),请检查上传按钮的输入类型是否为submit。对于第二种情况(当文件参数在控制器端接收为空时),我将重申上面答案中的脚注,即文件上传控件的名称属性应与控制器端的参数名称相同。希望这可以帮助到你。 - Rachit Pandey
问题出在第二种情况,且 name 属性与参数名匹配。 - Mikayil Abdullayev
请问您能否分享一下您使用文件上传器的视图代码?另外,我在上面的回答中提供的代码是直接从一个工作应用程序中获取的,因此您也可以将其与您的代码进行比较。 - Rachit Pandey
1
这个“有效”的原因可能是某些人的Ajax.BeginForm表现得像Html.BeginForm一样,而不是异步提交。当您没有正确加载jquery.unobtrusive-ajax.js时,通常会发生这种情况。因此,这不是一个正确的答案。 - Jason Honingford

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接