.NET 文件下载的原理与实现


前言

在.NET 里 实现文本下载可以依赖于HTTP协议来实现,通过基于客户端和服务器之间的请求 - 响应机制实现文件下载。本文先由HTTP协议的回顾开始,进而分别讨论.NET Core和.NET Framework里如何实现服务器本地文件下载。


一、HTTP 的 请求 - 响应模型

HTTP协议采用请求/响应模式,客户端向服务器发送一个请求报文,通过请求告诉服务器 “我需要某个文件”,服务器通过响应将文件数据传输给客户端。

1.1 浏览器(客户端)发起请求

通过请求的 URI,解析域名,建立TCP连接成功后,浏览器发起http请求。

1.2 服务器处理请求并构建响应

服务器收到请求后,检索指定资源,通过设置响应头字段Content-Type(指示资源的MIME类型,告诉客户端如何解析和显示内容),Content-Disposition(设置浏览器行为,指示内容的展示形式,可以是内联显示或附件下载),Content-Length(文件字节大小),Accept-Ranges(表示可用于定义范围的单位,向客户端宣传其对文件下载的部分请求,浏览器可能会尝试恢复中断的下载)。最后返回状态码。常用的状态码有200 OK(请求成功),400 Bad Request(服务器无法理解请求),401 Unauthorized(未授权),404 Not Found(资源未找到),408 Request Time-out(请求超时),415 Unsupported Media Type(不支持的媒体类型),500 Internal Server Error(服务器内部错误),502 Bad Gateway(服务器之间的通信存在问题)。

1.3 客户端接受文件

服务器将文件以 字节流 形式写入响应体,逐块传输给客户端(如 Content-Type: application/octet-stream)。客户端解析响应头,解析上文提到的 Content-Disposition,Content-Length等,将响应体数据以何种方式保存 何种行为展示

二、代码实现

2.1 实现背景

数据批量导入导出是一个在工作中十分常见的业务,这里讨论从服务器中下载导入数据Excel模板这种情况,该模板是一种已经存在的资源文件。

2.2 .NET 8 实现流程

找到文件目录,通过Path.Combine结合项目根目录与资源文件夹和资源名称。

	string fileName = "上传数据模板.xlsx";
    string basePath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot","static");
	string filePath = Path.Combine(basePath, fileName);

验证文件

	if (!System.IO.File.Exists(filePath))
    {
        return NotFound("文件未找到");
    }

设置文件类型,默认字节流(octet-stream)

	var contentTypeProvider = new FileExtensionContentTypeProvider();
    if (!contentTypeProvider.TryGetContentType(fileName, out var contentType))
    {
        contentType = "application/octet-stream";
    }

设定文件名称并返回文件

PhysicalFile 是直接指定文件的物理路径(绝对路径),而 File 方法有其他重载,比如使用相对路径,或者从流中读取。使用PhysicalFile 要确认地址访问的安全性,防止目录遍历攻击。

    string encodedFileName = WebUtility.UrlEncode(fileName, Encoding.UTF8);
    return PhysicalFile(filePath, contentType, encodedFileName);

2.3 .NET Framework 实现流程

找到文件目录,通过Path.Combine结合项目根目录与资源文件夹和资源名称。

	// 获取应用程序的基目录
    string baseDirectory = AppDomain.CurrentDomain.BaseDirectory;
	// 相对路径
	string relativePath = Path.Combine("Comm", "Excel", "上传数据模板.xlsx");
	// 拼接完整路径
	string fullPath = Path.Combine(baseDirectory, relativePath);

找到指定的资源文件

	FileInfo fi = new FileInfo(fullPath);
	if (!fi.Exists)
	{
	    HttpContext.Current.Response.StatusCode = 404;
	    HttpContext.Current.Response.Write("文件未找到");
	    return;
	}

设定响应头并写入文件到响应体

	HttpContext.Current.Response.Clear();
	// //当要下载的文件名是中文时,需加上HttpUtility.UrlEncode
	HttpContext.Current.Response.AddHeader("Content-Disposition", "attachment;filename=" + HttpUtility.UrlEncode(fi.Name));
	HttpContext.Current.Response.AddHeader("Content-Length", fi.Length.ToString());
	HttpContext.Current.Response.ContentType = "application/octet-stream";
	HttpContext.Current.Response.WriteFile(fi.FullName);
	HttpContext.Current.Response.End();

三、完整代码

3.1 .NET8 代码实现

[HttpGet]
public IActionResult DownloadExcel()
{
    string fileName = "上传数据模板.xlsx";
    string basePath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot","static");
	string filePath = Path.Combine(basePath, fileName);
   
    if (!System.IO.File.Exists(filePath))
    {
        return NotFound("文件未找到");
    }
    // 获取文件类型
    var contentTypeProvider = new FileExtensionContentTypeProvider();
    if (!contentTypeProvider.TryGetContentType(fileName, out var contentType))
    {
        contentType = "application/octet-stream";
    }
    // 显式指定编码
    string encodedFileName = WebUtility.UrlEncode(fileName, Encoding.UTF8);
    return PhysicalFile(filePath, contentType, encodedFileName);
}

3.2 .NET Framework 代码实现

public void DownloadExcel()
{
    try
    {
        // 获取应用程序的基目录
        string baseDirectory = AppDomain.CurrentDomain.BaseDirectory;
        // 相对路径
        string relativePath = Path.Combine("Comm", "Excel", "上传数据模板.xlsx");
        // 拼接完整路径
        string fullPath = Path.Combine(baseDirectory, relativePath);
        FileInfo fi = new FileInfo(fullPath);
        if (!fi.Exists)
		{
		    HttpContext.Current.Response.StatusCode = 404;
		    HttpContext.Current.Response.Write("文件未找到");
		    return;
		}
        HttpContext.Current.Response.Clear();
        // //当要下载的文件名是中文时,需加上HttpUtility.UrlEncode
        HttpContext.Current.Response.AddHeader("Content-Disposition", "attachment;filename=" + HttpUtility.UrlEncode(fi.Name));
        HttpContext.Current.Response.AddHeader("Content-Length", fi.Length.ToString());
        HttpContext.Current.Response.ContentType = "application/octet-stream";
        HttpContext.Current.Response.WriteFile(fi.FullName);
        HttpContext.Current.Response.End();
    }
    catch (Exception ex)
    {
        HttpContext.Current.Response.StatusCode = 500;
        HttpContext.Current.Response.Write(ex.Message);
    }
    
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值