最近在做的一個(gè)工程妇智,后端選用asp.net 2.0 core捅伤,前端用vue西篓。選用core純粹是好奇踪危,想體驗(yàn)一下微軟的新技術(shù)。還有就是實(shí)在不想寫java...铝耻。技術(shù)組成如下:
- 后端:asp.net core 2.0 +JWT Auth + mongodb + RESTFul + swagger
- 前端:Vue + Vuetify
一開始一切都算順利誊爹,可是到了文件上傳環(huán)節(jié),遇到了一點(diǎn)小挫折(掩面瓢捉,其實(shí)是耽誤了一天半時(shí)間)频丘,現(xiàn)在記錄一下:
- 前端上傳方式選擇:
首先根據(jù)微軟官網(wǎng)文檔,asp.net core文件上傳示例全部用的是表單+Razor的方式泡态,后臺(tái)用IFormFile
接收搂漠。順便吐槽一下,感覺微軟總是慢半拍啊某弦,整個(gè)core的介紹全都是MVC例子桐汤,沒有RESTFul的,數(shù)據(jù)庫全都是SQL靶壮。沒有NoSQL怔毛。
SPA文件上傳肯定不能用表單提交,那么可以選擇的合理方式有:
- axios
- HTML5 原生XMLHttpRequest
- jquery
- 各種封裝好的第三方庫(如
vue-upload-componen
)
以下對各種方式進(jìn)行實(shí)驗(yàn):
后端--------------------------
[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);
}
關(guān)于文件上傳腾降,官方有一段文字解釋如下:
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.
意思就是說拣度,上傳文件的表單必須設(shè)置enctype=multipart/form-data,否則是取不到值的螃壤。
但如果是以非表單方式上傳蜡娶,前臺(tái)應(yīng)該怎么做?
前端-----------------------------
前臺(tái)搭建以下簡單頁面:
<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映穗,經(jīng)過實(shí)驗(yàn)窖张,無法上傳文件到.net core后臺(tái):
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)
})
},
上面注釋掉的兩個(gè)header,就是試圖設(shè)置enctype蚁滋,其實(shí)就是表單header里的content-type宿接,經(jīng)過測試,都沒有效果辕录,axios始終發(fā)送:application/json
睦霎。后臺(tái)因此拿不到值。
- HTML5 原生XMLHttpRequest
首先是關(guān)于瀏覽器支持走诞,這個(gè)要看工程副女,比如我這個(gè)工程,都用到vue和vuetify了蚣旱,就不考慮兼容性了碑幅,放心大膽使用就行戴陡。
uploadOnXMLHttpRequest() {
const fileObj = this.$refs.f.files[0] // js 獲取文件對象
var url = this.url // 接收上傳文件的后臺(tái)地址
var form = new FormData() // FormData 對象
form.append('file', fileObj) // 文件對象
const xhr = new XMLHttpRequest() // XMLHttpRequest 對象
xhr.open('post', url, true) // post方式,url為服務(wù)器請求地址沟涨,true 該參數(shù)規(guī)定請求是否異步處理恤批。
// 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) // 開始上傳,發(fā)送form數(shù)據(jù)
},
經(jīng)測試棋返,可以上傳延都,注意xhr.setRequestHeader('Content-Type', undefined)
被注釋掉了,實(shí)際上這時(shí)XMLHttpRequest能自動(dòng)設(shè)置content-type睛竣,這句加了反倒會(huì)報(bào)錯(cuò)晰房。
- jquery
我沒有直接在vue項(xiàng)目中測試jquery,是在一個(gè)純靜態(tài)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>
結(jié)果:可以上傳酵颁,這個(gè)應(yīng)該沒問題,因?yàn)楸緛砭褪且粋€(gè)表單上傳啊~~月帝,query.form的作用是使用了一個(gè)隱藏的iframe躏惋。上傳后刷新的其實(shí)是這個(gè)iframe,所以用戶感覺不到頁面刷新嚷辅。
雖然實(shí)現(xiàn)了簿姨,但個(gè)人并不喜歡這種hack的寫法。jQuery應(yīng)該退出歷史舞臺(tái)了簸搞。也算功成名就扁位。還有,在vue中使用jQuery也不是很難趁俊,但總感覺不倫不類域仇。
- 其他庫:
這些庫功能很多,但個(gè)人不建議使用寺擂,一來很多功能用不上暇务,二來其底層實(shí)現(xiàn)不好控制
總結(jié):
我最后選擇了 HTML5 XMLHttpRequest 在asp.net core中上傳文件,原生模塊怔软,靈活方便垦细,隨便就能寫出一個(gè)個(gè)人定制的不錯(cuò)的上傳組件。
補(bǔ)充
其實(shí)挡逼,把上傳的文件和程序放在同一個(gè)服務(wù)器不是很好的做法括改,完全可以建一個(gè)資源服務(wù)器進(jìn)行隔離,以下是用 express和multer建立的一個(gè)簡單上傳文件后臺(tái):
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);
});
這樣家坎,上傳文件到這個(gè)服務(wù)器后嘱能,拿到文件地址吝梅,再回來應(yīng)用插入到數(shù)據(jù)庫就行
當(dāng)然,如果數(shù)據(jù)保密度不高焰檩,那用七牛是最簡單的了
最近有個(gè)想法憔涉,出一個(gè)vue+core的工程管理系統(tǒng)(PMS)系列教程,各位喜歡就點(diǎn)個(gè)贊吧析苫,我看著贊多了就開始寫(_)