简体   繁体   English

ASP.NET 核心 web 应用 - 如何上传大文件

[英]ASP.NET Core web application - How to upload large files

Problem问题

I'm trying to create an ASP.NET Core (3.1) web application that accepts file uploads and then breaks it into chunks to send to Sharepoint via MS Graph API. I'm trying to create an ASP.NET Core (3.1) web application that accepts file uploads and then breaks it into chunks to send to Sharepoint via MS Graph API. There are a few other posts here that address the similar questions but they assume a certain level of .NET knowledge that I don't have just yet.这里还有一些其他帖子解决了类似的问题,但他们假设我还没有一定程度的 .NET 知识。 So I'm hoping someone can help me cobble something together.所以我希望有人能帮我拼凑一些东西。

Configure Web server & app to Accept Large Files配置 Web 服务器和应用程序以接受大文件

I have done the following to allow IIS Express to upload up to 2GB files:我已完成以下操作以允许 IIS Express 最多上传 2GB 文件:

a) created a web.config file with the following code: 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) I have the following in my Startup.cs Configuration section: 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
         });

Here's what my form looks like that allows the user to pick the file and submit:这是我的表单的样子,它允许用户选择文件并提交:

@{
    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>

Here's what the controller looks like:这是 controller 的样子:

    [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. 

    }

When the user clicks on the file button and chooses a large file, I no longer get IIS 413 error messages.当用户单击文件按钮并选择大文件时,我不再收到 IIS 413 错误消息。 Great.伟大的。 The logic hits the right method in my controller.逻辑在我的 controller 中找到了正确的方法。

But I have the following questions for this part of the code:但是我对这部分代码有以下问题:

  • When the user picks the file... what is actually happening under the hood?当用户选择文件时……幕后实际发生了什么? Has the file actually been stuffed into my form and is accessible from my controller?该文件是否实际上已填充到我的表单中并且可以从我的 controller 访问?

  • Is it a stream?是 stream 吗?

  • how do i get to the file?我如何获取文件?

  • If ultimately, I need to send this file to Sharepoint using this type of an approach (the last example on chunking), it seems that the best approach is to save the file on my server somewhere... and then copy the sample code and try to chunk it out?如果最终,我需要使用这种方法(关于分块的最后一个示例)将此文件发送到 Sharepoint,似乎最好的方法是将文件保存在我的服务器上的某个地方......然后复制示例代码并试着把它分块? The sample code seems to be referring to file paths and file sizes, I'm assuming I need to persist it to my web server somewhere first, and then take it from there.示例代码似乎是指文件路径和文件大小,我假设我需要先将它保存到我的 web 服务器的某个地方,然后从那里获取它。

  • if i do need to save it, can you point me in the right direction - maybe some sample code that shows me how to take the POSTed data in my form and save it?如果我确实需要保存它,你能指出我正确的方向吗 - 也许一些示例代码向我展示了如何在我的表单中获取发布的数据并保存它?

  • ultimately, this will need to be refactored os that there is not GUI... but it's just an API that accepts large files to upload somewhere.最终,这将需要重构没有GUI的操作系统......但它只是一个接受大文件上传到某处的API。 But I think i'll try to learn how to do it this way first... and then refactor to change my code to be API only.但我想我会先尝试学习如何做到这一点......然后重构以将我的代码更改为仅 API。

Sorry for the noob questions.对不起菜鸟问题。 I have tried to do my research before posting here.在发帖之前,我曾尝试进行研究。 But somethings are still a bit fuzzy.但是有些东西还是有点模糊。

EDIT 1编辑 1

Per the suggestion in one of the posted answers, i've downloaded sample code that demonstrates how to bypass saving to a local file on the web server.根据已发布答案之一中的建议,我已经下载了示例代码,该代码演示了如何绕过保存到 web 服务器上的本地文件。 It's based on this article它基于这篇文章

I have created a web.config file again -to avoid the 413 errors from IIS.我再次创建了 web.config 文件 - 以避免来自 IIS 的 413 错误。 I have also edited the list of allowed file extensions to support.pdf and.docx and.mp4.我还编辑了允许的文件扩展名列表以支持.pdf 和.docx 和.mp4。

When I try to run the sample project, and I choose the "Stream a file with AJAX to a controller endpoint" under the "Physical Storage Upload Examples" section, it dies here:当我尝试运行示例项目时,我选择了“物理存储上传示例”部分下的“使用 AJAX 将文件流式传输到 controller 端点”,它在这里消失了:

                // 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);
                }

As is mentioned in the comments above the code, it's checking for form data and then when it finds it... it dies.正如代码上面的注释中提到的那样,它正在检查表单数据,然后当它找到它时......它就死了。 So i've been playing around with the HTML page which looked like this:所以我一直在玩 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>

And I've tried to remove the form like this:我试图删除这样的表格:

<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>

But the button doesn't do anything now when I click it.但是当我单击它时,该按钮现在什么也不做。

