下载服务器上的文件(最常见)

这是最普遍的需求,即提供一个链接,让用户点击后下载服务器上某个目录(如 ~/Downloads/)中的文件。

核心思路:

  1. 前端:创建一个普通的 <a> 链接,但它的 href 指向一个特殊的处理页面(DownloadHandler.ashxDownloadController/Action),并通过查询字符串(Query String)或路由参数传递要下载的文件名。
  2. 后端
    • 接收前端传递过来的文件名。
    • 安全检查极其重要! 必须验证文件名,防止目录遍历攻击(攻击者传入 ../../../windows/win.ini 这样的路径)。
    • 拼接出服务器上文件的完整物理路径。
    • 检查文件是否存在。
    • 设置 HTTP 响应头,告诉浏览器这是一个需要下载的文件,而不是要在浏览器中直接打开的。
    • 写入到 HTTP 响应输出流中。

示例 1:使用 Web Forms (.aspx + .aspx.cs)

前端页面 (DownloadPage.aspx)

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="DownloadPage.aspx.cs" Inherits="YourWebApp.DownloadPage" %>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">文件下载示例</title>
</head>
<body>
    <form id="form1" runat="server">
        <div>
            <h3>请选择要下载的文件:</h3>
            <!-- 
                LinkButton 的 OnClick 事件会触发后端服务器端代码,
                这是最直接的方式,不需要额外的处理页面。
            -->
            <asp:LinkButton ID="lnkDownloadReport" runat="server" Text="下载年度报告.pdf" OnClick="lnkDownloadReport_Click"></asp:LinkButton>
            <br />
            <br />
            <!-- 或者使用普通的 a 标签,指向一个 Generic Handler -->
            <a href="DownloadHandler.ashx?fileName=mydata.xlsx">下载数据表.xlsx</a>
        </div>
    </form>
</body>
</html>

后端代码 (DownloadPage.aspx.cs)

using System;
using System.IO;
using System.Web.UI;
namespace YourWebApp
{
    public partial class DownloadPage : Page
    {
        protected void lnkDownloadReport_Click(object sender, EventArgs e)
        {
            // 1. 定义要下载的文件名
            string fileName = "年度报告.pdf";
            // 2. 拼接文件的完整物理路径
            // Server.MapPath 将虚拟路径转换为服务器上的物理路径
            string filePath = Server.MapPath("~/Downloads/" + fileName);
            // 3. 安全检查:确保文件存在且路径是安全的
            if (File.Exists(filePath))
            {
                // 4. 设置响应头
                // 告诉浏览器这是一个要下载的附件,并建议的文件名
                Response.ContentType = "application/octet-stream";
                Response.AppendHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\"");
                // 5. 将文件写入响应输出流
                Response.TransmitFile(filePath);
                Response.End(); // 结束响应,防止后续代码执行
            }
            else
            {
                // 文件不存在时的处理
                Response.Write("抱歉,您要下载的文件不存在!");
            }
        }
    }
}

示例 2:使用 MVC (控制器 + 视图)

控制器 (DownloadController.cs)

using System;
using System.IO;
using System.Web.Mvc;
using System.Web;
namespace YourWebApp.Controllers
{
    public class DownloadController : Controller
    {
        // GET: Download
        public ActionResult Index()
        {
            // 可以在这里显示一个文件列表,让用户选择
            return View();
        }
        // 这是真正的下载 Action
        public ActionResult DownloadFile(string fileName)
        {
            if (string.IsNullOrEmpty(fileName))
            {
                return Content("文件名不能为空。");
            }
            // 1. 安全检查:防止目录遍历攻击
            // 使用 Path.GetFileName 确保只获取文件名部分,去掉路径
            string safeFileName = Path.GetFileName(fileName);
            if (string.IsNullOrEmpty(safeFileName))
            {
                return Content("非法的文件名。");
            }
            // 2. 拼接文件的完整物理路径
            string filePath = Server.MapPath("~/Downloads/" + safeFileName);
            // 3. 检查文件是否存在
            if (!System.IO.File.Exists(filePath))
            {
                return HttpNotFound("文件未找到。");
            }
            // 4. 返回 FileResult,MVC框架会自动处理响应头
            // 第一个参数:文件路径
            // 第二个参数:文件类型,null 表示自动推断
            // 第三个参数:下载时显示的文件名
            return File(filePath, "application/octet-stream", safeFileName);
        }
    }
}

视图 (Index.cshtml)

@{
    ViewBag.Title = "文件下载";
}
<h2>文件下载</h2>
@* 使用 ActionLink 生成指向 Download Action 的链接 *@
@Html.ActionLink("下载年度报告.pdf", "DownloadFile", "Download", new { fileName = "年度报告.pdf" }, null)
<br />
<br />
@* 或者使用普通的 a 标签 *@
<a href="@Url.Action("DownloadFile", "Download", new { fileName = "mydata.xlsx" })">下载数据表.xlsx</a>

下载动态生成的文件

根据数据库中的数据生成一个 Excel 或 CSV 文件,然后让用户下载。

核心思路:

  1. 后端:根据业务逻辑生成文件内容(一个 byte[] 数组或 Stream)。
  2. 后端:直接将这个内存中的文件内容写入 HTTP 响应流,并设置正确的响应头。不涉及服务器上的物理文件

示例:MVC 中动态生成并下载 CSV 文件

