【问题标题】:ASP.NET Core web application - How to upload large filesASP.NET Core Web 应用程序 - 如何上传大文件
【发布时间】:2022-01-03 05:24:14
【问题描述】:

问题

我正在尝试创建一个 ASP.NET Core (3.1) Web 应用程序,该应用程序接受文件上传,然后将其分成块以通过 MS Graph API 发送到 Sharepoint。这里还有一些其他帖子解决了类似的问题,但他们假设我还没有一定程度的 .NET 知识。所以我希望有人能帮我拼凑一些东西。

配置 Web 服务器和应用程序以接受大文件

我已完成以下操作以允许 IIS Express 上传最多 2GB 的文件:

a) 使用以下代码创建了一个 web.config 文件:

<?xml version="1.0" encoding="utf-8"?>
<configuration>

    <location path="Home/UploadFile">
        <system.webServer>
            <handlers>
                <add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModuleV2" resourceType="Unspecified" />
            </handlers>
            <security>
                <requestFiltering>
                    <!--unit is bytes => 2GB-->
                    <requestLimits maxAllowedContentLength="2147483647" />
                </requestFiltering>
            </security>
        </system.webServer>
    </location>
</configuration>

B) 我的 Startup.cs 配置部分中有以下内容:

        //Add support for uploading large files  TODO:  DO I NEED THIS?????
        services.Configure<FormOptions>(x =>
        {

            x.ValueLengthLimit = int.MaxValue; // Limit on individual form values
            x.MultipartBodyLengthLimit = int.MaxValue; // Limit on form body size
            x.MultipartHeadersLengthLimit = int.MaxValue; // Limit on form header size
        });

        services.Configure<IISServerOptions>(options =>
        {
            options.MaxRequestBodySize = int.MaxValue;  //2GB
         });

这是我的表单的样子,它允许用户选择文件并提交:

@{
    ViewData["Title"] = "Messages";
}
<h1>@ViewData["Title"]</h1>

<p></p>
<form id="uploadForm" action="UploadFile" method="post" enctype="multipart/form-data">
    <dl>
        <dt>
            <label for="file">File</label>
        </dt>
        <dd>
            <input id="file" type="file" name="file" />
        </dd>
    </dl>

    <input class="btn" type="submit" value="Upload" />

    <div style="margin-top:15px">
        <output form="uploadForm" name="result"></output>
    </div>
</form>

这是控制器的外观:

    [HttpPost]
    [RequestSizeLimit(2147483647)]       //unit is bytes => 2GB
    [RequestFormLimits(MultipartBodyLengthLimit = 2147483647)]
    public async void UploadFile()
    {
        User currentUser = null;
        currentUser = await _graphServiceClient.Me.Request().GetAsync();
        //nothing have to do with the file has been written yet. 

    }

当用户单击文件按钮并选择一个大文件时,我不再收到 IIS 413 错误消息。伟大的。逻辑在我的控制器中找到了正确的方法。

但是我对这部分代码有以下疑问:

  • 当用户选择文件时……幕后实际发生了什么?该文件是否实际上已填充到我的表单中并且可以从我的控制器访问?

  • 是流吗?

  • 如何获取文件?

  • 如果最终,我需要使用this type 的一种方法(关于分块的最后一个示例)将此文件发送到 Sharepoint,似乎最好的方法是将文件保存在我的服务器上的某个地方......然后复制示例代码并尝试将其分块?示例代码似乎是指文件路径和文件大小,我假设我需要先将它保存到我的网络服务器的某个地方,然后从那里获取它。

  • 如果我确实需要保存它,你能指出我正确的方向吗?也许是一些示例代码,告诉我如何在我的表单中获取 POST 的数据并保存它?

  • 最终,这将需要在没有 GUI 的情况下重构操作系统……但它只是一个接受大文件上传到某处的 API。但我想我会先尝试学习如何做到这一点......然后重构以将我的代码更改为仅限 API。

对不起,菜鸟问题。在发帖之前,我曾尝试进行研究。但是有些东西还是有点模糊。

编辑 1

根据已发布答案之一中的建议,我下载了 sample code,它演示了如何绕过保存到 Web 服务器上的本地文件。它基于this article

我再次创建了一个 web.config 文件 - 以避免来自 IIS 的 413 错误。我还编辑了允许的文件扩展名列表以支持 .pdf 和 .docx 和 .mp4。

当我尝试运行示例项目,并在“物理存储上传示例”部分下选择“使用 AJAX 将文件流式传输到控制器端点”时,它会死在这里:

                // This check assumes that there's a file
                // present without form data. If form data
                // is present, this method immediately fails
                // and returns the model error.
                if (!MultipartRequestHelper
                    .HasFileContentDisposition(contentDisposition))
                if (!MultipartRequestHelper
                    .HasFileContentDisposition(contentDisposition))
                {
                    ModelState.AddModelError("File", 
                        $"The request couldn't be processed (Error 2).");
                    // Log error

                    return BadRequest(ModelState);
                }

正如代码上面的 cmets 中提到的,它正在检查表单数据,然后当它找到它时......它就死了。所以我一直在玩看起来像这样的 HTML 页面:

<form id="uploadForm" action="Streaming/UploadPhysical" method="post" 
    enctype="multipart/form-data" onsubmit="AJAXSubmit(this);return false;">
    <dl>
        <dt>
            <label for="file">File</label>
        </dt>
        <dd>
            <input id="file" type="file" name="file" />asdfasdf
        </dd>
    </dl>

    <input class="btn" type="submit" value="Upload" />

    <div style="margin-top:15px">
        <output form="uploadForm" name="result"></output>
    </div>
</form>

我已经尝试删除这样的表单:

<dl>
    <dt>
        <label for="file">File</label>
    </dt>
    <dd>
        <input id="file" type="file" name="file" />
    </dd>
</dl>

<input class="btn" type="button" asp-controller="Streaming" asp-action="UploadPhysical" value="Upload" />

<div style="margin-top:15px">
    <output form="uploadForm" name="result"></output>
</div>

但是当我点击它时,按钮现在什么都不做。

另外,如果您想知道/它有帮助,我手动将文件复制到计算机上的 c:\files 文件夹中,当示例应用程序打开时,它确实列出了文件 - 证明它可以读取文件夹. 我添加了读/写权限,因此希望 Web 应用程序可以在我到达那一步时对其进行写入。

【问题讨论】:

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


【解决方案1】:

我已经实现了一个类似的大文件控制器,但使用的是 mongoDB GridFS。

无论如何,流式传输是处理大文件的最佳方式,因为它既快速又轻量。 是的,最好的选择是在发送之前将文件保存在服务器存储中。 一个建议是,添加一些验证以允许特定扩展并限制执行权限。

回到你的问题:

将整个文件读入 IFormFile,它是用于处理或保存文件的文件的 C# 表示形式。

文件上传使用的资源(磁盘、内存)取决于并发文件上传的数量和大小。如果应用程序尝试缓冲过多的上传内容,则站点会在内存或磁盘空间不足时崩溃。如果文件上传的大小或频率耗尽了应用资源,请使用流式传输。

source 1

CopyToAsync 方法使您能够在不阻塞主线程的情况下执行资源密集型 I/O 操作。

source 2

这里有例子。

示例 1:

using System.IO;
using Microsoft.AspNetCore.Http;
//...

[HttpPost]
[Authorize]
[DisableRequestSizeLimit]
[RequestFormLimits(ValueLengthLimit = int.MaxValue, MultipartBodyLengthLimit = int.MaxValue)]
[Route("upload")]
public async Task<ActionResult> UploadFileAsync(IFormFile file)
{  
  if (file == null)
    return Ok(new { success = false, message = "You have to attach a file" });

  var fileName = file.FileName;     
  // var extension = Path.GetExtension(fileName);

  // Add validations here...
      
  var localPath = $"{Path.Combine(System.AppContext.BaseDirectory, "myCustomDir")}\\{fileName}";
  
  // Create dir if not exists
  Directory.CreateDirectory(Path.Combine(System.AppContext.BaseDirectory, "myCustomDir"));
  
  using (var stream = new FileStream(localPath, FileMode.Create)){
    await file.CopyToAsync(stream);
  }

  // db.SomeContext.Add(someData);
  // await db.SaveChangesAsync();

  return Ok(new { success = true, message = "All set", fileName});      
}  

使用 GridFS 的示例 2:

[HttpPost]
[Authorize]
[DisableRequestSizeLimit]
[RequestFormLimits(ValueLengthLimit = int.MaxValue, MultipartBodyLengthLimit = int.MaxValue)]
[Route("upload")]
public async Task<ActionResult> UploadFileAsync(IFormFile file)
{
  if (file == null)
    return Ok(new { success = false, message = "You have to attach a file" });

  var options = new GridFSUploadOptions
  {
    Metadata = new BsonDocument("contentType", file.ContentType)
  };

  using (var reader = new StreamReader(file.OpenReadStream()))
  {
    var stream = reader.BaseStream;
    await mongo.GridFs.UploadFromStreamAsync(file.FileName, stream, options);    
  }

  return Ok(new { success = true, message = "All set"});
}

【讨论】:

  • 我赞成您的回答,因为它“刚刚奏效”。但是......我实际上希望避免写入磁盘......所以我试图看看是否可以让流解决方案工作,因为它有助于我的最终目标是将“块”数据发送到 Sharepoint。
【解决方案2】:

您走在正确的道路上,但正如其他人指出的那样,Microsoft 已经提供了一份写得很好的文件上传文件,在您的情况下必须阅读 - https://docs.microsoft.com/en-us/aspnet/core/mvc/models/file-uploads?view=aspnetcore-6.0#upload-large-files-with-streaming

至于你的问题

  • 你需要services.Configure&lt;FormOptions&gt;(x =&gt;

    不,你没有!而且您也不需要services.Configure&lt;IISServerOptions&gt;(options =&gt;,它从您在web.config 中配置的maxAllowedContentLength 读取

  • 当用户选择文件时……幕后实际发生了什么?该文件是否实际上已填充到我的表单中并且可以从我的控制器访问?,它是一个流吗?

    如果您禁用表单值模型绑定并使用 MultipartReader 文件将被流式传输并且不会缓存到内存或磁盘中,当您排空流时,将从客户端(浏览器)接受更多数据

  • 如何获取文件?

    查看上面的文档,有一个访问流的工作示例。

  • 如果最终,我需要使用这种类型的方法(最后一个关于分块的示例)将此文件发送到 Sharepoint,似乎最好的方法是将文件保存在我的服务器上的某个地方......并且然后复制示例代码并尝试将其分块?示例代码似乎是指文件路径和文件大小,我假设我需要先将它保存到我的网络服务器的某个地方,然后从那里获取它。

    不一定,使用流的方式可以直接复制流数据。

【讨论】:

  • 所以我一直在审查那个例子......这是我开始但无法继续的例子。但是我已经从 microsoft 下载了该演示代码的 sampleApp,但它失败了。请参阅编辑 1
猜你喜欢
  • 2020-07-09
  • 1970-01-01
  • 1970-01-01
  • 2018-11-30
  • 2016-11-14
  • 1970-01-01
  • 2015-07-19
  • 1970-01-01
  • 2010-09-13
相关资源
最近更新 更多