You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.NET Core API基于MediatR模式下载wwwroot文件实现方案

实现问题说明

当前代码运行报错、功能不生效的核心原因如下:

  • 返回类型定义完全不符合文件下载要求:MediatR请求、处理器、控制器接口全部定义为返回bool类型,无法向客户端返回文件流数据
  • 文件读取逻辑错误:本地wwwroot目录下的文件无需通过WebClient发起HTTP请求读取,且代码存在路径重复拼接问题,会触发文件找不到异常
  • 逻辑判断无效:数据库查询使用Where()后直接判断非空,Where()返回的IQueryable对象永远不为null,判断逻辑完全不生效
  • 存在安全风险:未做文件名合法性校验,存在路径遍历攻击漏洞,未校验文件真实存在性
  • 依赖版本过时:使用已废弃的IHostingEnvironment接口,.NET Core 3.0+版本需替换为IWebHostEnvironment

MediatR模式下正确实现方案

保持MediatR业务层与Web层解耦,业务逻辑层返回文件数据传输对象,控制器层负责转换为HTTP文件响应。

1. 定义文件传输模型与Command

在Application层新增文件下载返回Dto,调整Command的返回值类型:

// 文件下载传输模型
public class FileDownloadResult
{
    public bool IsSuccess { get; set; }
    public string ErrorMessage { get; set; }
    public byte[] FileContent { get; set; }
    public string FileName { get; set; }
    public string ContentType { get; set; }
}

// 修改Command的IRequest泛型参数为上述模型
public class DownloadMPWorthyFileCommand : IRequest<FileDownloadResult>
{
    public string FileName { get; set; }
    public string PageName { get; set; }
    public string IdeaNumber { get; set; }
}

2. 重写Command Handler处理逻辑

移除无用的冗余依赖注入,补充校验逻辑,直接读取本地文件流:

using Microsoft.AspNetCore.Hosting;
using Microsoft.EntityFrameworkCore;

public class DownloadMPWorthyFileCommandHandler : IRequestHandler<DownloadMPWorthyFileCommand, FileDownloadResult>
{
    private readonly IKaizenDBContext _context;
    private readonly IWebHostEnvironment _webHostEnv;

    // 仅注入实际用到的依赖,删除IMapper、IEmailService等无用注入
    public DownloadMPWorthyFileCommandHandler(IKaizenDBContext context, IWebHostEnvironment webHostEnv)
    {
        _context = context;
        _webHostEnv = webHostEnv;
    }

    public async Task<FileDownloadResult> Handle(DownloadMPWorthyFileCommand request, CancellationToken cancellationToken)
    {
        // 文件名合法性校验,拦截路径遍历攻击
        if (string.IsNullOrWhiteSpace(request.FileName) 
            || request.FileName.Contains("..") 
            || Path.IsPathRooted(request.FileName))
        {
            return new FileDownloadResult { IsSuccess = false, ErrorMessage = "非法文件名" };
        }

        // 查询数据库文件记录,可在此处扩展用户权限校验
        var fileRecord = await _context.DocumentUploaded
            .FirstOrDefaultAsync(e => 
                e.IdeaNumber == request.IdeaNumber 
                && e.PageName == request.PageName
                && e.FileName == request.FileName, 
                cancellationToken);
        if (fileRecord == null)
        {
            return new FileDownloadResult { IsSuccess = false, ErrorMessage = "文件记录不存在" };
        }

        // 拼接wwwroot下文件的真实物理路径
        var filePhysicalPath = Path.Combine(_webHostEnv.WebRootPath, "document", request.FileName);
        if (!File.Exists(filePhysicalPath))
        {
            return new FileDownloadResult { IsSuccess = false, ErrorMessage = "服务器文件丢失" };
        }

        // 读取文件内容,识别文件MIME类型
        var fileBytes = await File.ReadAllBytesAsync(filePhysicalPath, cancellationToken);
        var fileExt = Path.GetExtension(request.FileName).ToLowerInvariant();
        var contentType = fileExt switch
        {
            ".pdf" => "application/pdf",
            ".doc" => "application/msword",
            ".docx" => "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
            ".xls" => "application/vnd.ms-excel",
            ".xlsx" => "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
            ".png" => "image/png",
            ".jpg" or ".jpeg" => "image/jpeg",
            ".txt" => "text/plain",
            _ => "application/octet-stream"
        };

        return new FileDownloadResult
        {
            IsSuccess = true,
            FileContent = fileBytes,
            FileName = request.FileName,
            ContentType = contentType
        };
    }
}

3. 修改控制器接口

调整接口返回类型为IActionResult,根据Handler返回结果生成响应:

[HttpPost("DownloadMPWorthyFile")]
public async Task<IActionResult> DownloadMPWorthyFile([FromBody] DownloadMPWorthyFileCommand command)
{
    var downloadResult = await Mediator.Send(command);
    if (!downloadResult.IsSuccess)
    {
        return BadRequest(new { message = downloadResult.ErrorMessage });
    }
    // 返回文件流,触发浏览器下载行为
    return File(downloadResult.FileContent, downloadResult.ContentType, downloadResult.FileName);
}

前端对接注意事项
  • 若使用Angular HttpClient发起Post请求,必须设置responseType: 'blob',拿到二进制Blob对象后通过URL.createObjectURL生成临时链接,触发浏览器下载
  • 如果希望简化前端调用,可将接口改为[HttpGet],参数通过QueryString传递,前端可直接通过a标签、window.open触发下载,无需额外处理Blob流

内容的提问来源于stack exchange,提问作者sonali borekarr

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 11:36:20