【问题标题】:asp.net core 3.1 streaming large file upload - can't prevent model bindingasp.net core 3.1 流式大文件上传 - 无法阻止模型绑定
【发布时间】:2020-10-23 05:25:31
【问题描述】:

=============== 更新 ===============

很抱歉,但在进行了所有的修改和更改、注释掉并重新输入,以尝试使其正常工作,我不小心停止了在 Create.cshtml 中调用脚本的表单。 View文件的形式实际上应该是这样的:

<form asp-controller="Documents" asp-action="Create" method="post" enctype="multipart/form-data" onSubmit="AJAXSubmit(this); return false;">

我还没有结束这个问题,因为Create.cshtml 中的脚本正在接收 400(错误请求)。这似乎是真正的问题 - 但是,我不明白为什么会这样。

如果我深入了解它,我会更新问题。希望这对某人来说是一个有用的答案。

=============== 结束更新 ===============

我一直关注https://docs.microsoft.com/en-us/aspnet/core/mvc/models/file-uploads?view=aspnetcore-3.1#upload-large-files-with-streaming 将大文件上传到网络应用程序。不幸的是,当我开始使用 MultipartReader (reader.ReadNextSectionAsync) 时,我收到了错误 unexpected end of stream, the content may have already been read by another component

我已经下载了示例应用 (https://github.com/dotnet/AspNetCore.Docs/tree/master/aspnetcore/mvc/models/file-uploads/samples/3.x/SampleApp) 并且运行良好,但他们使用的是 Razor 页面,而我的项目不是。

一个显着的区别:当我从 github 运行演示应用程序时,当我单击 Upload 以上传大文件时,会立即调用 Controller 方法。但是,当我在我的应用程序上单击上传文档时,浏览器会显示正在上传的文件,然后调用 Controller 方法。

我是 ASP.NET 的新手,几天来我一直在用头撞砖墙,因此非常感谢任何帮助。

我的相关代码sn-ps如下:

GenerateAntiforgeryTokenCookieAttribute:将防伪令牌添加到 cookie,完全从示例文档中复制

DisableFormValueModelBindingAttribute:防止表单绑定的属性类,完全复制自示例文档

MultipartRequestHelper:完全从示例文档中复制

startup.cs:这也许是我的问题所在,因为我的项目没有使用 Razor 页面,而是使用功能文件夹,所以我偏离了示例。另外,我的项目正在使用功能文件夹。

public void ConfigureServices(IServiceCollection services)
{
...
  services.AddMvc(o => o.Conventions.Add(new FeatureConvention()))
// first part is for feature folders
    .AddRazorOptions(options =>
    {
      options.ViewLocationFormats.Clear();
      options.ViewLocationFormats.Add("/Features/{3}/{1}/{0}.cshtml");
      options.ViewLocationFormats.Add("/Features/{3}/{0}.cshtml");
      options.ViewLocationFormats.Add("/Features/Shared/{0}.cshtml");
      options.ViewLocationExpanders.Add(new FeatureViewLocationExpander());
    })
// FeatureConvention and FeatureViewLocationExpander taken from https://docs.microsoft.com/en-us/archive/msdn-magazine/2016/september/asp-net-core-feature-slices-for-asp-net-core-mvc#feature-folders-in-aspnet-core-mvc
// next part is to prevent form value binding and to add the anti forgery token to the cookie
    .AddRazorPagesOptions(options =>
    {
      options.Conventions.AddPageApplicationModelConvention(
        "/Documents/Create",
        model =>
        {
          model.Filters.Add(new GenerateAntiForgeryTokenCookieAttribute());
          model.Filters.Add(new DisableFormValueModelBindingAttribute());
        }
      );
    });
...
}

我的文件夹结构如下:

root
|
`-- Features
    |
    `-- Documents
        |
        `-- Create.cshtml
        `-- DocumentsController.cs

创建.cshtml

@model DocumentCreateViewModel

<form asp-controller="Documents" asp-action="Create" method="post" enctype="multipart/form-data">
  <div>
    <label asp-for="filename"></label>
    <input asp-for="filename""><input/>
  </div>
  <div>
    <label asp-for="file"></label>
    <input asp-for="file"><input/>
  </div>
  <div>
    <button type="submit">submit</button>
  </div>
</form>

@section Scripts {
  <script>
    "use strict";
    async function AJAXSubmit (oFormElement) {
      const formData = new FormData(oFormElement);
      try {
        const response = await fetch(oFormElement.action, {
          method: 'POST',
          headers: {
            'RequestVerificationToken': getCookie('RequestVerificationToken')
          },
          body: formData
        });
        oFormElement.elements.namedItem("result").value = 
          'Result: ' + response.status + ' ' + response.statusText;
      } catch (error) {
        console.error('Error:', error);
      }
    }
    function getCookie(name) {
      var value = "; " + document.cookie;
      var parts = value.split("; " + name + "=");
      if (parts.length == 2) return parts.pop().split(";").shift();
    }
  </script>
}

DocumentCreateViewModel.cs

public class DocumentCreateViewModel
{
  [Required]
  public string Name { get; set; }

  [Required]
  public IFormFile File { get; set; }
}

DocumentsController.cs:是 UploadPhysical 的内容,完全从示例文档中复制而来,UploadPhysical 重命名为 Create

[HttpPost]
[DisableFormValueModelBinding]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create()
{
  if (!MultipartRequestHelper.IsMultipartContentType(Request.ContentType))
  {
    ModelState.AddModelError("File", 
      $"The request couldn't be processed (Error 1).");
      // Log error

      return BadRequest(ModelState);
  }

  var boundary = MultipartRequestHelper.GetBoundary(
      MediaTypeHeaderValue.Parse(Request.ContentType),
      _defaultFormOptions.MultipartBoundaryLengthLimit);
  var reader = new MultipartReader(boundary, HttpContext.Request.Body);
  var section = await reader.ReadNextSectionAsync();
// ^^^^ this is where the "unexpected end of stream" exception is thrown

【问题讨论】:

  • 根据您的查看代码,我发现您的 Create.cshtml 在发布表单数据时不使用 ajax。我建议您可以先尝试添加onsubmit="AJAXSubmit(this);return false;",然后再试一次。如果这不能解决您的问题,我建议您可以尝试将您的项目上传到 github 让我们测试。注意:请删除所有个人信息,只留下代码让我们重现问题。
  • 非常感谢您的回答 - 我已对问题添加了更新。我现在收到 400(错误请求),因此问题似乎出在表单发布(现在由 AJAX 完成)和控制器 DocumentsController 之间。我会看看我是否能到达 400 的底部,但如果不能,我会按照你的建议去做并发布到 GitHub。

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


【解决方案1】:

首先,感谢@Brando Zhang 让我重回正轨。

问题是 400(错误请求),是由于 DocumentsController 试图验证一个不存在的防伪令牌 - 这归因于我未能成功地将 Razor 示例移植到 MVC 中。

在示例代码中,startup.c 中,以下部分仅适用于 Razor 页面 - 不适用于 MVC:

.AddRazorPagesOptions(options =>
{
  options.Conventions.AddPageApplicationModelConvention(
    "/Documents/Create",
    model =>
    {
      model.Filters.Add(new GenerateAntiForgeryTokenCookieAttribute());
      model.Filters.Add(new DisableFormValueModelBindingAttribute());
    }
  );
});

因此,可以将其删除。但是,现在没有生成防伪令牌,所以当对DocumentsController 中的Create() 进行 POST 时,不存在令牌,因此返回 HTTP 400。

解决方案很简单(尽管我花了很长时间才找到它!):将[GenerateAntiForgeryTokenCookie] 添加到在DocumentsController 中呈现表单的GET 方法中:

[HttpGet]
[GenerateAntiForgeryTokenCookie]
public IActionResult Index()
{
  return View("Create");
}

[HttpPost]
[DisableFormValueModelBinding]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create()
{
  if (!MultipartRequestHelper.IsMultipartContentType(Request.ContentType))
...
}

希望对你有帮助!

【讨论】:

    猜你喜欢
    • 2021-05-27
    • 2013-05-06
    • 1970-01-01
    • 2022-02-01
    • 1970-01-01
    • 1970-01-01
    • 2020-05-23
    • 1970-01-01
    • 2020-07-19
    相关资源
    最近更新 更多