[筆記][Angular 2][Angular 4][Upload][上傳][WebAPI] Angular 2 (Angular 4) 搭配 WebAPI 檔案上傳 範例

檔案上傳,是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在以前小喵沒有這樣設定,不過這次在測試的時候,發現以下這樣的問題

XMLHttpRequest cannot load http://localhost:56956/api/upload. Response to preflight request doesn't pass access control check: The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'. Origin 'http://localhost:4200' is therefore not allowed access. The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute.

所以,「, SupportsCredentials:=True」這個設定請不要忘記。

 

 

參考資料:

 

相關程式碼:

https://github.com/topcattw/Angular2WebApiFileUploadSample

 


以下是簽名:


Microsoft MVP
Visual Studio and Development Technologies
(2005~2019/6) 
topcat
Blog:http://www.dotblogs.com.tw/topcat