资讯动态

ASP.NET MVC文件上传下载实战指南

发布时间:2026/10/14 15:16:16 来源:尧图企业网站定制
1. 文件上传下载功能在现代Web开发中的重要性在当今企业级应用开发中文件上传下载是最基础却最常被忽视的核心功能之一。我见过太多项目初期简单实现这个功能后期却因为性能、安全等问题不得不重构的案例。ASP.NET MVC框架提供了强大的文件处理能力但真正用好这些功能需要理解其底层机制。上周我刚为一个电商平台解决了大文件上传超时的问题发现很多开发者对HttpPostedFileBase的理解还停留在表面。本文将结合我8年ASP.NET开发经验从原理到实践带你掌握文件处理的正确姿势。2. 环境准备与基础配置2.1 创建MVC项目的基本结构使用Visual Studio新建ASP.NET MVC项目时建议选择空模板而非默认的MVC模板。这样可以避免引入不必要的依赖项。我通常这样组织文件处理相关的目录结构/Controllers /FileController.cs /Models /FileUploadModel.cs /Views /File /Upload.cshtml /App_Data /Uploads /Temp /Permanent重要提示永远不要将上传文件保存在项目目录或bin文件夹下这会导致应用程序池回收时文件丢失。App_Data是ASP.NET专门为数据文件设计的受保护目录。2.2 必要的NuGet包引用除了基本的System.Web.Mvc还需要添加Install-Package Microsoft.AspNet.Web.Optimization -Version 1.1.3 Install-Package System.IO.Abstractions -Version 19.2.29第二个包是我强烈推荐的——它允许你对文件系统操作进行抽象这在单元测试时非常有用。我们稍后会看到具体应用。3. 文件上传的完整实现3.1 前端表单的关键配置在View中创建上传表单时这些属性必须设置using (Html.BeginForm(Upload, File, FormMethod.Post, new { enctype multipart/form-data, id uploadForm, data_ajax false // 防止jQuery Ajax干扰文件上传 })) { input typefile namefiles multiple / Html.AntiForgeryToken() button typesubmit上传/button }特别注意当需要多文件上传时input的name属性必须带snamefiles这与后台模型绑定直接相关。3.2 控制器中的接收处理基础版的上传处理方法[HttpPost] [ValidateAntiForgeryToken] public ActionResult Upload(IEnumerableHttpPostedFileBase files) { foreach (var file in files) { if (file ! null file.ContentLength 0) { var fileName Path.GetFileName(file.FileName); var path Path.Combine(Server.MapPath(~/App_Data/Uploads), fileName); file.SaveAs(path); } } return RedirectToAction(Index); }但这段代码存在三个严重问题文件名冲突会导致覆盖没有文件类型检查大文件会阻塞线程3.3 生产环境级的上传方案改进后的安全上传方案[HttpPost] [ValidateAntiForgeryToken] public async TaskActionResult Upload(FileUploadViewModel model) { var allowedExtensions new[] { .pdf, .docx, .xlsx }; var maxFileSize 10 * 1024 * 1024; // 10MB foreach (var file in model.Files) { if (file?.ContentLength 0) { var extension Path.GetExtension(file.FileName)?.ToLower(); if (!allowedExtensions.Contains(extension)) { ModelState.AddModelError(, $不支持 {extension} 文件类型); continue; } if (file.ContentLength maxFileSize) { ModelState.AddModelError(, 文件大小超过10MB限制); continue; } var safeFileName ${Guid.NewGuid()}{extension}; var tempPath Path.Combine(Server.MapPath(~/App_Data/Uploads/Temp), safeFileName); using (var fileStream File.Create(tempPath)) { await file.InputStream.CopyToAsync(fileStream); } // 这里可以添加文件病毒扫描逻辑 } } return View(model); }关键改进点使用异步避免线程阻塞GUID重命名防止冲突严格的类型和大小检查使用Temp目录暂存待验证文件4. 文件下载的高级实现4.1 基础下载方法的问题最常见的错误下载实现public ActionResult Download(string fileName) { var path Path.Combine(Server.MapPath(~/App_Data/Uploads), fileName); return File(path, application/octet-stream, fileName); }这种实现存在安全隐患路径遍历攻击风险如fileName../../web.config未检查文件是否存在未处理并发访问冲突4.2 安全的文件下载实现改进后的下载方法public ActionResult Download(string fileId) { // 从数据库获取文件元数据 var fileMeta _fileService.GetFileById(fileId); if (fileMeta null) return HttpNotFound(); var safePath Path.GetFullPath(Path.Combine( Server.MapPath(~/App_Data/Uploads/Permanent), fileMeta.StoredName)); if (!safePath.StartsWith(Server.MapPath(~/App_Data))) { return new HttpStatusCodeResult(403); } if (!System.IO.File.Exists(safePath)) { return HttpNotFound(); } // 处理断点续传 var fileStream new FileStream(safePath, FileMode.Open, FileAccess.Read, FileShare.Read); return new FileStreamResult(fileStream, fileMeta.ContentType) { FileDownloadName fileMeta.OriginalName }; }4.3 大文件分块下载技术对于超过100MB的大文件应该实现分块下载public ActionResult DownloadLargeFile(string fileId) { // ... 省略安全验证代码 var response HttpContext.Response; response.Buffer false; response.BufferOutput false; response.ContentType application/octet-stream; response.AddHeader(Content-Disposition, $attachment; filename{HttpUtility.UrlEncode(fileMeta.OriginalName)}); response.AddHeader(Content-Length, fileMeta.Size.ToString()); using (var fileStream new FileStream(safePath, FileMode.Open, FileAccess.Read, FileShare.Read)) { var buffer new byte[1024 * 64]; // 64KB缓冲区 int bytesRead; while ((bytesRead fileStream.Read(buffer, 0, buffer.Length)) 0 response.IsClientConnected) { response.OutputStream.Write(buffer, 0, bytesRead); response.Flush(); } } return new EmptyResult(); }5. 生产环境中的关键问题处理5.1 文件上传大小限制默认情况下ASP.NET限制上传文件大小为4MB。要修改这个限制需要在web.config中添加system.web httpRuntime maxRequestLength102400 executionTimeout3600 / /system.web system.webServer security requestFiltering requestLimits maxAllowedContentLength104857600 / /requestFiltering /security /system.webServer注意maxRequestLength单位是KB而maxAllowedContentLength单位是字节。两者需要同时配置。5.2 并发上传冲突处理当多个用户同时上传同名文件时可以采用以下策略public string GenerateUniqueFilePath(string uploadPath, string fileName) { var safeFileName Path.GetFileName(fileName); var path Path.Combine(uploadPath, safeFileName); if (System.IO.File.Exists(path)) { var fileNameWithoutExtension Path.GetFileNameWithoutExtension(safeFileName); var extension Path.GetExtension(safeFileName); var counter 1; do { var newFileName ${fileNameWithoutExtension} ({counter}){extension}; path Path.Combine(uploadPath, newFileName); } while (System.IO.File.Exists(path)); } return path; }5.3 文件上传进度显示实现上传进度显示需要结合JavaScript$(document).ready(function() { $(#uploadForm).submit(function(e) { e.preventDefault(); var formData new FormData(this); var xhr new XMLHttpRequest(); xhr.upload.addEventListener(progress, function(evt) { if (evt.lengthComputable) { var percentComplete Math.round((evt.loaded / evt.total) * 100); $(#progressBar).css(width, percentComplete %); } }, false); xhr.open(POST, $(this).attr(action), true); xhr.send(formData); }); });6. 安全防护措施6.1 文件类型验证的陷阱不要仅依赖文件扩展名验证攻击者可以伪造扩展名。应该检查文件头签名public bool IsValidFileType(Stream fileStream, string extension) { var header new byte[8]; fileStream.Read(header, 0, header.Length); fileStream.Position 0; switch (extension.ToLower()) { case .pdf: return header.Take(4).SequenceEqual(new byte[] { 0x25, 0x50, 0x44, 0x46 }); case .docx: return header.Take(4).SequenceEqual(new byte[] { 0x50, 0x4B, 0x03, 0x04 }); default: return false; } }6.2 防病毒扫描集成建议集成ClamAV等开源杀毒引擎public bool ScanForViruses(string filePath) { var clam new ClamClient(localhost, 3310); var scanResult clam.ScanFileOnServerAsync(filePath).Result; return scanResult.Result ClamScanResults.Clean; }6.3 日志记录与审计记录所有文件操作public class FileService : IFileService { private readonly ILogger _logger; public FileService(ILogger logger) { _logger logger; } public void SaveFile(HttpPostedFileBase file) { var fileInfo new { Name file.FileName, Size file.ContentLength, User HttpContext.Current.User.Identity.Name, Time DateTime.UtcNow }; _logger.LogInformation(File uploaded: {FileInfo}, fileInfo); // 实际保存逻辑... } }7. 性能优化技巧7.1 内存流与缓冲优化对于中等大小文件(1-50MB)使用内存流可以提高性能public ActionResult DownloadOptimized(string fileId) { // ...省略验证代码 var memoryStream new MemoryStream(); using (var fileStream new FileStream(safePath, FileMode.Open)) { fileStream.CopyTo(memoryStream); } memoryStream.Position 0; return File(memoryStream, fileMeta.ContentType, fileMeta.OriginalName); }7.2 使用CDN加速下载对于公开可下载的大文件建议使用CDNpublic ActionResult DownloadWithCDN(string fileId) { var fileMeta _fileService.GetFileById(fileId); if (fileMeta null) return HttpNotFound(); if (fileMeta.Size 1024 * 1024 * 50) // 大于50MB { var cdnUrl $https://cdn.yourdomain.com/files/{fileMeta.StoredName}; return Redirect(cdnUrl); } // 小文件直接本地处理 return Download(fileId); }7.3 异步IO的最佳实践正确使用async/await处理文件IOpublic async TaskActionResult UploadAsync(FileUploadViewModel model) { foreach (var file in model.Files) { if (file?.ContentLength 0) { var tempPath GetTempFilePath(file.FileName); using (var fileStream System.IO.File.Create(tempPath)) { await file.InputStream.CopyToAsync(fileStream) .ConfigureAwait(false); // 重要避免死锁 } // 其他处理... } } return View(model); }8. 单元测试策略8.1 使用System.IO.Abstractions之前引入的System.IO.Abstractions在这里发挥作用[TestClass] public class FileServiceTests { private IFileSystem _fileSystem; private FileService _fileService; [TestInitialize] public void Setup() { _fileSystem new MockFileSystem(); _fileService new FileService(_fileSystem); } [TestMethod] public void SaveFile_ShouldCreateFile() { // 准备 var mockFile new MockHttpPostedFileBase(); mockFile.Setup(f f.FileName).Returns(test.txt); mockFile.Setup(f f.ContentLength).Returns(10); // 执行 _fileService.SaveFile(mockFile.Object); // 断言 Assert.IsTrue(_fileSystem.File.Exists(/App_Data/Uploads/test.txt)); } }8.2 控制器测试技巧测试文件上传控制器[TestClass] public class FileControllerTests { [TestMethod] public async Task Upload_ValidFile_ReturnsRedirect() { // 准备 var controller new FileController(); var mockFile new MockHttpPostedFileBase(); mockFile.Setup(f f.FileName).Returns(test.pdf); mockFile.Setup(f f.ContentLength).Returns(1024); // 执行 var result await controller.Upload(new FileUploadViewModel { Files new[] { mockFile.Object } }) as RedirectToRouteResult; // 断言 Assert.AreEqual(Index, result.RouteValues[action]); } }9. 实际项目中的经验总结9.1 文件存储策略选择根据项目需求选择合适存储方案存储方式适用场景优点缺点本地磁盘小型应用内部系统实现简单零成本扩展性差无冗余数据库小文件(1MB)需要事务备份方便强一致性性能差成本高Azure Blob/AWS S3中大型项目高可用易扩展需要网络调用分布式文件系统企业级应用高性能高可靠架构复杂9.2 上传失败处理经验常见的上传失败场景及对策网络中断实现断点续传前端自动重试机制文件过大提前验证文件大小分块上传技术服务器磁盘满监控磁盘空间自动清理临时文件病毒文件集成杀毒扫描隔离可疑文件9.3 性能监控指标建议监控这些关键指标public class FilePerformanceMetrics { // 上传平均耗时(ms) public double UploadTime { get; set; } // 下载速度(KB/s) public double DownloadSpeed { get; set; } // 并发上传数 public int ConcurrentUploads { get; set; } // 失败率 public double FailureRate { get; set; } }在Global.asax中定期记录这些指标protected void Application_EndRequest() { if (Request.Files.Count 0) { var stopwatch (Stopwatch)HttpContext.Items[UploadTimer]; stopwatch.Stop(); _metrics.UploadTime stopwatch.ElapsedMilliseconds; _metricsLogger.Log(_metrics); } }10. 高级应用场景10.1 大文件分块上传使用Plupload等库实现分块上传var uploader new plupload.Uploader({ browse_button: selectFiles, url: /File/ChunkUpload, chunk_size: 1mb, filters: { max_file_size: 10gb, mime_types: [{ title: Documents, extensions: pdf,docx }] } }); uploader.init();对应的控制器处理[HttpPost] public ActionResult ChunkUpload() { var chunkNumber int.Parse(Request[chunk]); var totalChunks int.Parse(Request[chunks]); var fileName Request[name]; var chunkData Request.Files[0]; var tempPath Path.Combine(Server.MapPath(~/App_Data/Uploads/Temp), fileName); // 追加模式写入分块 using (var fs new FileStream(tempPath, chunkNumber 0 ? FileMode.Create : FileMode.Append)) { var buffer new byte[chunkData.InputStream.Length]; chunkData.InputStream.Read(buffer, 0, buffer.Length); fs.Write(buffer, 0, buffer.Length); } if (chunkNumber totalChunks - 1) { // 所有分块接收完成执行最终处理 _fileService.ProcessCompleteFile(tempPath, fileName); } return Json(new { success true }); }10.2 文件预览生成使用第三方库生成文件预览public ActionResult GeneratePreview(string fileId) { var fileMeta _fileService.GetFileById(fileId); var filePath GetFilePath(fileMeta.StoredName); if (fileMeta.ContentType.StartsWith(image/)) { using (var image Image.FromFile(filePath)) { var thumbnail image.GetThumbnailImage(200, 200, null, IntPtr.Zero); var ms new MemoryStream(); thumbnail.Save(ms, ImageFormat.Jpeg); return File(ms.ToArray(), image/jpeg); } } else if (fileMeta.ContentType application/pdf) { // 使用Ghostscript或其他PDF处理库生成预览 var previewPath _pdfService.GeneratePreview(filePath); return File(previewPath, image/jpeg); } return File(~/Content/default-preview.png, image/png); }10.3 文件加密存储使用AES加密敏感文件public void SaveEncryptedFile(HttpPostedFileBase file, string password) { using (var aes Aes.Create()) { aes.Key DeriveKey(password, aes.IV); var encryptor aes.CreateEncryptor(); var outputPath GetSafeFilePath(file.FileName .enc); using (var outputFs new FileStream(outputPath, FileMode.Create)) using (var cryptoStream new CryptoStream(outputFs, encryptor, CryptoStreamMode.Write)) { file.InputStream.CopyTo(cryptoStream); } // 保存IV和文件元数据到数据库 _fileRepo.Save(new FileMeta { FileName file.FileName, StoredName Path.GetFileName(outputPath), IV Convert.ToBase64String(aes.IV) }); } } private byte[] DeriveKey(string password, byte[] salt) { using (var deriveBytes new Rfc2898DeriveBytes(password, salt, 10000)) { return deriveBytes.GetBytes(32); // 256-bit key } }11. 移动端适配方案11.1 响应式上传界面使用Bootstrap实现移动友好的上传界面div classcontainer mt-5 div classrow justify-content-center div classcol-md-8 col-sm-12 div classcard div classcard-body h5 classcard-title文件上传/h5 using (Html.BeginForm(Upload, File, FormMethod.Post, new { enctype multipart/form-data, class dropzone, id uploadDropzone })) { div classfallback input namefile typefile classform-control-file / /div div classdz-message>if (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) { $(#fileInput).attr(capture, camera); // 允许直接拍照上传 $(#fileInput).attr(accept, image/*); // 限制为图片类型 }11.3 离线上传支持使用PWA技术实现离线上传// 注册Service Worker if (serviceWorker in navigator) { navigator.serviceWorker.register(/sw.js).then(function(registration) { console.log(ServiceWorker注册成功); }); } // 监听离线事件 window.addEventListener(offline, function() { // 将文件保存到IndexedDB saveFilesToQueue(); }); // 网络恢复后自动重试上传 window.addEventListener(online, function() { retryUploadQueue(); });12. 微服务架构下的文件处理12.1 文件微服务设计建议的文件微服务API设计[Route(api/files)] public class FileApiController : ApiController { [HttpPost] [Route(upload)] public async TaskIHttpActionResult UploadFile() { if (!Request.Content.IsMimeMultipartContent()) { return BadRequest(只支持multipart/form-data); } var provider new MultipartMemoryStreamProvider(); await Request.Content.ReadAsMultipartAsync(provider); foreach (var file in provider.Contents) { var fileName file.Headers.ContentDisposition.FileName.Trim(\); var fileBytes await file.ReadAsByteArrayAsync(); // 存储到分布式文件系统 var fileId await _distributedFileStorage.SaveAsync(fileName, fileBytes); return Ok(new { fileId }); } return BadRequest(未接收到有效文件); } [HttpGet] [Route(download/{fileId})] public async TaskHttpResponseMessage DownloadFile(string fileId) { var fileInfo await _distributedFileStorage.GetInfoAsync(fileId); if (fileInfo null) return Request.CreateResponse(HttpStatusCode.NotFound); var fileStream await _distributedFileStorage.GetStreamAsync(fileId); var response new HttpResponseMessage(HttpStatusCode.OK) { Content new StreamContent(fileStream) }; response.Content.Headers.ContentDisposition new ContentDispositionHeaderValue(attachment) { FileName fileInfo.OriginalName }; response.Content.Headers.ContentType new MediaTypeHeaderValue(fileInfo.ContentType); return response; } }12.2 使用消息队列处理大文件通过RabbitMQ异步处理文件public class FileUploadController : ApiController { [HttpPost] public IHttpActionResult Upload() { var file HttpContext.Current.Request.Files[0]; var tempPath SaveTempFile(file); // 发送处理消息 var message new FileProcessMessage { TempPath tempPath, UserId User.Identity.GetUserId() }; _messageQueue.Publish(file_processing, message); return Ok(new { status processing }); } } // 后台Worker处理 public class FileProcessingWorker : IConsumerFileProcessMessage { public void Consume(FileProcessMessage message) { try { // 实际处理逻辑 var result _fileProcessor.Process(message.TempPath); // 更新数据库状态 _fileRepo.UpdateStatus(message.FileId, processed); // 发送通知 _notificationService.Send(message.UserId, 文件处理完成); } catch (Exception ex) { _logger.Error(ex, 文件处理失败); _fileRepo.UpdateStatus(message.FileId, failed); } } }12.3 分布式文件存储集成与MinIO集成示例public class MinioFileStorage : IFileStorage { private readonly MinioClient _minio; public MinioFileStorage() { _minio new MinioClient() .WithEndpoint(minio.example.com:9000) .WithCredentials(accessKey, secretKey) .Build(); } public async Taskstring SaveAsync(string bucketName, string objectName, Stream stream) { // 确保存储桶存在 var bucketExists await _minio.BucketExistsAsync(bucketName); if (!bucketExists) { await _minio.MakeBucketAsync(bucketName); } // 上传文件 await _minio.PutObjectAsync(bucketName, objectName, stream, stream.Length); return ${bucketName}/{objectName}; } public async TaskStream GetAsync(string bucketName, string objectName) { var memoryStream new MemoryStream(); await _minio.GetObjectAsync(bucketName, objectName, stream { stream.CopyTo(memoryStream); memoryStream.Position 0; }); return memoryStream; } }13. 实际项目案例解析13.1 电商平台商品图片处理系统我最近为某电商平台设计的图片处理方案上传流程前端使用WebP格式压缩图片分块上传到临时存储后台Worker生成多种尺寸缩略图最终存储到S3兼容存储技术栈ASP.NET Core WebAPIImageSharp处理图片MinIO作为S3兼容存储Redis缓存处理队列性能指标平均上传时间1.2s (2MB图片)支持并发上传500自动生成5种尺寸缩略图13.2 企业文档管理系统为金融客户设计的文档安全管理系统安全措施上传时实时病毒扫描自动OCR识别敏感内容基于角色的访问控制所有下载操作水印日志特殊需求处理合同文档自动分类与电子签名系统集成保留所有版本历史技术难点处理1000页的PDF文档保持高分辨率扫描件可读性实现快速全文检索13.3 医疗影像存储系统医院PACS系统的替代方案核心需求DICOM格式支持匿名化处理快速调阅历史影像多级存储策略解决方案专用DICOM处理器热数据SSD存储冷数据自动归档到磁带库基于患者ID的快速检索性能优化预加载相邻切片渐进式传输本地缓存策略14. 未来技术演进方向14.1 WebAssembly在文件处理中的应用使用Wasm加速前端文件处理// 加载Wasm模块 const module await WebAssembly.instantiateStreaming( fetch(file_processor.wasm), imports ); // 调用Wasm处理文件 const file document.getElementById(fileInput).files[0]; const arrayBuffer await file.arrayBuffer(); const bytes new Uint8Array(arrayBuffer); // 分配内存并传递数据 const ptr module._malloc(bytes.length); module.HEAPU8.set(bytes, ptr); // 调用Wasm处理函数 const resultPtr module._process_file(ptr, bytes.length); const resultSize module._get_result_size(); const result new Uint8Array( module.HEAPU8.buffer, resultPtr, resultSize ); // 释放内存 module._free(ptr);14.2 基于AI的智能文件处理集成机器学习模型自动分类文件public async Taskstring ClassifyFile(Stream fileStream) { // 加载预训练模型 var model await TensorFlowModel.LoadAsync(file_classifier.pb); // 提取文件特征 var features ExtractFileFeatures(fileStream); // 运行预测 var input new Listfloat[]{ features }; var output model.Predict(input); // 返回分类结果 var labels new[] { 合同, 发票, 报告, 其他 }; return labels[output.PredictedLabel]; }14.3 区块链文件存证将文件哈希上链实现不可篡改public async Taskstring StoreFileHash(byte[] fileBytes) { // 计算文件哈希 using var sha256 SHA256.Create(); var hash sha256.ComputeHash(fileBytes); var hexHash BitConverter.ToString(hash).Replace(-, ); // 调用智能合约 var transaction new Transaction { To 0xContractAddress, Data hexHash, GasLimit 100000 }; var receipt await _web3.Eth.Transactions.SendTransaction.SendRequestAsync(transaction); return receipt.TransactionHash; }15. 完整项目示例15.1 文件上传下载模块完整实现FileController.cs 完整代码[Authorize] public class FileController : Controller { private readonly IFileService _fileService; private readonly ILoggerFileController _logger; public FileController(IFileService fileService, ILoggerFileController logger) { _fileService fileService; _logger logger; } [HttpGet] public ActionResult Upload() { return View(); } [HttpPost] [ValidateAntiForgeryToken] public async TaskActionResult Upload(FileUploadViewModel model) { try { if (ModelState.IsValid) { var stopwatch Stopwatch.StartNew(); var result await _fileService.ProcessUploadAsync( model.Files, User.Identity.Name, HttpContext.Request.UserHostAddress); stopwatch.Stop(); _logger.LogInformation(文件上传成功 - 耗时 {Elapsed}ms, stopwatch.ElapsedMilliseconds); return RedirectToAction(UploadComplete, new { count result.SuccessCount }); } } catch (FileSizeExceededException ex) { ModelState.AddModelError(, ex.Message); _logger.LogWarning(ex, 文件大小超过限制); } catch (InvalidFileTypeException ex) { ModelState.AddModelError(, ex.Message); _logger.LogWarning(ex, 无效文件类型); } catch (Exception ex) { ModelState.AddModelError(, 上传过程中发生错误); _logger.LogError(ex, 文件上传失败); } return View(model); } [HttpGet] public async TaskActionResult Download(string id) { try { var fileResult await _fileService.GetFileForDownloadAsync(id); Response.Headers.Add(X-Content-Type-Options, nosniff); Response.Headers.Add(X-Frame-Options, DENY); return File(fileResult.Stream, fileResult.ContentType, fileResult.FileName); } catch (FileNotFoundException) { return HttpNotFound(); } catch (UnauthorizedAccessException) { return new HttpStatusCodeResult(HttpStatusCode.Forbidden); } } [HttpGet] public ActionResult UploadComplete(int count) { ViewBag.UploadCount count; return View(); } }15.2 文件服务层实现FileService.cs 核心逻辑public class FileService : IFileService { private readonly IFileStorage _storage; private readonly IFileValidator _validator; private readonly IFileRepository _repository; private readonly IAntivirusScanner _scanner; public FileService( IFileStorage storage, IFileValidator validator, IFileRepository repository, IAntivirusScanner scanner) { _storage storage; _validator validator; _repository repository; _scanner scanner; } public async TaskUploadResult ProcessUploadAsync( IEnumerableHttpPostedFileBase files, string uploadedBy, string ipAddress) { var result new UploadResult(); foreach (var file in files) { if (file?.ContentLength 0) { try { // 验证文件 await _validator.ValidateAsync(file); // 病毒扫描 var isClean await _scanner.ScanAsync(file.InputStream); if (!isClean) { throw new VirusDetectedException(发现病毒文件); } // 生成存储文件名 var storedName GenerateStoredFileName(file.FileName); // 保存到存储系统 var fileSize await _storage.SaveAsync(storedName, file.InputStream); // 保存元数据 var fileRecord new FileRecord { OriginalName file.FileName, StoredName storedName, ContentType file.ContentType, Size fileSize, UploadedBy uploadedBy, IpAddress ipAddress, UploadDate DateTime.UtcNow }; await _repository.AddAsync(fileRecord); result.SuccessCount; } catch (Exception ex) when ( ex is FileSizeExceededException || ex is InvalidFileTypeException) { result.FailedFiles.Add(new FailedFile { FileName file?.FileName, Error ex.Message }); } } } return result; } public async TaskFileDownloadResult GetFileForDownloadAsync(string fileId) { // 获取文件元数据 var fileRecord await _repository.GetAsync(fileId); if (fileRecord null

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