在TypeScript和ASP.NET Core中处理文件上传和受保护的下载

目录

介绍

背景

使用代码

注册您的API

main.ts

api-plugin.d.ts

上传文件

提交文件

API服务方法

ASP.NET Core API方法

下载文件

客户端API下载

保护上传的文件


介绍

我最近在一个需要上传和下载文件的网站上工作。似乎对某些人来说这是一个痛苦的地方,所以我想我应该写一篇小文章(因为我的上一篇文章已经太久了,已经过期很久了),既为我自己的后代,也为其他东西。

背景

我正在构建的应用程序是销售数字产品的商店。前端全部用VueJS编写,带有一个ASP.NET Core后端API,用于提供文件和SPA

使用代码

该代码是很容易解释的,因此,我将仅简要介绍每个模块的功能,而不是详细介绍所有内容并弄乱该主题。

注册您的API

main.ts

导入您的api模块

import api from './services/api'; -- this is my sites 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()对象内部创建了一个变量,用于保存要发送到服务器的文件。

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';
    });

API服务方法

上面在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);
        });
}

ASP.NET Core API方法

该方法中的代码会根据您自己的要求而有很大的不同,但是基本结构看起来像这样。

[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

客户端API下载

为了在服务器上调用此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目录中文件的人都将被引导,浏览器将收到服务器的错误响应。

我希望您在这种方法的简短描述中能找到一些用处。

你可能感兴趣的:(ASP.NET,CORE,架构及框架,ASP.NET,Core,TypeScript,文件上传,受保护的下载)