| 特性 | WebForms (FileUpload) | MVC (IFormFile) |
|---|---|---|
| 控件/模型 | FileUpload 控件 | `IFormFile` 介面 / ViewModel 屬性 |
| 表單編碼 | 自動 `enctype="multipart/form-data"` | 手動指定 ` |
| 文件訪問 | `FileUpload.PostedFile` | `Request.Form.Files` / 參數綁定 |
| 服務器映射 | `Server.MapPath` | `IWebHostEnvironment.WebRootPath` / `ContentRootPath` |
@model FileUploadViewModel
public class FileUploadViewModel
{
[Required(ErrorMessage = "請選擇文件")]
[DataType(DataType.Upload)]
[MaxFileSize(10 * 1024 * 1024)] // 自定義驗證特性 10MB
[AllowedExtensions(new[] { ".jpg", ".png", ".pdf", ".docx" })]
public IFormFile File { get; set; }
[StringLength(500)]
public string Description { get; set; }
}
public class MaxFileSizeAttribute : ValidationAttribute
{
private readonly long _maxBytes;
public MaxFileSizeAttribute(long maxBytes) => _maxBytes = maxBytes;
protected override ValidationResult IsValid(object value, ValidationContext context)
{
if (value is IFormFile file && file.Length > _maxBytes)
return new ValidationResult($"文件大小不能超過 {_maxBytes / 1024 / 1024}MB");
return ValidationResult.Success;
}
}
[HttpPost] [ValidateAntiForgeryToken] public async TaskUpload(FileUploadViewModel model) { if (!ModelState.IsValid) return View(model); // 1. 生成安全文件名 string originalName = Path.GetFileName(model.File.FileName); string safeName = $"{Guid.NewGuid()}{Path.GetExtension(originalName)}"; // 2. 確定存儲路徑 string uploadsFolder = Path.Combine(_env.WebRootPath, "uploads", "files"); Directory.CreateDirectory(uploadsFolder); string filePath = Path.Combine(uploadsFolder, safeName); // 3. 保存文件 (異步流複製) using (var stream = new FileStream(filePath, FileMode.Create)) { await model.File.CopyToAsync(stream); } // 4. 可選:存入數據庫記錄 var fileRecord = new FileRecord { OriginalName = originalName, StoredName = safeName, ContentType = model.File.ContentType, Size = model.File.Length, Description = model.Description, UploadTime = DateTime.UtcNow, UploadedBy = User.Identity.Name }; _context.FileRecords.Add(fileRecord); await _context.SaveChangesAsync(); TempData["Success"] = "上傳成功"; return RedirectToAction("Index"); } // 多文件上傳 [HttpPost] public async Task UploadMultiple(List files) { var results = new List
public IActionResult Download(string fileName)
{
string filePath = Path.Combine(_env.WebRootPath, "uploads", "files", fileName);
if (!System.IO.File.Exists(filePath)) return NotFound();
// 方式 1:FileStreamResult (流式,大文件推薦)
var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read);
return File(stream, "application/octet-stream", fileName);
// 方式 2:PhysicalFileResult (高性能,TransmitFile)
// return PhysicalFile(filePath, "application/octet-stream", fileName);
// 方式 3:VirtualFileResult (虛擬路徑)
// return File($"/uploads/files/{fileName}", "application/octet-stream", fileName);
}
public async TaskDownloadFromDb(int id) { var record = await _context.FileRecords.FindAsync(id); if (record == null) return NotFound(); string filePath = Path.Combine(_env.WebRootPath, "uploads", "files", record.StoredName); if (!System.IO.File.Exists(filePath)) return NotFound(); // 返回原始文件名 return PhysicalFile(filePath, record.ContentType, record.OriginalName); }
public IActionResult ExportReport()
{
byte[] excelBytes = GenerateExcelReport(); // 內存生成
string fileName = $"Report_{DateTime.Now:yyyyMMdd}.xlsx";
return File(excelBytes, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", fileName);
}
[HttpPost("upload-chunk")]
public async Task UploadChunk(IFormFile chunk, string fileId, int chunkIndex, int totalChunks)
{
string tempDir = Path.Combine(_env.ContentRootPath, "temp_uploads", fileId);
Directory.CreateDirectory(tempDir);
string chunkPath = Path.Combine(tempDir, $"{chunkIndex}.part");
using (var stream = new FileStream(chunkPath, FileMode.Create))
await chunk.CopyToAsync(stream);
// 最後一片:合併文件
if (chunkIndex == totalChunks - 1)
{
string finalPath = Path.Combine(_env.WebRootPath, "uploads", fileId);
using (var finalStream = new FileStream(finalPath, FileMode.Create))
{
for (int i = 0; i < totalChunks; i++)
{
string partPath = Path.Combine(tempDir, $"{i}.part");
using (var partStream = System.IO.File.OpenRead(partPath))
await partStream.CopyToAsync(finalStream);
}
}
Directory.Delete(tempDir, true);
return Ok(new { merged = true, file = fileId });
}
return Ok(new { received = true });
}
public async TaskProcessImageAsync(IFormFile file) { using (var image = Image.Load(file.OpenReadStream())) // ImageSharp { // 1. 生成縮略圖 image.Mutate(x => x.Resize(new ResizeOptions { Mode = ResizeMode.Max, Size = new Size(800, 600) })); // 2. 添加水印 // image.Mutate(x => x.DrawText(...)); // 3. 保存為 WebP (現代格式) string webpName = $"{Guid.NewGuid()}.webp"; string path = Path.Combine(_env.WebRootPath, "uploads", "images", webpName); await image.SaveAsWebpAsync(path); return webpName; } }
CREATE TABLE FileStore (
Id INT IDENTITY PRIMARY KEY,
FileContent VARBINARY(MAX) NOT NULL, -- 大文件建議 FILESTREAM
MimeType NVARCHAR(100) NOT NULL,
FileName NVARCHAR(255) NOT NULL,
UploadTime DATETIME2 DEFAULT SYSUTCDATETIME()
);
// 存入
var record = new FileStore
{
FileContent = await GetFileBytesAsync(file),
MimeType = file.ContentType,
FileName = file.FileName
};
_context.FileStores.Add(record);
await _context.SaveChangesAsync();
// 讀取
var record = await _context.FileStores.FindAsync(id);
return File(record.FileContent, record.MimeType, record.FileName);
MVC 文件上傳下載核心在於 `IFormFile` 流式處理、`enctype="multipart/form-data"` 表單編碼、`FileResult` 多態返回,配合驗證、安全、異步最佳實踐,構建健壯的文件管理功能。