0202年了, jQuery可以继续梭哈...
记录一下jQuery使用ajax上传文件和Post表单的操作
相关文档: jQuery官方ajax文档
上传文件
仅上传单个文件
前端jquery使用FormData
构造请求数据
<!-- 防止点击提交按钮页面自动刷新, 所以设置 onsubmit="return false;" -->
<form onsubmit="return false;" method="post" class="form-group">
<div>
<input type="file" id="i-avatar" name="image" />
</div>
<div class="mt-2">
<button class="btn btn-primary" onclick="uploadImage()">提交</button>
</div>
</form>
<script type="text/javascript">
function uploadImage() {
const form = new FormData();
form.append("file", $("#i-avatar").prop("files")[0]);
$.ajax({
type: "post",
url: "/videos/image",
contentType: false,
data: form,
processData: false,
success: function () {
window.alert("success");
}
});
}
</script>
后端Action:
[HttpPost("image")]
public async Task<IActionResult> UploadImage([FromForm] IFormFile file)
{
using (var rs = file.OpenReadStream())
{
string path = Path.Combine(_webHostEnvironment.WebRootPath, "upload", "images", file.FileName);
Directory.CreateDirectory(Path.GetDirectoryName(path));
using (var fs = new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite))
{
await rs.CopyToAsync(fs);
await fs.FlushAsync();
}
}
return Ok();
}
这里就是拿到文件流然后保存到~/wwwroot/upload/image
文件夹下, 这里文件名直接使用了用户上传的文件名, 微软官方文档中不建议这么做, 因为可能会有安全问题
写好之后在浏览器中测试, 看一下最终发出的请求是什么样的
可以看到, 虽然我们再ajax函数中将contentType设置成了false, 但是浏览器自动为我们添上了 content-type: multipart/form-data; boundary=----WebKitFormBoundaryxZyRmmXUfCxJRX7A
, 一开始我手动设置了contentType: multipart/form-data
, 这样肯定是不行的, 至于为什么, 因为没有设置boundary
这个东西啊
简单总结一下ajax要注意的配置
dataType
, 表示期望服务器返回的类型, 这个不能乱填, 如果返回的格式是json
则设置为 json
, 返回的是纯文字, 则设置为 text
, 这个属性还可以被设置为 xml
, html
, script
, jsonp
, 如果不设置这个属性, 那么jQuery会根据响应的MIME来自动推断, 如果你不确定响应的格式, 那么就不要设置这个属性;
contentType
, 发送数据给服务器时, 指定的数据的格式, 注意, 一定要注意, 这个属性是有默认值的, 且默认值是 application/x-www-form-urlencoded; ciharset=UTF-8
, 如果服务器不接受这种类型的输入, 那么就会请求失败, 所以务必确认好再设置, 如果将这个属性设置为false
, 那么jquery就不会在请求头的加入content-type
processData
, 告诉jquery, 是否要将请求数据内容转换成 application/x-www-form-urlencoded
, 默认是true
, 也就是说jquery会将ajax的data属性格式化成 application/x-www-form-urlencoded
这种数据格式, 关于Post请求的数据格式的问题, 可以看这篇博文, HTTP_POST请求的数据格式, 非常详细, 如果是上传文件的场景, 要将这个属性设置成 false
上传文件的同时, 再传一些其他 Key-Value 类型数据
在用户注册的场景下, 需要提交用户名, 密码和头像等信息, 头像往往是一个图片文件; 当然, 也可以先单独上传头像, 然后传图片的url给用户注册接口api, 但是, 我觉得更好的做法是直接把文件一起传给用户注册接口的api, 这个接口同时保存用户头像;
还是使用FormData
对象来构造请求数据:
<form onsubmit="return false;">
<div>
<label>
用户名: <input type="text" id="username" name="username" value="" />
</label>
</div>
<div>
<label>
密码: <input type="password" id="password" name="password" value="" />
</label>
</div>
<div>
<label>
头像: <input type="file" id="avatar" name="avatar" value="" multiple/>
</label>
</div>
<div><button class="btn btn-primary" onclick="registerUser()">注册</button></div>
<div id="register-result"></div>
</form>
<script type="text/javascript">
function registerUser() {
const formData = new FormData();
formData.set("username", $("#username").val());
formData.set("password", $("#password").val());
const files = $("#avatar").prop("files");
if (files && files.length > 0)
formData.set("avatar", files[0]);
// 如果有多个文件怎么办?
//for (let i = 0; i < files.length; i++) {
// formData.set(i, files[i]);
//}
$.ajax({
url: "/user",
type: "post",
processData: false,
contentType: false,
data: formData,
success: function (result) {
$("#register-result").text(JSON.stringify(result));
}
});
}
</script>
后台接收数据:
[HttpPost]
public Task<IActionResult> Register([FromForm] IFormCollection form)
{
return Task.FromResult<IActionResult>(Ok(form));
}
测试看一下请求是什么样的
可以看到 content-type
依旧是 multipart/formdata; boundary...
这种, 但是, 如果我要在传图片的同时, 传一些其他数据数据或者复杂对象怎么办啊? 比如我后台定义这样的对象:
public class VideoTagDto
{
public string TagName { get; set; }
public DateTime CreateDate { get; set; }
}
public class ActorDto
{
public string Name { get; set; }
public string Sex { get; set; }
public string Country { get; set; }
public DateTime Birth { get; set; }
}
public class VideoDto
{
public string Name { get; set; }
public DateTime PublishDate { get; set; }
public IList<VideoTagDto> VideoTags { get; set; }
public IList<ActorDto> Actors { get; set; }
public IFormFile CoverImage { get; set; }
public VideoDto()
{
VideoTags = new List<VideoTagDto>();
Actors = new List<ActorDto>();
}
}
前台有没有办法可以让后台接口接收到 VideoDto
呢? VideoDto
里面包含了 IFormFile
类型 CoverImage
;
不好意思, 好像不太可以, 我尝试了希望使用 application/json
格式来上传文件和其他复杂对象, 不过浏览器的File
对象没办法序列化成JSON格式; 所以只能先上传文件, 然后拿到文件的url, 再和其他数据一起上传