最近在做的一个工程,后端选用asp.net 2.0 core,前端用vue。选用core纯粹是好奇,想体验一下微软的新技术。还有就是实在不想写java...。技术组成如下:
- 后端:asp.net core 2.0 +JWT Auth + mongodb + RESTFul + swagger
- 前端:Vue + Vuetify
一开始一切都算顺利,可是到了文件上传环节,遇到了一点小挫折(掩面,其实是耽误了一天半时间),现在记录一下:
- 前端上传方式选择:
首先根据微软官网文档,asp.net core文件上传示例全部用的是表单+Razor的方式,后台用IFormFile
接收。顺便吐槽一下,感觉微软总是慢半拍啊,整个core的介绍全都是MVC例子,没有RESTFul的,数据库全都是SQL。没有NoSQL。
SPA文件上传肯定不能用表单提交,那么可以选择的合理方式有:
- axios
- HTML5 原生XMLHttpRequest
- jquery
- 各种封装好的第三方库(如
vue-upload-componen
)
以下对各种方式进行实验:
后端--------------------------
[HttpPost("test")]
public AppResponse UploadTest(IFormFile file)
{
if (file==null)
{
return responser.ReturnError(STATUS_CODE.BAD_REQUEST, "files is empty");
}
return responser.ReturnSuccess(extraInfo: file.FileName);
}
关于文件上传,官方有一段文字解释如下:
If your controller is accepting uploaded files using IFormFile but you find that the value is always null, confirm that your HTML form is specifying an enctype value of multipart/form-data. If this attribute is not set on the <form> element, the file upload will not occur and any bound IFormFile arguments will be null.
意思就是说,上传文件的表单必须设置enctype=multipart/form-data,否则是取不到值的。
但如果是以非表单方式上传,前台应该怎么做?
前端-----------------------------
前台搭建以下简单页面:
<template>
<v-card>
<h1>UPLOAD</h1>
<v-divider></v-divider>
<input type="file" ref="f" />
<v-btn @click.native="uploadOnXMLHttpRequest">使用XMLHttpRequest上传</v-btn>
<v-btn @click.native="uploadOnAxios">使用axios上传</v-btn>
</v-card>
</template>
- 首先说axios,经过实验,无法上传文件到.net core后台:
uploadOnAxios() {
const options = {
url: this.url,
method: 'post',
data: {
'file': this.$refs.f.files[0]
},
// headers: { 'Content-Type': undefined }//无效
// headers: { 'Content-Type': 'multipart/form-data' }//无效
}
this.$http.request(options).then(res => {
console.log(res)
})
},
上面注释掉的两个header,就是试图设置enctype,其实就是表单header里的content-type,经过测试,都没有效果,axios始终发送:application/json
。后台因此拿不到值。
- HTML5 原生XMLHttpRequest
首先是关于浏览器支持,这个要看工程,比如我这个工程,都用到vue和vuetify了,就不考虑兼容性了,放心大胆使用就行。
uploadOnXMLHttpRequest() {
const fileObj = this.$refs.f.files[0] // js 获取文件对象
var url = this.url // 接收上传文件的后台地址
var form = new FormData() // FormData 对象
form.append('file', fileObj) // 文件对象
const xhr = new XMLHttpRequest() // XMLHttpRequest 对象
xhr.open('post', url, true) // post方式,url为服务器请求地址,true 该参数规定请求是否异步处理。
// xhr.setRequestHeader('Content-Type', undefined)
xhr.onload = (evt) => {
var data = JSON.parse(evt)
if (data.status) {
alert('上传成功!')
} else {
alert('上传失败!')
}
} // 请求完成
xhr.onerror = (x) => {
alert('failed:' + JSON.parse(x))
} // 请求失败
xhr.onprogress = (x) => {
console.log(`uploading...${x}%`)
} // 请求失败
xhr.send(form) // 开始上传,发送form数据
},
经测试,可以上传,注意xhr.setRequestHeader('Content-Type', undefined)
被注释掉了,实际上这时XMLHttpRequest能自动设置content-type,这句加了反倒会报错。
- jquery
我没有直接在vue项目中测试jquery,是在一个纯静态html中测试的,使用到了jQuery和query.form插件:
<!doctype html>
<head>
<title>File Upload Progress Demo #2</title>
<style>
body {
padding: 30px
}
form {
display: block;
margin: 20px auto;
background: #eee;
border-radius: 10px;
padding: 15px
}
.progress {
position: relative;
width: 400px;
border: 1px solid #ddd;
padding: 1px;
border-radius: 3px;
}
.bar {
background-color: #B4F5B4;
width: 0%;
height: 20px;
border-radius: 3px;
}
.percent {
position: absolute;
display: inline-block;
top: 3px;
left: 48%;
}
</style>
</head>
<body>
<h1>File Upload Progress Demo #2</h1>
<code><input type="file" name="myfile[]" multiple></code>
<form action="http://localhost:5000/api/document/test" method="post" enctype="multipart/form-data">
<input type="file" name="files" multiple>
<br>
<input type="submit" value="Upload File to Server">
</form>
<div class="progress">
<div class="bar"></div>
<div class="percent">0%</div>
</div>
<div id="status"></div>
<script src="https://cdn.bootcss.com/jquery/1.7.2/jquery.min.js"></script>
<script src="https://cdn.bootcss.com/jquery.form/3.36/jquery.form.min.js"></script>
<script>
(function () {
var bar = $('.bar');
var percent = $('.percent');
var status = $('#status');
$('form').ajaxForm({
beforeSend: function () {
status.empty();
var percentVal = '0%';
bar.width(percentVal)
percent.html(percentVal);
},
uploadProgress: function (event, position, total, percentComplete) {
var percentVal = percentComplete + '%';
bar.width(percentVal)
percent.html(percentVal);
//console.log(percentVal, position, total);
},
success: function () {
var percentVal = '100%';
bar.width(percentVal)
percent.html(percentVal);
},
complete: function (xhr) {
status.html(xhr.responseText);
}
});
})();
</script>
结果:可以上传,这个应该没问题,因为本来就是一个表单上传啊~~,query.form的作用是使用了一个隐藏的iframe。上传后刷新的其实是这个iframe,所以用户感觉不到页面刷新。
虽然实现了,但个人并不喜欢这种hack的写法。jQuery应该退出历史舞台了。也算功成名就。还有,在vue中使用jQuery也不是很难,但总感觉不伦不类。
- 其他库:
这些库功能很多,但个人不建议使用,一来很多功能用不上,二来其底层实现不好控制
总结:
我最后选择了 HTML5 XMLHttpRequest 在asp.net core中上传文件,原生模块,灵活方便,随便就能写出一个个人定制的不错的上传组件。
补充
其实,把上传的文件和程序放在同一个服务器不是很好的做法,完全可以建一个资源服务器进行隔离,以下是用 express和multer建立的一个简单上传文件后台:
var express = require('express')
var multer = require('multer')
var upload = multer({ dest: 'uploads/' })
var app = express()
//allow custom header and CORS
app.all('*',function (req, res, next) {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Headers', 'Content-Type, Content-Length, Authorization, Accept, X-Requested-With , yourHeaderFeild');
res.header('Access-Control-Allow-Methods', 'PUT, POST, GET, DELETE, OPTIONS');
if (req.method == 'OPTIONS') {
res.send(200); //让options请求快速返回/
}
else {
next();
}
});
app.post('/profile', upload.single('file'), function (req, res, next) {
const file = req.file
const body = req.body
res.send('ok,uploaded')
next()
// req.body will hold the text fields, if there were any
})
app.post('/photos/upload', upload.array('photos', 12), function (req, res, next) {
// req.files is array of `photos` files
// req.body will contain the text fields, if there were any
})
var cpUpload = upload.fields([{ name: 'avatar', maxCount: 1 }, { name: 'gallery', maxCount: 8 }])
app.post('/cool-profile', cpUpload, function (req, res, next) {
// req.files is an object (String -> Array) where fieldname is the key, and the value is array of files
//
// e.g.
// req.files['avatar'][0] -> File
// req.files['gallery'] -> Array
//
// req.body will contain the text fields, if there were any
})
var server = app.listen(3000, function () {
var host = server.address().address;
var port = server.address().port;
console.log('Example app listening at http://%s:%s', host, port);
});
这样,上传文件到这个服务器后,拿到文件地址,再回来应用插入到数据库就行
当然,如果数据保密度不高,那用七牛是最简单的了
最近有个想法,出一个vue+core的工程管理系统(PMS)系列教程,各位喜欢就点个赞吧,我看着赞多了就开始写(_)