jQuery Ajax/JSON 使用 .Net 6 MVC 模型投标序列化上传文件

Upload files using .Net 6 MVC model biding serialization by jQuery Ajax/JSON

需要通过 AJAX jQuery/JSON.

使用 MVC 模型投标上传文件

我之前使用的是正常的提交表单,但现在我需要更改为 AJAX。 我怎样才能做到这一点?我的意思是,使用 MVC 和 AJAX 进行投标,序列化我的表格或类似的东西。

现在,我的控制器上的 imagemPro 和 imagemPre 总是 'null'。

在我看来:

    @model Ri.Models.Produto

    <form class="settings-form" id="frmAdd" enctype="multipart/form-data">
       <label for="setting-input-1" class="form-label">Título</label>
       <input asp-for="@Model.TituloProduto" type="text" class="form-control" required>
       <input asp-for="@Model.ImagemProduto" type="file" class="form-control" required>

       <label for="setting-input-1" class="form-label">Premio</label>
       <input asp-for="@Model.TituloPremio" type="text" class="form-control" required>
       <input asp-for="@Model.ImagemPremio" type="file" class="form-control" required>
                            
       <input type="button" value="Criar" class="btn app-btn-primary" id="btnAdd">
    </form>     
            
    @section scripts{
        <script src="~/admin/js/produtoAdd.js"></script>
    }

在我的控制器上:

[HttpPost("/api/ProdutoAdd")]
public async Task<IActionResult> ProdutoAdd([Bind("TituloProduto,ImagemProduto,TituloPremio,ImagemPremio")] Produto produto, IFormFile imagemPro, IFormFile imagemPre)
{
    if (!ModelState.IsValid)
    {
        return Json(new { success = false, msg = "1" });
    }
   
    if (imagemPro != null)
    {
        var name = Path.Combine(_enviroment.WebRootPath + "/imgs", System.IO.Path.GetFileName(imagemPro.FileName));
        await imagemPro.CopyToAsync(new FileStream(name, FileMode.Create));
        produto.ImagemProduto = imagemPro.FileName;
     }

     if (imagemPro != null)
     {
        var name = Path.Combine(_enviroment.WebRootPath + "/imgs", System.IO.Path.GetFileName(imagemPre.FileName));
        await imagemPro.CopyToAsync(new FileStream(name, FileMode.Create));
        produto.ImagemPremio = imagemPre.FileName;
     }

     _context.Add(produto);
     await _context.SaveChangesAsync();
   
     return Json(new { success = true });
}

我的脚本:

$(function () {
    $("#btnAdd").click(function (e) {
        e.preventDefault();
        var _this = $(this);
        var _form = _this.closest("form");

        var isvalid = _form.valid();

        if (isvalid) {
            Create();
        }
        else {
            //alert('false');
        }
    });

    Create = function () {
        var options = {};
        options.type = "POST";
        options.url = "/api/ProdutoAdd";
        options.dataType = "JSON";
        options.cache = true;
        options.async = true;
        contentType = "application/json; charset=utf-8",
        options.data = $('#frmAdd').serialize();

        options.beforeSend = function (xhr) {
            xhr.setRequestHeader("XSRF-TOKEN", $('input:hidden[name="__RequestVerificationToken"]').val());
        };

        options.success = function (data) {
        };

        options.error = function (res) {

        };
        $.ajax(options);
    };
});

您的负载和您的内容类型不匹配。 jQuery.serialize 将您的表单数据编码为 application/x-www-form-urlencoded,但您告诉服务器期望内容类型为 application/json。最简单的解决方案是更改内容类型。

此外,您可能希望使用表单的表单 submit 事件,而不是按钮上的 'click' 事件。这样做的原因是浏览器除了单击按钮之外还会以其他方式提交表单(例如,在文本输入时按“enter”键)。提交将处理所有这些方法。

另一个注意事项:您创建 options 对象的方法可以使用,但该语法有点笨拙。最常见的“最佳实践”是声明与对象内联的属性:

var options = {
  type: 'POST',
  url: '/api/ProdutoAdd',
  // etc ...
  success: function (data) {
  },
  // etc ...
};

这使您不必在每个 属性 之前键入 options.

我建议你创建一个viewModel

public class ProdutoViewModel
{
public Produto Produto  {get; set;} 
public IFormFile ImagemPro {get; set;}
public IFormFile ImagemPre {get; set;}
}

动作(也删除 Bind 属性)

[HttpPost("/api/ProdutoAdd")]
public async Task<IActionResult> ProdutoAdd(ProdutoViewModel model )

我建议您使用提交按钮而不是 ajax,这对您来说会容易得多

@model ProdutoViewModel

    <form class="settings-form" id="frmAdd" enctype="multipart/form-data">
       ....
           
       <input type="submit" value="Criar" class="btn app-btn-primary">
    </form>     

1.ModelBinding通过name属性绑定属性,你这里的参数名是imagemPro/imagemPre.

2.The jQuery serialize() 方法将不包括输入文件元素。因此用户选择的文件不会包含在序列化值中。

您需要创建一个 FormData 对象,将文件附加到该对象,然后将表单字段值也附加到同一个 FormData 对象。您可以简单地遍历所有输入字段并添加它。

这是您可以遵循的完整工作演示:

@model Produto
                                    //add `method="post"` in form tag
                                   //otherwise it will not generate token input
<form class="settings-form" id="frmAdd" enctype="multipart/form-data" method="post">
    <label for="setting-input-1" class="form-label">Título</label>
    <input asp-for="@Model.TituloProduto" type="text" class="form-control" required>
    <input asp-for="@Model.ImagemProduto"  type="file" class="form-control" required>

    <label for="setting-input-1" class="form-label">Premio</label>
    <input asp-for="@Model.TituloPremio" type="text" class="form-control" required>
    <input asp-for="@Model.ImagemPremio"  type="file" class="form-control" required>
                            
    <input type="button" value="Criar" class="btn app-btn-primary" id="btnAdd">
</form>     
            
@section scripts{
      @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
      <script src="~/admin/js/produtoAdd.js"></script>
}

JS:

<script>
        $(function () {
            $("#btnAdd").click(function (e) {
                e.preventDefault();
                var _this = $(this);
                var _form = _this.closest("form");   
                var isvalid = _form.valid();    
                if (isvalid) {
                    Create();
                }
                else {
                }
            });    
            Create = function () {
                var fdata = new FormData();   
                var fileInput1 = $('#ImagemProduto')[0];
                var file1 = fileInput1.files[0];
                fdata.append("imagemPro", file1); 
                
                var fileInput2 = $('#ImagemPremio')[0];
                var file2 = fileInput2.files[0];
                fdata.append("imagemPre", file2);    
                $("form input[type='text']").each(function (x, y) {
                    fdata.append($(y).attr("name"), $(y).val());
                });    
                var options = {};
                options.type = "POST";
                options.url = "/api/ProdutoAdd";
                options.dataType = "JSON";                    
                options.contentType = false;   //change here...
                options.processData= false;    //add this...
                options.data = fdata;    
                options.beforeSend = function (xhr) {
                    xhr.setRequestHeader("XSRF-TOKEN", $('input:hidden[name="__RequestVerificationToken"]').val());
                };    

                options.success = function (data) {
                };    
                options.error = function (res) {    
                };
                $.ajax(options);
            };
        });
    </script>