目录
介绍
背景
使用代码
注册您的API
main.ts
api-plugin.d.ts
上传文件
提交文件
API服务方法
ASP.NET Core API方法
下载文件
客户端API下载
保护上传的文件
我最近在一个需要上传和下载文件的网站上工作。似乎对某些人来说这是一个痛苦的地方,所以我想我应该写一篇小文章(因为我的上一篇文章已经太久了,已经过期很久了),既为我自己的后代,也为其他东西。
我正在构建的应用程序是销售数字产品的商店。前端全部用VueJS编写,带有一个ASP.NET Core后端API,用于提供文件和SPA。
该代码是很容易解释的,因此,我将仅简要介绍每个模块的功能,而不是详细介绍所有内容并弄乱该主题。
导入您的api模块
import api from './services/api'; -- this is my sites api
...
Vue.prototype.$api = api;
将$api变量附加到Vue并为其指定Api类型。
import { Api } from './services/api';
declare module 'vue/types/vue' {
interface Vue {
$api: Api;
}
}
export default Api;
在我的VueJS组件中,我在data()对象内部创建了一个变量,用于保存要发送到服务器的文件。
files: new FormData(),
我添加了一个处理程序方法来响应向上传文件添加文件的用户
handleFileUpload(fileList: any) {
this.files.append('file', fileList[0], fileList[0].name);
},
Vue组件模板包含文件输入元素
然后,当用户在您的UI上执行触发上传的操作时,我将调用我的API。
this.$api.uploadFile(this.files)
.then((response: <>) => {
this.hasError = false;
this.message = 'File uploaded';
}).catch((error) => {
this.hasError = true;
this.message = 'Error uploading file';
});
上面在tern中显示的组件方法在我的API服务上调用此方法。
public async uploadFile(fileData: FormData): Promise<<>> {
return await axios.post('/api/to/your/upload/handler', fileData, { headers: { 'Content-Type': 'multipart/form-data' } })
.then((response: any) => {
return response.data;
})
.catch((error: any) => {
throw new Error(error);
});
}
该方法中的代码会根据您自己的要求而有很大的不同,但是基本结构看起来像这样。
[HttpPost("/api/to/your/upload/handler")]
[Consumes("multipart/form-data")]
public async Task UploadHandler(IFormCollection uploads)
{
if (uploads.Files.Length <= 0) { return BadRequest("No file data"); }
foreach (var f in uploads.Files)
{
var filePath = "YourGeneratedUniqueFilePath";
using (var stream = System.IO.File.Create(filePath))
{
await file.CopyToAsync(stream);
}
}
return Ok();
}
这次从服务器端开始,您的API方法将如下所示。自从我使用Kestral以来,我选择使用该ControllerBase.PhysicalFile()方法,但是如果您的控制器更适合您的需求,那么控制器上还有基本的控制器返回方法ControllerBase.File()。
由于我的上传与我的数据存储中的实体相关联,因此需要通过ID值请求下载,但您可以使用任何适合您需要的方法。
[HttpGet("[action]")]
public async Task GetFile(string id)
{
var dir = "GetOrBuildYourDirectoryString";
var fileName = "GetYourFileName";
var mimeType = GetMimeType(fileName);
var path = Path.Combine(dir, fileName);
return PhysicalFile(path, mimeType, version.FileName);
}
public string GetMimeType(string fileName)
{
var provider = new FileExtensionContentTypeProvider();
string contentType;
if (!provider.TryGetContentType(fileName, out contentType))
{
contentType = "application/octet-stream";
}
return contentType;
}
注意:FileExtensionContentTypeProvider类型来自Microsoft.AspNetCore.StaticFiles NuGet包
Install-Package Microsoft.AspNetCore.StaticFiles -Version 2.2.0
为了在服务器上调用此GetFile()方法,我们的客户端API服务需要公开下载方法。这会使事情变得有些棘手。您可能必须配置服务器以提供和/或公开内容处置标头。这有点超出本文的讨论范围,因为我想保持简洁。
我不需要执行任何特定的步骤来访问此标头,但是我确实必须执行一些操作来提取客户端所需的数据——主要是文件名。不幸的是,这段代码并不是特别好。如果有人对如何改善它有任何建议,请告诉我。
public async downloadFile(id: string): Promise {
return await axios.get('/api/download/getfile?id=' + id, { responseType : 'blob' } )
.then((response: any) => {
const disposition = response.headers['content-disposition'];
let fileName = '';
if (disposition && disposition.indexOf('attachment') !== -1) {
const filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/;
const matches = filenameRegex.exec(disposition);
if (matches != null && matches[1]) {
fileName = matches[1].replace(/['"]/g, '');
}
}
const fileUrl = window.URL.createObjectURL(new Blob([response.data]));
const fileLink = document.createElement('a');
fileLink.href = fileUrl;
fileLink.setAttribute('download', fileName);
document.body.appendChild(fileLink);
fileLink.click();
})
.catch((error: any) => {
throw new Error(error);
});
}
这将设置客户端下载并打开本地用户常规浏览器文件保存对话框。
鉴于上面的代码是“手动”处理文件下载和上传的,因此可以认为,浏览器HTML中文件的简单URL不是理想的情况。在我的例子中,上传的文件放在我要处理下载的目录中。
为了保护此“下载”目录,我在该StartUp.cs Configure方法中将少量逻辑映射到.NET Core IApplicationBuilder实例。这将拦截对此URL的任何请求并发送401响应。
app.Map("/downloads", subApp => {
subApp.Use(async (context, next) =>
{
context.Response.StatusCode = StatusCodes.Status401Unauthorized;
});
});
任何试图访问该downloads目录中文件的人都将被引导,浏览器将收到服务器的错误响应。
我希望您在这种方法的简短描述中能找到一些用处。