檔案上傳,是Web開發很普遍的一個需求。Angular 2 的部分,要怎麼處理這一塊呢?請繼續看下去...
緣起
承上篇「[筆記][WebAPI][Upload] WebAPI 檔案上傳範例」,本篇小喵記錄Angular 2 ( Angular 4 )的部分,在測試的過程中,比較大的問題在CORS的處理與解決,這部分,會針對 WebAPI 的部分,做一個補充。
安裝套件:ng2-file-upload
小喵這裡是使用別人寫好的套件「ng2-file-upload」,請輸入以下指令進行安裝:
npm i ng2-file-upload --save
特別注意:小喵發現,這一套件與小喵之前用的一個bootstrap套件「ngx-bootstrap」有衝突,會 把原本安裝好的套件移除如果您也遇到小喵一樣的狀況,請您再次把他裝回去即可。
AppModule
接著,先在AppModule中,處理好後續需要套件或模組:
import { FileUploader, FileDropDirective, FileSelectDirective } from 'ng2-file-upload';
import { HttpModule } from '@angular/http';
import { FormsModule } from '@angular/forms';
import { FormBuilder } from '@angular/forms';
//.....
@NgModule({
  declarations: [
    //...
    UploadComponent,
    FileDropDirective,
    FileSelectDirective,
	//...
  ],
  imports: [
	//...
    FormsModule,
    HttpModule,
    //...
  ],
  providers: [FormBuilder],
	//...
})
其中 imports:[] 中的 「FormsModule, HttpModule」以前在 Angular 2 預設是載入的,但後來的 Angular-Cli 建立的 Angular 4 專案,這部分預設省略,我們把他補回來。
建立Component
接著,建立Component來撰寫上傳的相關部分,首先,透過以下的語法,產生Component 「Upload」
ng g c Upload
在AppComponent中,清掉原有的內容,使用這個Component
<app-upload></app-upload>
TS : upload.component.ts
import { Component, OnInit } from '@angular/core';
import { NgClass, NgStyle} from '@angular/common';
import { FileUploader } from 'ng2-file-upload'
// const URL = '/api/';
// 請依據您的 WebAPI 的網址修正
const URL = 'http://localhost:56956/api/upload';
@Component({
  selector: 'app-upload',
  templateUrl: './upload.component.html',
  styleUrls: ['./upload.component.css'],
  
})
export class UploadComponent implements OnInit {
  constructor() { }
  ngOnInit() {
  }
  public uploader:FileUploader = new FileUploader({url: URL});
  public hasBaseDropZoneOver:boolean = false;
  public hasAnotherDropZoneOver:boolean = false;
 
  public fileOverBase(e:any):void {
    this.hasBaseDropZoneOver = e;
  }
 
  public fileOverAnother(e:any):void {
    this.hasAnotherDropZoneOver = e;
  }
}
CSS : upload.component.css
.my-drop-zone { border: dotted 3px lightgray; }
.nv-file-over { border: dotted 3px red; } /* Default class applied to drop zones on over */
.another-file-over-class { border: dotted 3px green; }
html, body { height: 100%; }
Html : upload.component.html
<form name="form1" method="post" enctype="multipart/form-data" action="api/upload">
<div class="container">
 
    <div class="navbar navbar-default">
        <div class="navbar-header">
            <a class="navbar-brand" href>Angular2 File Upload</a>
        </div>
    </div>
 
    <div class="row">
 
        <div class="col-md-3">
 
            <h3>Select files</h3>
 
            <div ng2FileDrop
                 [ngClass]="{'nv-file-over': hasBaseDropZoneOver}"
                 (fileOver)="fileOverBase($event)"
                 [uploader]="uploader"
                 class="well my-drop-zone">
                Base drop zone
            </div>
 
            <div ng2FileDrop
                 [ngClass]="{'another-file-over-class': hasAnotherDropZoneOver}"
                 (fileOver)="fileOverAnother($event)"
                 [uploader]="uploader"
                 class="well my-drop-zone">
                Another drop zone
            </div>
 
            Multiple
            <input type="file" ng2FileSelect [uploader]="uploader" multiple  /><br/>
 
            Single
            <input type="file" ng2FileSelect [uploader]="uploader" />
        </div>
 
        <div class="col-md-9" style="margin-bottom: 40px">
 
            <h3>Upload queue</h3>
            <p>Queue length: {{ uploader?.queue?.length }}</p>
 
            <table class="table">
                <thead>
                <tr>
                    <th width="50%">Name</th>
                    <th>Size</th>
                    <th>Progress</th>
                    <th>Status</th>
                    <th>Actions</th>
                </tr>
                </thead>
                <tbody>
                <tr *ngFor="let item of uploader.queue">
                    <td><strong>{{ item?.file?.name }}</strong></td>
                    <td *ngIf="uploader.isHTML5" nowrap>{{ item?.file?.size/1024/1024 | number:'.2' }} MB</td>
                    <td *ngIf="uploader.isHTML5">
                        <div class="progress" style="margin-bottom: 0;">
                            <div class="progress-bar" role="progressbar" [ngStyle]="{ 'width': item.progress + '%' }"></div>
                        </div>
                    </td>
                    <td class="text-center">
                        <span *ngIf="item.isSuccess"><i class="glyphicon glyphicon-ok"></i></span>
                        <span *ngIf="item.isCancel"><i class="glyphicon glyphicon-ban-circle"></i></span>
                        <span *ngIf="item.isError"><i class="glyphicon glyphicon-remove"></i></span>
                    </td>
                    <td nowrap>
                        <button type="button" class="btn btn-success btn-xs"
                                (click)="item.upload()" [disabled]="item.isReady || item.isUploading || item.isSuccess">
                            <span class="glyphicon glyphicon-upload"></span> Upload
                        </button>
                        <button type="button" class="btn btn-warning btn-xs"
                                (click)="item.cancel()" [disabled]="!item.isUploading">
                            <span class="glyphicon glyphicon-ban-circle"></span> Cancel
                        </button>
                        <button type="button" class="btn btn-danger btn-xs"
                                (click)="item.remove()">
                            <span class="glyphicon glyphicon-trash"></span> Remove
                        </button>
                    </td>
                </tr>
                </tbody>
            </table>
 
            <div>
                <div>
                    Queue progress:
                    <div class="progress" style="">
                        <div class="progress-bar" role="progressbar" [ngStyle]="{ 'width': uploader.progress + '%' }"></div>
                    </div>
                </div>
                <button type="button" class="btn btn-success btn-s"
                        (click)="uploader.uploadAll()" [disabled]="!uploader.getNotUploadedItems().length">
                    <span class="glyphicon glyphicon-upload"></span> Upload all
                </button>
                <button type="button" class="btn btn-warning btn-s"
                        (click)="uploader.cancelAll()" [disabled]="!uploader.isUploading">
                    <span class="glyphicon glyphicon-ban-circle"></span> Cancel all
                </button>
                <button type="button" class="btn btn-danger btn-s"
                        (click)="uploader.clearQueue()" [disabled]="!uploader.queue.length">
                    <span class="glyphicon glyphicon-trash"></span> Remove all
                </button>
            </div>
 
        </div>
 
    </div>
 
</div>
</form>
執行結果:

Angular 2 (Angular 4)的部分,到這邊就完成囉。可以單筆選取,多筆選取,拖拉放,一次上傳多筆的介面也大多OK,到最後的測試結果也OK沒問題。不過~
剛開始測試,結果在上傳的時候,發生問題,原因是原本的WebAPI,沒有撰寫『CORS』跨網域的相關處理。
所以,接下來,又回到Server端的 WebAPI 程式,要來處理 WebAPI CORS的部分
WebAPI CORS處理
nuget : WebAPI CORS
WebAPI 2後,有套件可以簡單容易的去處理 CORS的問題,所以,首先,開啟NuGet套件管理員,要把 WebAPI CORS 的套件裝起來

App_Start / WebApiConfig
加上 Imports 與設定
Imports System
Imports System.Collections.Generic
Imports System.Linq
Imports System.Web.Http
Imports System.Web.Http.Cors    '<--這裡
Public Module WebApiConfig
    Public Sub Register(ByVal config As HttpConfiguration)
        ' Web API 設定和服務
        config.EnableCors()    '<--這裡
        ' Web API 路由
        config.MapHttpAttributeRoutes()
        config.Routes.MapHttpRoute(
            name:="DefaultApi",
            routeTemplate:="api/{controller}/{id}",
            defaults:=New With {.id = RouteParameter.Optional}
        )
    End Sub
End Module
修改Controller設定
Imports System.Web.Http.Cors
Namespace Controllers
    <EnableCors("*", "*", "*", SupportsCredentials:=True)>
    Public Class UploadController
'....以下略
其中,SupportsCredentials在以前小喵沒有這樣設定,不過這次在測試的時候,發現以下這樣的問題
所以,「, SupportsCredentials:=True」這個設定請不要忘記。
參考資料:
- http://valor-software.com/ng2-file-upload/
 - http://www.inoaspect.com.au/uploading-files-angular-2-asp-net-core-web-api-part-1/
 
相關程式碼:
https://github.com/topcattw/Angular2WebApiFileUploadSample
以下是簽名:
- 歡迎轉貼本站的文章,不過請在貼文主旨上加上【轉貼】,並在文章中附上本篇的超連結與站名【topcat姍舞之間的極度凝聚】,感恩大家的配合。
 - 小喵大部分的文章會以小喵熟悉的語言VB.NET撰寫,如果您需要C#的Code,也許您可以試著用線上的工具進行轉換,這裡提供幾個參考
 
| Microsoft MVP Visual Studio and Development Technologies (2005~2019/6)  | topcat Blog:http://www.dotblogs.com.tw/topcat  |