Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
1 task done
ashishdhingra opened this issue Oct 21, 2024 · 0 comments
Labels
area-networking Includes servers, yarp, json patch, bedrock, websockets, http client factory, and http abstractions

Comments

@ashishdhingra
Copy link

ashishdhingra commented Oct 21, 2024

Is there an existing issue for this?

  • 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 class from Microsoft.AspNetCore.WebUtilities 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 aws/aws-sdk-net#3201 (comment)).

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 aws/aws-sdk-net#3201 (comment) 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 aws/aws-sdk-net#3201 (comment)).

  • Create a ASP.NET Core Razor App project targeting .NET 8.0.
    .csproj
    <Project Sdk="Microsoft.NET.Sdk.Web">
    
      <PropertyGroup>
        <TargetFramework>net8.0</TargetFramework>
        <Nullable>enable</Nullable>
        <ImplicitUsings>enable</ImplicitUsings>
      </PropertyGroup>
    
    </Project>
    
  • Modify Pages\Index.cshtml.cs with the below code:
    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<IndexModel> _logger;
            private readonly int _bufferSize = 64 * 1024;
    
            public IndexModel(ILogger<IndexModel> logger)
            {
                _logger = logger;
            }
    
            public void OnGet()
            {
    
            }
    
            public async Task<IActionResult> 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<long?> 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

@dotnet-issue-labeler dotnet-issue-labeler bot added the area-networking Includes servers, yarp, json patch, bedrock, websockets, http client factory, and http abstractions label Oct 21, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-networking Includes servers, yarp, json patch, bedrock, websockets, http client factory, and http abstractions
Projects
None yet
Development

No branches or pull requests

1 participant