在现代Web开发中,文件上传和下载是常见的需求。本文将介绍如何使用VueJS作为前端框架,ASP.NET Core作为后端API,实现一个简单而有效的文件上传和下载功能。
正在构建的是一个用于销售数字产品的在线商店。前端使用VueJS构建,后端则由ASP.NET Core提供API服务和文件服务。
代码相对直观,下面将简要概述每个代码块的作用,而不是详细解释每一行代码,以免使主题变得混乱。
在main.ts文件中,首先导入API模块。
import api from './services/api';
Vue.prototype.$api = api;
然后,在api-plugin.d.ts文件中,将$api变量附加到Vue上,并给它分配Api类型。
import { Api } from './services/api';
declare module 'vue/types/vue' {
interface Vue {
$api: Api;
}
}
export default Api;
在VueJS组件中,创建了一个变量来保存要上传到服务器的文件。
data() {
return {
files: new FormData(),
};
}
添加一个处理方法来响应用户添加文件到上传列表。
handleFileUpload(fileList: any) {
this.files.append('file', fileList[0], fileList[0].name);
},
Vue组件模板包含文件输入元素。
<input type="file" v-on:change="handleFileUpload($event.target.files)" />
当用户在UI上执行触发上传的操作时,调用API。
this.$api.uploadFile(this.files)
.then((response: YourResponseType) => {
this.hasError = false;
this.message = '文件上传成功';
})
.catch((error) => {
this.hasError = true;
this.message = '文件上传出错';
});
上述组件方法调用API服务的此方法。
public async uploadFile(fileData: FormData): Promise<YourResponseType> {
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<IActionResult> UploadHandler(IFormCollection uploads)
{
if (uploads.Files.Length <= 0) {
return BadRequest("没有文件数据");
}
foreach (var f in uploads.Files) {
var filePath = "YourGeneratedUniqueFilePath";
using (var stream = System.IO.File.Create(filePath)) {
await file.CopyToAsync(stream);
}
}
return Ok();
}
从服务器端开始,API方法将类似于此。由于使用了Kestrel,选择了使用ControllerBase.PhysicalFile()方法,但基础控制器ControllerBase.File()返回方法可能更适合需求。
[HttpGet("[action]")]
public async Task<IActionResult> 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;
}
为了调用服务器上的GetFile()方法,客户端API服务需要公开一个下载方法。
public async downloadFile(id: string): Promise<void> {
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显然不是理想的场景。在案例中,上传的文件被放置在一个目录中,希望处理下载。
为了保护这个“下载”目录,在.NET Core IApplicationBuilder实例中的Startup.cs Configure方法中映射了一点逻辑。这拦截了对这个URL的任何请求,并发送401响应。
app.Map("/downloads", subApp => {
subApp.Use(async (context, next) => {
context.Response.StatusCode = StatusCodes.Status401Unauthorized;
});
});