首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> ASP.NET 基礎 >> ASP.NET 基礎

ASP.NET Core MVC 文件上傳下載完整實戰教程:從表單到數據庫存儲

     編輯:ASP.NET 基礎

核心差異:WebForms vs MVC

特性WebForms (FileUpload)MVC (IFormFile)
控件/模型FileUpload 控件`IFormFile` 介面 / ViewModel 屬性
表單編碼自動 `enctype="multipart/form-data"`手動指定 `
`
文件訪問`FileUpload.PostedFile``Request.Form.Files` / 參數綁定
服務器映射`Server.MapPath``IWebHostEnvironment.WebRootPath` / `ContentRootPath`

一、文件上傳完整實現

1. View 頁面 (Razor)

@model FileUploadViewModel


    

2. ViewModel 與驗證

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

3. Controller 處理邏輯

[HttpPost]
[ValidateAntiForgeryToken]
public async Task Upload(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();
    foreach (var file in files)
    {
        if (file.Length > 0)
        {
            string safeName = $"{Guid.NewGuid()}{Path.GetExtension(file.FileName)}";
            string path = Path.Combine(_env.WebRootPath, "uploads", safeName);
            using (var stream = new FileStream(path, FileMode.Create))
                await file.CopyToAsync(stream);
            
            results.Add(new { original = file.FileName, stored = safeName, size = file.Length });
        }
    }
    return Json(new { success = true, files = results });
}

二、文件下載實現

1. 物理文件下載 (FileResult)

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

2. 數據庫存儲文件下載

public async Task DownloadFromDb(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);
}

3. 內存生成文件下載 (如 Excel/PDF)

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 Task ProcessImageAsync(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;
    }
}

安全防護清單

  1. [ ] 驗證文件擴展名 + MIME 類型雙重檢查
  2. [ ] 文件名重命名 (GUID/Hash),防止路徑遍歷
  3. [ ] 限制上傳目錄執行權限 (IIS 移除腳本映射)
  4. [ ] 病毒掃描集成 (ClamAV / 云端 API)
  5. [ ] 文件大小限制 (客戶端 + 服務端雙重)
  6. [ ] 存儲路徑隔離:非 WebRoot 目錄,通過 Controller 讀取

四、數據庫存儲二進制數據 (不推薦大文件)

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` 多態返回,配合驗證、安全、異步最佳實踐,構建健壯的文件管理功能。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved