首页 > 解决方案 > 上传完成前文件上传未命中控制器并临时保存到磁盘

问题描述

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

对于上传,我想立即点击控制器。我现在可以看到它缓存到磁盘,这可能是因为我的EnableBuffering属性。我有一个<form method="post" enctype="multipart/form-data">POST 到我的 .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 服务器,而不先保存到磁盘吗?

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

解决方案


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

this.HttpContext.Request.Form.Files

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

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

的源代码DisableFormValueModelBindingAttribute可以在 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)
    {
    }
}

推荐阅读