做航空航天相关系统时,我遇到过一类看上去很基础、实际上非常折腾的需求:用户要把本地一个很大的资料文件夹整体传到服务器上,而且不是“把文件选出来传上去”那么简单,必须把文件夹的层级结构、文件名、校验结果全部保留下来,还能随时看到传了几成、失败了哪些、能不能续传。我当时用的是 ASP.NET MVC(.NET Framework 4.7.2)部署在 IIS 上,整体打磨下来,发现“文件夹上传的精确控制”远不是拖一个上传组件就完事,里面藏着请求验证、IIS 限制、断点状态、路径穿越、并发写入这些一道道坎。
这篇就围绕航空航天场景里的实际约束,把 ASP.NET 下做文件夹上传精确控制的方法、配置和踩坑写清楚。主要内容包括:怎么在浏览器端拿到目录树和相对路径,怎么用分片上传把大目录拆成可控小任务,后端怎么保证目录结构不丢、文件不坏、中途断了能续,还有 IIS 和 web.config 那些真正会卡住你的限制项。适合正在用 ASP.NET / ASP.NET Core 做资料管理、试验数据回传、技术文档入库这类系统的开发者看,尤其是项目里需要处理成百上千个文件、单个文件可能到 GB 级的情况。
1. 航空场景里,“文件夹上传”先别急着写代码
1.1 精确控制的第一层:目录结构和相对路径不能丢
先问一个问题:为什么不能通过普通的上传控件,把所有文件平铺传到服务器上,再由一个人手工整理成目录?答案在航空航天这种场景里非常直白:资料如果失去了原始目录,基本等于废了一半。
举个例子,一次试飞或一次发动机孔探检查产生的数据,往往已经是按“型号/批次/测试项目/日期”组织好的目录结构。里面文件命名很规范,比如 TBO-2024-03-18_LH-01.mp4,但如果全部平铺到一个目录里,文件重名、批次混淆、人工二次归类错误就可能发生。数据合规审计时,要求看到的是“原始目录结构 + 原始文件名 + 校验值”,而不是一个被压平到不辨来源的文件池。
所以文件夹上传的第一层精确控制,就是完整保留相对路径。用户在本地选中的如果是 E:\batch_2024\TBO\images\engine_1\photo_01.jpg,上传到服务器后,它应该落在“上传根目录/batch_2024/TBO/images/engine_1/photo_01.jpg”,而不是只落一个 photo_01.jpg。这意味着前端不能只把文件一股脑抛给后端,必须连同“相对路径”一起传;后端也必须按相对路径逐级创建目录,再落文件。
1.2 精确控制的第二层:可校验、可断点、可审计
如果仅仅是“把文件传完”,那些带进度条的单文件上传控件也能凑合。但航空场景下,网络环境不总是理想。我接触过的部署现场,有的在办公室局域网,有的在临时搭建的测试工位,有 1Gbps 的高速环境,也有延迟高、偶尔断一下的无线网。一次上传几百 GB 的数据时,中间任何一秒断网都可能导致整批重来,这在工作流程上是不能接受的。
因此精确控制至少要包含三个能力:
- 可校验。服务器不能只信客户端说“这个文件传完了”,最好在完成后能核对文件大小、分片数量、最终文件哈希,至少保证文件在传输链路中没有缺损。
- 可断点续传。每个文件按固定大小切块,已经传完的块在服务器端有记录,重试时跳过,而不是从头再来。
- 可审计。谁、什么时候、哪个批次、哪个源目录、传了哪些文件、走完没有,都要留下日志。航空航天系统的质量流程里,资料上传本身就是有追溯要求的。
1.3 先约定好这几个边界指标
我建议任何项目在编码前,先和业务方把边界条件定清楚,而不是等开发到一半才谈。我在这个项目里实际的约定是这样的:
| 边界项 | 我的约定 | 理由 |
|---|---|---|
| 单次任务总大小 | 单目录不超过 100GB | 再大就建议分批次,避免一个任务拖太久 |
| 单文件大小 | 不超过 8GB | 再大应走离线介质导入,而不是在线传 |
| 分片大小 | 4MB | 对 IIS 和 ASP.NET 的请求体相对友好 |
| 单任务文件数上限 | 5000 个 | 防止一个文件夹里有几十万个零碎小文件 |
| 上传会话有效期 | 48 小时 | 超过时限自动清理临时目录 |
| 校验级别 | 完整 SHA256 校验 | 每分片校验一次,合成后再校验一次 |
这些数字并不绝对,但必须让前端、后端、部署和安全人员心里都有数。没有这些约束,“精确控制”只是一个口号。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 选型:为什么普通文件上传在 ASP.NET 里撑不住
2.1 浏览器如何“看到”文件夹
现代浏览器其实早就支持用户选择整个文件夹,而不是只能选文件。最简单的实现是给 <input> 加上 webkitdirectory 和 directory 属性:
html复制<input type="file" id="folderPicker" webkitdirectory directory multiple />
用户点开这个控件时,弹出的就是文件夹选择对话框,选完之后,input.files 里返回的是文件夹内所有文件的列表,而且每个 File 对象上都带有 webkitRelativePath 属性,例如 batch_2024/TBO/images/engine_1/photo_01.jpg。这个属性就是我们要保留的相对路径。
拖拽上传也可以实现。把文件夹拖到页面上的时候,使用 DataTransferItem.webkitGetAsEntry() 拿到目录项,再递归遍历目录树。这个递归逻辑不复杂,但要注意入口文件和目录两种情况,它的价值在于能实现“拖一个文件夹上来,界面实时显示目录树和文件数量”这类体验。
无论哪种方式,浏览器都是处于沙箱里的,它只能读取用户主动选择的目录,不可能遍历服务器目录,也不可能偷偷上传任何东西。这一点上航空信息安全的要求没有冲突——所有文件先出现在用户面前,需要确认后才会进入上传队列。
2.2 经典 HttpPostedFileUpload 的局限性
很多老系统里,ASP.NET 上传文件是这么写的:Request.Files 里拿到 HttpPostedFileBase,然后调用 SaveAs()。这种方式处理单文件、小文件很顺手,但放到“文件夹整体上传”的背景下,有几个硬伤很明显:
- 没有“目录结
