Asp.net core 2.0 +SPA文件上傳注意事項(xiàng)

最近在做的一個(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)在記錄一下:
  1. 前端上傳方式選擇:
    首先根據(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>
  1. 首先說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)因此拿不到值。

  1. 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ò)晰房。

  1. 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>&lt;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也不是很難趁俊,但總感覺不倫不類域仇。

  1. 其他庫:
    這些庫功能很多,但個(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è)贊吧析苫,我看著贊多了就開始寫(_)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末兜叨,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子衩侥,更是在濱河造成了極大的恐慌国旷,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,509評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件茫死,死亡現(xiàn)場離奇詭異跪但,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)峦萎,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評論 3 394
  • 文/潘曉璐 我一進(jìn)店門屡久,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人爱榔,你說我怎么就攤上這事被环。” “怎么了详幽?”我有些...
    開封第一講書人閱讀 163,875評論 0 354
  • 文/不壞的土叔 我叫張陵筛欢,是天一觀的道長。 經(jīng)常有香客問我唇聘,道長版姑,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,441評論 1 293
  • 正文 為了忘掉前任迟郎,我火速辦了婚禮剥险,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘宪肖。我一直安慰自己炒嘲,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,488評論 6 392
  • 文/花漫 我一把揭開白布匈庭。 她就那樣靜靜地躺著夫凸,像睡著了一般。 火紅的嫁衣襯著肌膚如雪阱持。 梳的紋絲不亂的頭發(fā)上夭拌,一...
    開封第一講書人閱讀 51,365評論 1 302
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼鸽扁。 笑死蒜绽,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的桶现。 我是一名探鬼主播躲雅,決...
    沈念sama閱讀 40,190評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼骡和!你這毒婦竟也來了相赁?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,062評論 0 276
  • 序言:老撾萬榮一對情侶失蹤慰于,失蹤者是張志新(化名)和其女友劉穎钮科,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體婆赠,經(jīng)...
    沈念sama閱讀 45,500評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡绵脯,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,706評論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了休里。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蛆挫。...
    茶點(diǎn)故事閱讀 39,834評論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖妙黍,靈堂內(nèi)的尸體忽然破棺而出悴侵,到底是詐尸還是另有隱情,我是刑警寧澤废境,帶...
    沈念sama閱讀 35,559評論 5 345
  • 正文 年R本政府宣布畜挨,位于F島的核電站筒繁,受9級特大地震影響噩凹,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜毡咏,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,167評論 3 328
  • 文/蒙蒙 一驮宴、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧呕缭,春花似錦堵泽、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至片仿,卻和暖如春纹安,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評論 1 269
  • 我被黑心中介騙來泰國打工厢岂, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留光督,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,958評論 2 370
  • 正文 我出身青樓塔粒,卻偏偏與公主長得像结借,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子卒茬,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,779評論 2 354

推薦閱讀更多精彩內(nèi)容