控制器代码 (ReportController.cs)

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Web.Mvc;
namespace YourWebApp.Controllers
{
    public class ReportController : Controller
    {
        public ActionResult GenerateCsvReport()
        {
            // 1. 模拟从数据库获取数据
            var data = new List<string[]>
            {
                new string[] { "姓名", "年龄", "城市" },
                new string[] { "张三", "28", "北京" },
                new string[] { "李四", "32", "上海" },
                new string[] { "王五", "25", "广州" }
            };
            // 2. 使用内存流构建CSV文件内容
            using (var memoryStream = new MemoryStream())
            using (var streamWriter = new StreamWriter(memoryStream, Encoding.UTF8))
            {
                foreach (var row in data)
                {
                    // 将每行数据用逗号连接,并写入流中
                    streamWriter.WriteLine(string.Join(",", row));
                }
                streamWriter.Flush(); // 确保所有数据都写入内存流
                // 3. 将内存流的指针移到开头
                memoryStream.Position = 0;
                // 4. 返回 FileResult
                // 第一个参数:文件流
                // 第二个参数:MIME 类型
                // 第三个参数:下载时的文件名
                return File(memoryStream, "text/csv", "用户报表.csv");
            }
        }
    }
}

视图代码 (GenerateCsvReport.cshtml)

@{
    ViewBag.Title = "生成报表";
}
<h2>生成报表</h2>
<p>点击下方按钮,动态生成并下载一个 CSV 文件。</p>
@* 使用 ActionLink 生成链接 *@
@Html.ActionLink("下载CSV报表", "GenerateCsvReport", "Report")

下载整个文件夹(或多个文件)

将服务器上的一个文件夹打包成一个 ZIP 文件,然后供用户下载。

核心思路:

  1. 后端
    • 使用一个 ZIP 压缩库,最常用的是 SharpZipLib (ICSharpCode.SharpZipLib)。
    • 获取文件夹中的所有文件。
    • 创建一个内存中的 ZIP 文件流。
    • 遍历文件夹中的每个文件,将其添加到 ZIP 流中。
    • 将生成的 ZIP 流作为文件下载。

示例:MVC 中下载文件夹为 ZIP

通过 NuGet 安装 SharpZipLib 在 Visual Studio 的包管理器控制台中运行: Install-Package ICSharpCode.SharpZipLib

控制器代码 (FolderController.cs)

using System;
using System.IO;
using System.IO.Compression;
using System.Web.Mvc;
using ICSharpCode.SharpZipLib.Zip;
namespace YourWebApp.Controllers
{
    public class FolderController : Controller
    {
        public ActionResult DownloadZipFolder()
        {
            // 1. 定义要打包的文件夹路径
            string folderPath = Server.MapPath("~/MyProjectFiles/");
            string zipFileName = "MyProjectFiles.zip";
            if (!Directory.Exists(folderPath))
            {
                return Content("要打包的文件夹不存在。");
            }
            // 2. 创建内存流来保存 ZIP 文件
            using (var memoryStream = new MemoryStream())
            {
                // 3. 使用 SharpZipLib 创建 ZIP 文件
                using (var zipArchive = new ZipArchive(memoryStream, ZipArchiveMode.Create, true))
                {
                    // 递归或循环添加文件夹中的所有文件
                    foreach (string file in Directory.GetFiles(folderPath, "*.*", SearchOption.AllDirectories))
                    {
                        // 获取相对于文件夹的路径,作为 ZIP 内部的路径
                        string entryName = file.Substring(folderPath.Length).TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
                        // 创建 ZIP 条目
                        var entry = zipArchive.CreateEntry(entryName);
                        // 将文件内容写入 ZIP 条目
                        using (var entryStream = entry.Open())
                        using (var fileStream = new FileStream(file, FileMode.Open, FileAccess.Read))
                        {
                            fileStream.CopyTo(entryStream);
                        }
                    }
                }
                // 4. 将内存流的指针移到开头
                memoryStream.Position = 0;
                // 5. 返回 ZIP 文件
                return File(memoryStream, "application/zip", zipFileName);
            }
        }
    }
}

视图代码 (DownloadZipFolder.cshtml)

@{
    ViewBag.Title = "下载文件夹";
}
<h2>下载文件夹</h2>
<p>点击下方按钮,下载整个文件夹的 ZIP 压缩包。</p>
@Html.ActionLink("下载文件夹 ZIP", "DownloadZipFolder", "Folder")

总结与安全提示

需求场景 推荐方法 关键技术点
下载静态文件 MVC: return File(filePath, ...)
WebForms: Response.TransmitFile()
Server.MapPath(), Path.GetFileName() (安全), Response 对象
下载动态生成文件 MVC: return File(memoryStream, ...) MemoryStream, StreamWriter, byte[] 数组
下载文件夹/多文件 MVC: return File(memoryStream, ...) + SharpZipLib ZipArchive, MemoryStream, 递归遍历目录

⚠️ 安全警告: 在处理任何来自用户输入的文件名时,绝对不能直接拼接路径,必须进行严格的验证和清理,以防止 目录遍历攻击 (Directory Traversal / Path Traversal),攻击者可能会使用类似 ../../../etc/passwd 的路径来访问你服务器上的敏感文件。

始终使用以下方法之一来确保安全:

  1. Path.GetFileName(): 从字符串中提取纯粹的文件名,去除所有路径信息。
    string userInput = "../../malicious.exe";
    string safeFileName = Path.GetFileName(userInput); // 结果是 "malicious.exe"
  2. 白名单验证: 如果你知道所有可能的文件名,可以检查用户提供的文件名是否在你的允许列表中。
  3. 规范化路径: 检查最终拼接的完整路径是否在你允许的目录范围内。

希望这些详细的示例能帮助你解决 ASP.NET 网页代码下载的问题!