javascript - 使用 ASP.NET Web API 在 Angular 6 中下载文件
问题描述
我们有一个带有 ASP.NET Web API 后端的 Angular 客户端(Angular 6)。
要求: 在后端提供的 UI 上下载文件。文件可以是任何类型 - PDF、图像、文档、excel 文件、记事本。
实现如下:
网络 API
[Route("api/Clients/{id:int}/Documents/{documentId}")]
public HttpResponseMessage GetDocument(int id, string documentId)
{
string methodName = "GetDocument";
logger.Info(methodName + ": begins. Id: " + id + ". Document Id: " + documentId);
HttpResponseMessage response = null;
try
{
var ticket = Request.Properties["Ticket"];
var userName = ((Microsoft.Owin.Security.AuthenticationTicket)ticket).Identity.Name;
ClientHelper clientHelper = new ClientHelper(userName);
MemoryStream fileContent = new MemoryStream();
//this._googleDriveManager.Get(documentId).CopyTo(fileContent);
var fileData = this._googleDriveManager.Get(documentId);
//Get file extension
var document = clientHelper.GetDocumentbyDriveId(documentId);
if (fileData.Length > 0)
{
response = new HttpResponseMessage
{
StatusCode = HttpStatusCode.OK,
Content = new ByteArrayContent(fileData.ToArray())
};
response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment");
response.Content.Headers.ContentDisposition.FileName = document.FileName;
//response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/pdf");
response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
response.Content.Headers.ContentLength = fileData.Length;
}
else
{
response = Request.CreateResponse(HttpStatusCode.NotFound);
}
}
catch (Exception exception)
{
//Log exception.
logger.Error(methodName, exception);
var errorModel = new { error = "There was an error." };
response = new HttpResponseMessage();
response.StatusCode = HttpStatusCode.InternalServerError;
}
logger.Info(methodName + " ends");
return response;
}
角代码如下:
服务
import { Injectable } from '@angular/core';
import { AppSettings } from '../helpers/AppConstants';
import { HttpClient, HttpRequest, HttpEventType, HttpResponse, HttpHeaders } from '@angular/common/http';
import { Observable, Subject } from 'rxjs';
import { map, filter, catchError, mergeMap } from 'rxjs/operators';
@Injectable()
export class SharedService {
constructor(private http: HttpClient) { }
//This method gets the details of one client.
public getDocument(id: number, fileId: string) {
return this.http.get(AppSettings.DOCUMENTDOWNLOAD_ENDPOINT(id, fileId),
{responseType: 'blob' as 'json'});
}
}
零件
import { Component, OnInit, ViewChild, ElementRef, Input, Output, EventEmitter } from '@angular/core';
import { Document } from '../../../../models/document';
import { DocumentType } from '../../../../models/document-type';
import { SharedService } from '../../../../services/shared.service';
import { MatDialog, MatDialogConfig, MatTableDataSource, MatPaginator } from '@angular/material';
@Component({
selector: 'app-documents',
templateUrl: './documents.component.html',
styleUrls: ['./documents.component.scss']
})
export class DocumentsComponent implements OnInit {
constructor(private _sharedService: SharedService, public dialog: MatDialog) { }
ngOnInit() {
}
fileDownload(document: any) {
this._sharedService.getDocument(document.clientId, document.driveId)
.subscribe(fileData => {
console.log(fileData);
let b: any = new Blob([fileData], { type: 'application/pdf' });
var url = window.URL.createObjectURL(b);
window.open(url);
}
);
}
}
在客户端,我们在 application/pdf 和 application/octet-stream 之间进行了更改,没有区别。本质上,在 API 上设置的内容标头似乎根本不重要,它只是在响应数据上接收到的具有大小的 blob。
当通过 Postman 访问同一个 API 端点并发送发送和下载请求时,文件下载对话框会按预期弹出 - 带有文件名和扩展名。但在 API 上,它只显示没有文件名或扩展名的 blob。
我们在这里缺少什么?
解决方案
使用文件保护程序库解决了这个问题。
https://www.npmjs.com/package/file-saver
https://shekhargulati.com/2017/07/16/implementing-file-save-functionality-with-angular-4/
服务中的代码现在看起来像这样
import { Injectable } from '@angular/core';
import { AppSettings } from '../helpers/AppConstants';
import { HttpClient, HttpRequest, HttpEventType, HttpResponse, HttpHeaders } from '@angular/common/http';
import { Observable, Subject } from 'rxjs';
import {saveFile, saveAs} from 'file-saver';
import { map, filter, catchError, mergeMap } from 'rxjs/operators';
@Injectable()
export class SharedService {
constructor(private http: HttpClient) {}
downloadFile(data: any, filename: string) {
const blob = new Blob([data], { type: 'application/octet-stream' });
saveAs(blob, filename);
}
//This method gets the details of one client.
public getDocument(id: number, fileId: string, fileName: string) {
this.http.get(AppSettings.DOCUMENTDOWNLOAD_ENDPOINT(id, fileId), {responseType: 'blob'})
.subscribe((data) => this.downloadFile(data, fileName), error => console.log('Error downloading the file.'),
() => console.info('OK'));
}
}
import { Component, OnInit, ViewChild, ElementRef, Input, Output, EventEmitter } from '@angular/core';
import { Document } from '../../../../models/document';
import { DocumentType } from '../../../../models/document-type';
import { SharedService } from '../../../../services/shared.service';
import { MatDialog, MatDialogConfig, MatTableDataSource, MatPaginator } from '@angular/material';
import { DialogComponent } from '../upload/dialog/dialog.component';
//import 'rxjs/Rx' ;
@Component({
selector: 'app-documents',
templateUrl: './documents.component.html',
styleUrls: ['./documents.component.scss']
})
export class DocumentsComponent implements OnInit {
@Input() documentTypes: DocumentType[] = [];
@ViewChild(MatPaginator) paginator: MatPaginator;
@Input()
set documents (value: Document[]) {
if (!value) {
value = [];
}
constructor(private _sharedService: SharedService, public dialog: MatDialog) { }
fileDownload(document: any) {
this._sharedService.getDocument(document.clientId, document.driveId, document.fileName);
}
}