【问题标题】:File upload not hitting controller before upload is finished and temporarily saved to disk上传完成前文件上传未命中控制器并临时保存到磁盘
【发布时间】:2020-07-09 13:45:53
【问题描述】:

假设我在具有 1GB 存储和 1GB RAM 的设备上运行 .NET Core API。我想将文件从我的网站直接上传到 FTP 服务器,而不会将文件缓存在内存或磁盘中。我有这个用于下载文件的工作,因为我基本上充当代理,通过在流中打开 FTP 文件,然后将其直接流式传输到 HttpContext.Request.Body

为了上传,我想立即点击控制器。我现在可以看到它缓存到磁盘,这可能是因为我的EnableBuffering 属性。我有一个常规的<form method="post" enctype="multipart/form-data">,它会发布到我的 .NET Core 后端。控制器如下所示:

[EnableBuffering]
[RequestFormLimits(ValueLengthLimit = int.MaxValue, MultipartBodyLengthLimit = long.MaxValue)]
[HttpPost("[action]")]
public async Task<IActionResult> Upload(string path)
{
    if (!IsMultipartContentType(HttpContext.Request.ContentType))
        return BadRequest("Not multipart request");

    await _fileProvider.Upload(path);

    return Ok();
}

启用缓冲:

public class EnableBufferingAttribute : Attribute, IResourceFilter
{
    public void OnResourceExecuting(ResourceExecutingContext context)
    {
        context.HttpContext.Request.EnableBuffering();
    }

    public void OnResourceExecuted(ResourceExecutedContext context) {}
}

上传逻辑:

public async Task Upload(string path)
{
    const int buffer = 8 * 1024;

    var request = _httpContextAccessor.HttpContext.Request;
    var boundary = request.GetMultipartBoundary();
    var reader = new MultipartReader(boundary, request.Body, buffer);
    var section = await reader.ReadNextSectionAsync();

    using (var client = await _ftpHelper.GetFtpClient())
    {
        while (section != null)
        {
            var fileSection = section.AsFileSection();

            if (fileSection != null)
            {
                var fileName = fileSection.FileName;

                var uploadPath = Path.Combine(path, fileName);

                var stream = await client.OpenWriteAsync(uploadPath);
                await section.Body.CopyToAsync(stream, buffer);
            }

            section = await reader.ReadNextSectionAsync();
        }
    }
}

如果我不启用缓冲,我会得到:

System.IO.IOException: Stream 意外结束,内容可能已被另一个组件读取。

但是,通过启用缓冲,我可以看到它说:

确保 requestBody 可以被多次读取。通常在内存中缓冲请求体; 将大于 30K 字节的请求写入磁盘

所以这部分是有道理的。但是,我该如何解决这个问题?如果我在Upload() 方法的第一行设置断点,然后开始上传,它首先上传文件然后 命中断点。

我能否解决这个问题,让它立即访问我的控制器并开始上传到 FTP 服务器,而不先保存到磁盘?

【问题讨论】:

  • 尝试使用日志输出而不是设置断点,因为它可能会干扰上传工作流
  • @HirasawaYui 我可以保证在上传完成之前它不会撞到控制器。通过构建和部署到生产环境进行测试。
  • 您将需要缓冲,但只需使用一个小缓冲区。 CopyTo 中使用的默认缓冲区大小为 82kb,这意味着您可以传输大文件,而每次传输仅使用 82kb 内存
  • @MalteR 是的,没错,但是,这并不能解决我在磁盘上缓存文件的问题。即使我删除了 EnableBuffering 属性,它仍然会先上传文件,同时暂时保存到磁盘,然后再访问我的控制器。

标签: c# asp.net-core .net-core fluentftp


【解决方案1】:

在进入Upload方法之前处理请求的原因是Form model bindingUpload 方法有path 参数,它的值来自表单数据。因此,需要在调用控制器方法之前解析请求以从表单数据中提取path 参数值。 同样在这种情况下,可以使用Files 集合检索上传文件的列表和内容,该集合通过ControllerBase 类的HttpContext 属性提供。

this.HttpContext.Request.Form.Files

解决此问题的最简单方法是禁用表单模型绑定并通过查询字符串传递path 参数。

[HttpPost("[action]")]
[DisableFormValueModelBinding]
public async Task<IActionResult> Upload([FromQuery] string path)

DisableFormValueModelBindingAttribute的源码可以在Upload files in ASP.NET Core的文章中找到。

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class DisableFormValueModelBindingAttribute : Attribute, IResourceFilter
{
    public void OnResourceExecuting(ResourceExecutingContext context)
    {
        var factories = context.ValueProviderFactories;
        factories.RemoveType<FormValueProviderFactory>();
        factories.RemoveType<FormFileValueProviderFactory>();
        factories.RemoveType<JQueryFormValueProviderFactory>();
    }

    public void OnResourceExecuted(ResourceExecutedContext context)
    {
    }
}

【讨论】:

    猜你喜欢
    • 2013-12-26
    • 2018-09-14
    • 1970-01-01
    • 2016-02-20
    • 1970-01-01
    • 2011-03-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多