dotnet / dotnet/aspnetcore

Microsoft.AspNetCore.WebUtilities: FileMultipartSection.FileStream.Length is missing value initially

Open
#58,555 0 comments 0 reactions 0 assignees View on GitHub
area-networking
Dominant language
C#
Stars
38.4k
Forks
10.9k
Avg merge
2d 6h
Merged PRs (30d)
290

Description

### Is there an existing issue for this?

- [x] I have searched the existing issues

### Describe the bug

Hi, I work with AWS SDK for .NET team. Customer reported an issue in our GitHub repository where exception `System.Net.Http.HttpRequestException: Unable to write content to request stream; content would exceed Content-Length.` when using our package.

In our AWS SDK for .NET package, we inspect `Stream.Length` property to determine, if for large files we should use multi-part upload (which is basically equivalent to uploading stream/file in chunks).

In customer's scenario, they are using [MultipartSection](https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.webutilities.multipartsection?view=aspnetcore-8.0) class from [Microsoft.AspNetCore.WebUtilities](https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.webutilities?view=aspnetcore-8.0) namespace, which is used to process the uploaded form file via HTTP POST (customer is using VS Code `Thunder Client` extension to invoke the Web API in the code sample attached in https://github.com/aws/aws-sdk-net/issues/3201#issuecomment-2011362833).

During our investigation, we found that in `Microsoft.AspNetCore.WebUtilities` package targeting **.NET 7.0** and **.NET 8.0**, `FileMultipartSection.FileStream.Length` could be missing value initially (in **.NET 6.0**, `fileSection.FileStream.Length` always has the value). Kindly refer https://github.com/aws/aws-sdk-net/issues/3201#issuecomment-2013727254 for more details.

Please advise if this could be fixed in `Microsoft.AspNetCore.WebUtilities` package.

### Expected Behavior

`FileMultipartSection.FileStream.Length` should always have a value when using `Microsoft.AspNetCore.WebUtilities` package targeting **.NET 7.0** and **.NET 8.0** (similar to **.NET 6.0** target).

### Steps To Reproduce

Below is the simple reproduction (extracted from sample project attached in https://github.com/aws/aws-sdk-net/issues/3201#issuecomment-2011362833).
- Create a ASP.NET Core Razor App project targeting `.NET 8.0`.
**.csproj**
```


net8.0
enable
enable


```
- Modify **Pages\Index.cshtml.cs** with the below code:
```C#
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.Primitives;
using Microsoft.Net.Http.Headers;
using System.ComponentModel.DataAnnotations;
using System.Threading;

namespace TestWebApp.Pages
{
[IgnoreAntiforgeryToken()]
public class IndexModel : PageModel
{
private readonly ILogger _logger;
private readonly int _bufferSize = 64 * 1024;

public IndexModel(ILogger logger)
{
_logger = logger;
}

public void OnGet()
{

}

public async Task OnPost()
{
if (!IsMultipartContentType(Request))
{
return BadRequest($"Expected a multipart request, but got {Request.ContentType}");
}

try
{
var boundary = GetBoundary(Request);
var reader = new MultipartReader(boundary, Request.Body, _bufferSize);
MultipartSection section;
List fileLengths = new();
while (reader != null && (section = await reader.ReadNextSectionAsync()) != null)
{
var contentDisposition = section.GetContentDispositionHeader();
if (contentDisposition != null && contentDisposition.IsFileDisposition())
{
var fileSection = section.AsFileSection();
_logger.LogInformation("Received file to upload: {0}", fileSection?.FileName);

_logger.LogDebug("FileStream Length: {0}", fileSection?.FileStream?.Length);
fileLengths.Add(fileSection?.FileStream?.Length);
}
}

return Content(string.Join(',', fileLengths.Select(l => l.ToString() ?? "NULL")));
}
catch (Exception ex)
{
_logger.LogError(ex, "Got exception on POST.");
return BadRequest(ex.ToString());
}
}

private bool IsMultipartContentType(HttpRequest request)
{
return !string.IsNullOrEmpty(request.ContentType)
&& request.ContentType.IndexOf("multipart/", StringComparison.OrdinalIgnoreCase) >= 0;
}

private static string GetBoundary(HttpRequest request)
{
if (request == null || request.ContentType == null)
throw new ArgumentNullException(nameof(request));

var elements = request.ContentType.Split(' ');
var element = elements.First(entry => entry.StartsWith("boundary="));
var boundary = element.Substring("boundary=".Length);

boundary = HeaderUtilities.RemoveQuotes(new StringSegment(boundary)).ToString();

return boundary;
}
}
}
```
- Fire a `POST` request with file upload using any tool like Postman.

**RESULT:**
`fileSection?.FileStream?.Length` would be `0`.
Executing `fileSection.FileStream.CopyTo(new MemoryStream())` in Immediate Window populates the `Length` property.

### Exceptions (if any)

_No response_

### .NET Version

_No response_

### Anything else?

_No response_

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.