凌峰创科服务平台

ASP.NET如何上传文件到服务器?

  1. 传统 WebForms / MVC 方法:使用 <input type="file"> 和服务器端代码。
  2. 现代 Blazor / API 方法:使用 JavaScript (或 Blazor 的 JS 互操作) 和 Web API 来处理异步上传。

传统 WebForms / MVC 方法

这是最经典、最直接的方法,适用于传统的 ASP.NET Web Forms 和 ASP.NET MVC 项目。

ASP.NET如何上传文件到服务器?-图1
(图片来源网络,侵删)

前端视图 (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如何上传文件到服务器?-图2
(图片来源网络,侵删)

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

创建一个控制器来专门处理文件上传请求。

ASP.NET如何上传文件到服务器?-图3
(图片来源网络,侵删)
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>

安全最佳实践 (非常重要)

无论使用哪种方法,安全性都是首要考虑的问题。

  1. 验证文件内容,而不仅仅是扩展名

    • 问题: 用户可以轻易地将 .exe 文件重命名为 .jpg 并上传。
    • 解决方案: 读取文件头(Magic Number)来判断真实的文件类型,JPEG 文件的头是 FF D8 FF,可以使用专门的库如 FileTypeDetectiveFileSignatures 来实现。
  2. 限制文件大小

    • 问题: 恶意用户可能上传一个巨大的文件,耗尽服务器磁盘空间或导致内存溢出。
    • 解决方案:
      • 前端: 在 <input type="file"> 上添加 accept 属性,并提供用户提示。
      • 后端:
        • MVC: 在 web.confighttpRuntime 中设置 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.ContentLengthfile.Length
  3. 使用安全的文件名

    • 问题: 用户上传的文件名可能包含路径(如 ../../malicious.exe)或特殊字符,导致路径遍历攻击或服务器错误。
    • 解决方案: 永远不要直接使用用户提供的文件名,生成一个随机的、唯一的文件名(如 Guid.NewGuid()),然后将原始文件名作为元数据存储或仅用于显示。
  4. 病毒扫描

    • 问题: 上传的文件可能包含恶意软件。
    • 解决方案: 在保存文件之前,使用服务器上的杀毒软件(如 ClamAV)对文件内容进行扫描。
  5. 存储位置

    • 最佳实践: 不要将上传的文件与你的应用程序代码放在同一个目录下,最好放在一个专门的、不执行脚本的目录中(wwwroot 之外的 App_Data/UploadsContent/Uploads),这样可以防止上传的脚本文件(如 .aspx, .php)被直接执行。
  6. 授权与认证

    • 问题: 任何人都可能访问上传接口或上传的文件。
    • 解决方案:
      • 确保上传接口需要用户登录(使用 [Authorize] 特性)。
      • 对于已上传的文件,如果它们不是公开资源,也应该进行访问控制,例如通过一个需要授权的控制器来提供文件下载服务。
特性 传统 MVC/WebForms 现代 Blazor/API
适用场景 传统网站,表单提交 现代SPA,前后端分离,移动应用
实现方式 服务器直接处理 HttpPostedFileBase 客户端通过 FormData 发送,服务器 API 接收 IFormFile
用户体验 同步上传,页面会刷新或卡住 异步上传,可以显示进度条,用户体验更好
灵活性 较低,与 tightly coupled 的视图绑定 高,API 可被任何客户端调用
核心安全点 验证文件名、大小、类型;使用 Guid 防止覆盖 同左,但增加了对 IFormFile 流的正确处理

对于新项目,强烈推荐使用方法二(Blazor/API),因为它更现代、更安全、更具扩展性,对于维护旧项目,方法一仍然是一个可靠的选择。

分享:
扫描分享到社交APP
上一篇
下一篇