- 传统 WebForms / MVC 方法:使用
<input type="file">和服务器端代码。 - 现代 Blazor / API 方法:使用 JavaScript (或 Blazor 的 JS 互操作) 和 Web API 来处理异步上传。
传统 WebForms / MVC 方法
这是最经典、最直接的方法,适用于传统的 ASP.NET Web Forms 和 ASP.NET MVC 项目。

前端视图 (View)
在视图中,你需要一个 enctype="multipart/form-data" 的表单,以及一个 type="file" 的输入控件。
Razor 语法 (MVC / Razor Pages):
@* 确保表单的 enctype 设置为 multipart/form-data *@
<form method="post" enctype="multipart/form-data" asp-action="UploadFile">
<div class="form-group">
<label for="fileInput">选择文件:</label>
<input type="file" id="fileInput" name="file" class="form-control" />
</div>
<button type="submit" class="btn btn-primary">上传</button>
</form>
WebForms 语法 (.aspx):
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Upload.aspx.cs" Inherits="MyWebApp.Upload" %>
<!DOCTYPE html>
<html>
<head>文件上传</title>
</head>
<body>
<form id="form1" runat="server" enctype="multipart/form-data">
<div>
<asp:FileUpload ID="FileUpload1" runat="server" />
<br />
<asp:Button ID="UploadButton" runat="server" Text="上传" OnClick="UploadButton_Click" />
</div>
</form>
</body>
</html>
后端控制器 (Controller)
这是处理文件上传的核心逻辑。

ASP.NET MVC 控制器:
using System;
using System.IO;
using System.Web;
using System.Web.Mvc; // 引入命名空间
public class HomeController : Controller
{
[HttpPost] // 必须是 POST 请求
public ActionResult UploadFile(HttpPostedFileBase file) // 参数名必须与 <input name="..."> 匹配
{
// 1. 检查是否有文件被选择
if (file == null || file.ContentLength == 0)
{
ViewBag.Message = "请选择一个文件。";
return View();
}
// 2. 检查文件大小 ( 限制为 10MB)
int maxFileSize = 10 * 1024 * 1024; // 10MB
if (file.ContentLength > maxFileSize)
{
ViewBag.Message = "文件大小不能超过 10MB。";
return View();
}
// 3. 检查文件类型 ( 只允许 .jpg, .png, .gif)
string[] allowedExtensions = { ".jpg", ".jpeg", ".png", ".gif" };
string fileExtension = Path.GetExtension(file.FileName).ToLower();
if (!Array.Exists(allowedExtensions, ext => ext == fileExtension))
{
ViewBag.Message = "只允许上传 JPG, PNG, GIF 格式的图片。";
return View();
}
try
{
// 4. 定义服务器上保存文件的路径
// Server.MapPath 将虚拟路径转换为服务器物理路径
// "~/Uploads/" 表示网站根目录下的 Uploads 文件夹
string path = Server.MapPath("~/Uploads/");
// 如果文件夹不存在,则创建
if (!Directory.Exists(path))
{
Directory.CreateDirectory(path);
}
// 5. 生成唯一的文件名,防止文件名冲突
string uniqueFileName = Guid.NewGuid().ToString() + "_" + file.FileName;
string fullPath = Path.Combine(path, uniqueFileName);
// 6. 保存文件到服务器
file.SaveAs(fullPath);
ViewBag.Message = "文件上传成功: " + fullPath;
}
catch (Exception ex)
{
ViewBag.Message = "文件上传失败: " + ex.Message;
}
return View();
}
// 显示上传表单的 Action
public ActionResult Index()
{
return View();
}
}
ASP.NET WebForms 后台代码 (.cs):
using System;
using System.IO;
using System.Web.UI;
namespace MyWebApp
{
public partial class Upload : Page
{
protected void UploadButton_Click(object sender, EventArgs e)
{
// 1. 检查是否有文件被选择
if (FileUpload1.HasFile)
{
try
{
// 2. 检查文件大小 ( 限制为 10MB)
int maxFileSize = 10 * 1024 * 1024; // 10MB
if (FileUpload1.FileBytes.Length > maxFileSize)
{
UploadStatusLabel.Text = "文件大小不能超过 10MB。";
UploadStatusLabel.ForeColor = System.Drawing.Color.Red;
return;
}
// 3. 检查文件类型
string[] allowedExtensions = { ".jpg", ".jpeg", ".png", ".gif" };
string fileExtension = Path.GetExtension(FileUpload1.FileName).ToLower();
if (!Array.Exists(allowedExtensions, ext => ext == fileExtension))
{
UploadStatusLabel.Text = "只允许上传 JPG, PNG, GIF 格式的图片。";
UploadStatusLabel.ForeColor = System.Drawing.Color.Red;
return;
}
// 4. 定义保存路径
string path = Server.MapPath("~/Uploads/");
if (!Directory.Exists(path))
{
Directory.CreateDirectory(path);
}
// 5. 生成唯一文件名并保存
string uniqueFileName = Guid.NewGuid().ToString() + "_" + FileUpload1.FileName;
string fullPath = Path.Combine(path, uniqueFileName);
FileUpload1.SaveAs(fullPath);
UploadStatusLabel.Text = "文件上传成功: " + fullPath;
UploadStatusLabel.ForeColor = System.Drawing.Color.Green;
}
catch (Exception ex)
{
UploadStatusLabel.Text = "文件上传失败: " + ex.Message;
UploadStatusLabel.ForeColor = System.Drawing.Color.Red;
}
}
else
{
UploadStatusLabel.Text = "请选择一个文件。";
UploadStatusLabel.ForeColor = System.Drawing.Color.Red;
}
}
}
}
现代 Blazor / API 方法 (推荐)
这种方法更灵活,适用于构建前后端分离的应用(如 SPA、Blazor WebAssembly、移动 App),前端通过 JavaScript 或 Blazor 的 JS 互操作将文件发送到后端的 Web API。
后端 Web API
创建一个控制器来专门处理文件上传请求。

using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting; // 引入 IWebHostEnvironment
[ApiController]
[Route("api/[controller]")]
public class UploadController : ControllerBase
{
private readonly IWebHostEnvironment _hostingEnvironment;
// 通过依赖注入获取 IWebHostEnvironment
public UploadController(IWebHostEnvironment hostingEnvironment)
{
_hostingEnvironment = hostingEnvironment;
}
[HttpPost]
public async Task<IActionResult> UploadFile(IFormFile file)
{
// 1. 检查文件
if (file == null || file.Length == 0)
{
return BadRequest("没有选择文件。");
}
// 2. 检查文件大小 ( 10MB)
int maxFileSize = 10 * 1024 * 1024;
if (file.Length > maxFileSize)
{
return BadRequest("文件大小不能超过 10MB。");
}
// 3. 检查文件类型
string[] allowedExtensions = { ".jpg", ".jpeg", ".png", ".gif" };
string fileExtension = Path.GetExtension(file.FileName).ToLower();
if (!Array.Exists(allowedExtensions, ext => ext == fileExtension))
{
return BadRequest("只允许上传 JPG, PNG, GIF 格式的图片。");
}
try
{
// 4. 构建保存路径
// _hostingEnvironment.WebRootPath 指向 wwwroot 目录
string uploadsFolder = Path.Combine(_hostingEnvironment.WebRootPath, "uploads");
if (!Directory.Exists(uploadsFolder))
{
Directory.CreateDirectory(uploadsFolder);
}
// 5. 生成唯一文件名
string uniqueFileName = Guid.NewGuid().ToString() + "_" + file.FileName;
string filePath = Path.Combine(uploadsFolder, uniqueFileName);
// 6. 保存文件
using (var stream = new FileStream(filePath, FileMode.Create))
{
await file.CopyToAsync(stream);
}
// 7. 返回成功响应,可以包含文件的访问路径
string fileUrl = $"/uploads/{uniqueFileName}";
return Ok(new { message = "文件上传成功", filePath = fileUrl });
}
catch (Exception ex)
{
// 记录日志 ex
return StatusCode(500, $"文件上传失败: {ex.Message}");
}
}
}
前端调用
A. 在 Blazor Server 或 Blazor WebAssembly 中
可以使用 IJSRuntime 进行 JS 互操作,或者直接在 Blazor 组件中通过 HttpClient 发送 FormData。
Blazor 组件示例 (Razor):
@page "/upload"
@inject IHttpClientFactory HttpClientFactory
@inject IJSRuntime JSRuntime
<h3>文件上传 (Blazor API)</h3>
<input type="file" @onchange="HandleFileSelected" />
<button @onclick="UploadFile" disabled="@isUploading">上传</button>
<p>Status: @statusMessage</p>
@code {
private IBrowserFile? selectedFile;
private bool isUploading = false;
private string statusMessage = "";
private void HandleFileSelected(InputFileChangeEventArgs e)
{
selectedFile = e.File;
statusMessage = $"已选择文件: {selectedFile.Name}";
}
private async Task UploadFile()
{
if (selectedFile == null)
{
statusMessage = "请先选择一个文件。";
return;
}
isUploading = true;
statusMessage = "正在上传...";
try
{
var httpClient = HttpClientFactory.CreateClient();
var content = new MultipartFormDataContent();
// 将 Blazor 的 IBrowserFile 转换为 StreamContent
var fileContent = new StreamContent(selectedFile.OpenReadStream(maxAllowedSize: 10 * 1024 * 1024));
content.Add(fileContent, "file", selectedFile.Name);
var response = await httpClient.PostAsync("api/upload", content);
if (response.IsSuccessStatusCode)
{
var result = await response.Content.ReadFromJsonAsync<dynamic>();
statusMessage = $"上传成功! 文件路径: {result.filePath}";
}
else
{
statusMessage = $"上传失败: {response.StatusCode}";
}
}
catch (Exception ex)
{
statusMessage = $"发生错误: {ex.Message}";
}
finally
{
isUploading = false;
}
}
}
B. 在纯 JavaScript (HTML 页面) 中
<!DOCTYPE html>
<html>
<head>文件上传 (JS API)</title>
</head>
<body>
<h3>文件上传 (JavaScript API)</h3>
<input type="file" id="fileInput" />
<button onclick="uploadFile()">上传</button>
<p id="status"></p>
<script>
async function uploadFile() {
const fileInput = document.getElementById('fileInput');
const statusElement = document.getElementById('status');
const file = fileInput.files[0];
if (!file) {
statusElement.textContent = '请选择一个文件。';
return;
}
// 创建 FormData 对象
const formData = new FormData();
formData.append('file', file);
try {
statusElement.textContent = '正在上传...';
const response = await fetch('/api/upload', {
method: 'POST',
body: formData // 浏览器会自动设置 Content-Type 为 multipart/form-data
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
statusElement.textContent = `上传成功! 文件路径: ${result.filePath}`;
} catch (error) {
statusElement.textContent = `上传失败: ${error.message}`;
}
}
</script>
</body>
</html>
安全最佳实践 (非常重要)
无论使用哪种方法,安全性都是首要考虑的问题。
-
验证文件内容,而不仅仅是扩展名
- 问题: 用户可以轻易地将
.exe文件重命名为.jpg并上传。 - 解决方案: 读取文件头(Magic Number)来判断真实的文件类型,JPEG 文件的头是
FF D8 FF,可以使用专门的库如FileTypeDetective或FileSignatures来实现。
- 问题: 用户可以轻易地将
-
限制文件大小
- 问题: 恶意用户可能上传一个巨大的文件,耗尽服务器磁盘空间或导致内存溢出。
- 解决方案:
- 前端: 在
<input type="file">上添加accept属性,并提供用户提示。 - 后端:
- MVC: 在
web.config的httpRuntime中设置maxRequestLength。<system.web> <httpRuntime targetFramework="4.8" maxRequestLength="10240" /> <!-- 10MB --> </system.web>
- ASP.NET Core: 在
Program.cs中配置。builder.Services.Configure<FormOptions>(options => { options.MultipartBodyLengthLimit = 10 * 1024 * 1024; // 10MB }); - 在代码中也应再次检查
file.ContentLength或file.Length。
- MVC: 在
- 前端: 在
-
使用安全的文件名
- 问题: 用户上传的文件名可能包含路径(如
../../malicious.exe)或特殊字符,导致路径遍历攻击或服务器错误。 - 解决方案: 永远不要直接使用用户提供的文件名,生成一个随机的、唯一的文件名(如
Guid.NewGuid()),然后将原始文件名作为元数据存储或仅用于显示。
- 问题: 用户上传的文件名可能包含路径(如
-
病毒扫描
- 问题: 上传的文件可能包含恶意软件。
- 解决方案: 在保存文件之前,使用服务器上的杀毒软件(如 ClamAV)对文件内容进行扫描。
-
存储位置
- 最佳实践: 不要将上传的文件与你的应用程序代码放在同一个目录下,最好放在一个专门的、不执行脚本的目录中(
wwwroot之外的App_Data/Uploads或Content/Uploads),这样可以防止上传的脚本文件(如.aspx,.php)被直接执行。
- 最佳实践: 不要将上传的文件与你的应用程序代码放在同一个目录下,最好放在一个专门的、不执行脚本的目录中(
-
授权与认证
- 问题: 任何人都可能访问上传接口或上传的文件。
- 解决方案:
- 确保上传接口需要用户登录(使用
[Authorize]特性)。 - 对于已上传的文件,如果它们不是公开资源,也应该进行访问控制,例如通过一个需要授权的控制器来提供文件下载服务。
- 确保上传接口需要用户登录(使用
| 特性 | 传统 MVC/WebForms | 现代 Blazor/API |
|---|---|---|
| 适用场景 | 传统网站,表单提交 | 现代SPA,前后端分离,移动应用 |
| 实现方式 | 服务器直接处理 HttpPostedFileBase |
客户端通过 FormData 发送,服务器 API 接收 IFormFile |
| 用户体验 | 同步上传,页面会刷新或卡住 | 异步上传,可以显示进度条,用户体验更好 |
| 灵活性 | 较低,与 tightly coupled 的视图绑定 | 高,API 可被任何客户端调用 |
| 核心安全点 | 验证文件名、大小、类型;使用 Guid 防止覆盖 |
同左,但增加了对 IFormFile 流的正确处理 |
对于新项目,强烈推荐使用方法二(Blazor/API),因为它更现代、更安全、更具扩展性,对于维护旧项目,方法一仍然是一个可靠的选择。