Also, in case you're wondering / it helps, I manually copied in a file into the c:\files folder on my computer and when the sample app opens, it does list the file - proving it can read the folder.另外,如果您想知道/它有帮助,我手动将文件复制到我计算机上的 c:\files 文件夹中,当示例应用程序打开时,它确实列出了文件 - 证明它可以读取文件夹。 I added read /write permissions so hopefully the web app can write to it when I get that far.我添加了读/写权限,因此希望 web 应用程序可以在我到达那一步时对其进行写入。

I've implemented a similar large file controller but using mongoDB GridFS.我已经实现了一个类似的大文件 controller 但使用 mongoDB GridFS。 It's an amazing software.这是一个了不起的软件。

Back to your questions:回到你的问题:

The entire file is read into an IFormFile, which is a C# representation of the file used to process or save the file.整个文件被读入 IFormFile,它是用于处理或保存文件的文件的 C# 表示。

The resources (disk, memory) used by file uploads depend on the number and size of concurrent file uploads.文件上传使用的资源(磁盘、内存)取决于并发文件上传的数量和大小。 If an app attempts to buffer too many uploads, the site crashes when it runs out of memory or disk space.如果应用程序尝试缓冲过多的上传内容,则站点会在 memory 或磁盘空间不足时崩溃。 If the size or frequency of file uploads is exhausting app resources, use streaming.如果文件上传的大小或频率耗尽了应用程序资源,请使用流式传输。

source 资源

Example 1:示例 1:

[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 = ContentDispositionHeaderValue.Parse(file.ContentDisposition).FileName.Trim('"');     
  var extension = Path.GetExtension(fileName);
      
  var localPath = $"{Path.Combine(System.AppContext.BaseDirectory, "myCustomDir")}\\{fileName}.{extension}";
  
  // Create if not exists
  Directory.CreateDirectory(Path.Combine(System.AppContext.BaseDirectory, "myCustomDir"));
  
  using (var stream = new FileStream(localPath, FileMode.Create)){
    file.CopyTo(stream);
  }

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

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

Example 2 with GridFS:使用 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;
    var bsonId = await mongo.GridFs.UploadFromStreamAsync(file.FileName, stream, options);    
  }

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

You are on the right path, but as others have pointed out Microsoft have put up a well written document on file uploading which is a must read in your situation - https://docs.microsoft.com/en-us/aspnet/core/mvc/models/file-uploads?view=aspnetcore-6.0#upload-large-files-with-streaming .您走在正确的道路上,但正如其他人指出的那样,微软已经提供了一份关于文件上传的写得很好的文档,在您的情况下必须阅读 - https://docs.microsoft.com/en-us/aspnet/core /mvc/models/file-uploads?view=aspnetcore-6.0#upload-large-files-with-streaming

As for your questions至于你的问题

  • do you need services.Configure<FormOptions>(x =>你需要services.Configure<FormOptions>(x =>

    No you don't!不,你没有! And you don't need services.Configure<IISServerOptions>(options => either, its read from the maxAllowedContentLength that you have configured in your web.config而且您也不需要services.Configure<IISServerOptions>(options => ,它从您在 web.config 中配置的maxAllowedContentLength中读取

  • When the user picks the file... what is actually happening under the hood?当用户选择文件时……幕后实际发生了什么? Has the file actually been stuffed into my form and is accessible from my controller?, Is it a stream?该文件是否实际上已填充到我的表单中并且可以从我的 controller 访问?是 stream 吗?

    If you disable the form value model binding and use the MultipartReader the file is streamed and won't be cached into memory or disk, as you drain the stream, more data will be accepted from the client(the browser)如果您禁用表单值 model 绑定并使用 MultipartReader 文件将被流式传输,并且不会缓存到 memory 或磁盘中,因为您耗尽了 ZF7B44CFAFD5C52223D5498196C8A2E7 客户端的数据(B 浏览器将接受更多数据)

  • how do i get to the file?我如何获取文件?

    Check the document above, there is a working sample for accessing the stream.检查上面的文档,有一个用于访问 stream 的工作示例。

  • If ultimately, I need to send this file to Sharepoint using this type of an approach (the last example on chunking), it seems that the best approach is to save the file on my server somewhere... and then copy the sample code and try to chunk it out?如果最终,我需要使用这种方法(关于分块的最后一个示例)将此文件发送到 Sharepoint,似乎最好的方法是将文件保存在我的服务器上的某个地方......然后复制示例代码并试着把它分块? The sample code seems to be referring to file paths and file sizes, I'm assuming I need to persist it to my web server somewhere first, and then take it from there.示例代码似乎是指文件路径和文件大小,我假设我需要先将它保存到我的 web 服务器的某个地方,然后从那里获取它。

    Not necessarily, using the streaming approach you can copy the stream data directly.不一定,使用流式方法可以直接复制 stream 数据。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM