C#实现从页面获取文件夹层级并保存至数据库的方案
解决方案:获取完整文件夹层级或使用压缩包替代
一、直接获取完整文件夹层级的方法
浏览器原生的<input type="file" directory multiple>存在局限性——只会返回包含文件的目录,空目录不会被单独提交,且IFormFile无法直接获取目录结构信息。要解决这个问题,需要借助前端JavaScript读取目录结构,再把层级数据和文件一起提交:
前端处理逻辑
- 监听文件输入的
change事件,通过webkitEntries(Chrome、Edge等现代浏览器支持)获取所有选中的目录和文件条目 - 递归遍历目录条目,收集完整的目录层级(包括空文件夹)和文件信息
- 将目录层级数据转为JSON字符串,和文件一起通过
FormData提交到后端
示例前端代码:
document.querySelector('input[type="file"]').addEventListener('change', async (e) => { const formData = new FormData(); const dirStructure = []; // 递归遍历目录 async function traverseEntry(entry, parentPath = '') { const currentPath = parentPath ? `${parentPath}/${entry.name}` : entry.name; if (entry.isDirectory) { // 记录目录信息 dirStructure.push({ path: currentPath, type: 'directory' }); const reader = entry.createReader(); const entries = await new Promise((resolve) => reader.readEntries(resolve)); await Promise.all(entries.map(child => traverseEntry(child, currentPath))); } else if (entry.isFile) { // 记录文件信息并添加到FormData const file = await new Promise((resolve) => entry.file(resolve)); formData.append('files', file, currentPath); dirStructure.push({ path: currentPath, type: 'file' }); } } // 处理所有选中的条目 for (const entry of e.target.webkitEntries) { await traverseEntry(entry); } // 将目录结构JSON添加到FormData formData.append('directoryStructure', JSON.stringify(dirStructure)); // 提交到后端 await fetch('/api/upload', { method: 'POST', body: formData }); });- 监听文件输入的
后端处理逻辑
- 控制器中接收
directoryStructure参数(JSON字符串)和文件列表 - 解析JSON得到完整的目录层级,包含空文件夹
- 将层级数据保存到数据库,同时处理上传的文件
示例后端代码(ASP.NET Core):
[HttpPost("api/upload")] public async Task<IActionResult> Upload([FromForm] IFormCollection form) { // 获取目录结构JSON并解析 var dirStructureJson = form["directoryStructure"].FirstOrDefault(); if (string.IsNullOrEmpty(dirStructureJson)) { return BadRequest("未提供目录结构数据"); } var directoryStructure = JsonSerializer.Deserialize<List<DirectoryEntry>>(dirStructureJson); // 保存目录层级到数据库 foreach (var entry in directoryStructure.Where(e => e.Type == "directory")) { // 这里写保存到数据库的逻辑,比如创建Directory实体 // dbContext.Directories.Add(new Directory { Path = entry.Path }); } await dbContext.SaveChangesAsync(); // 处理上传的文件(可选) var files = form.Files; foreach (var file in files) { // 处理文件保存逻辑 } return Ok(); } // 辅助类 public class DirectoryEntry { public string Path { get; set; } public string Type { get; set; } }注意:这种方法依赖浏览器的
webkitEntriesAPI,兼容性上需要确认目标用户使用的浏览器是否支持(现代Chrome、Edge、Safari都支持,Firefox需要开启相关配置)。- 控制器中接收
二、要求用户上传压缩包的替代方案
如果前端兼容性要求较高,或者不想处理复杂的前端目录遍历逻辑,让用户上传压缩包(ZIP、RAR等)是更稳妥的方案:
前端提示:在页面上明确告知用户需要将目标文件夹压缩为ZIP包后上传,使用普通的
<input type="file" accept=".zip">即可。后端处理逻辑:
- 接收上传的压缩包文件
- 使用开源库(如ASP.NET Core中的
SharpZipLib或System.IO.Compression)解压压缩包 - 遍历解压后的目录结构,提取所有文件夹层级(包括空文件夹)
- 将层级数据保存到数据库,同时可选择保留或删除解压后的文件
示例后端解压代码(使用
System.IO.Compression):[HttpPost("api/upload-zip")] public async Task<IActionResult> UploadZip(IFormFile zipFile) { if (zipFile == null || zipFile.Length == 0) { return BadRequest("请选择有效的压缩包文件"); } var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); Directory.CreateDirectory(tempDir); // 解压ZIP包 using (var stream = new MemoryStream()) { await zipFile.CopyToAsync(stream); stream.Position = 0; using (var archive = new ZipArchive(stream, ZipArchiveMode.Read)) { archive.ExtractToDirectory(tempDir); } } // 遍历目录结构,收集层级 var dirPaths = new List<string>(); CollectDirectories(tempDir, dirPaths, Path.GetFileNameWithoutExtension(zipFile.FileName)); // 保存到数据库 foreach (var path in dirPaths) { // dbContext.Directories.Add(new Directory { Path = path }); } await dbContext.SaveChangesAsync(); // 清理临时目录(可选) Directory.Delete(tempDir, recursive: true); return Ok(); } // 递归收集所有目录路径 private void CollectDirectories(string rootDir, List<string> dirPaths, string basePath) { // 添加当前目录 var relativePath = rootDir.Replace(Path.GetDirectoryName(rootDir), basePath).Replace("\\", "/"); dirPaths.Add(relativePath); // 遍历子目录 foreach (var subDir in Directory.GetDirectories(rootDir)) { CollectDirectories(subDir, dirPaths, basePath); } }
这种方案兼容性好,不需要依赖浏览器特定API,缺点是用户需要多一步压缩操作,需要在页面上做好引导说明。
内容的提问来源于stack exchange,提问作者Bubbler
相关产品推荐
相关产品推荐

