Skip to content

C# Client Posting Multipart/Form-Data To C# API Endpoint Throws InvalidDataException #10503

@ideoclickVanessa

Description

@ideoclickVanessa

Describe the bug

I have a C# API endpoint that accepts files for processing, defined like so:

//POST api/Ticket/Attachment
[HttpPost("Attachment")]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<IActionResult> PostAttachment(int ticketId, [FromForm]IFormFileCollection files)

When I call this endpoint from Postman, the endpoint successfully receives / processes the files.
However, I receive an InvalidDataException: Multipart body length limit 16384 exceeded. exception when I call the endpoint from a C# client using this code:

public async Task PostAttachmentAsync(int ticketID, IFormFileCollection files, CancellationToken cancellationToken = default)
{
	using (HttpRequestMessage request = new HttpRequestMessage())
	{
		string boundary = $"{Guid.NewGuid().ToString()}";
		MultipartFormDataContent requestContent = new MultipartFormDataContent(boundary);
		requestContent.Headers.Remove("Content-Type");
		// The two dashes in front of the boundary are important as the framework includes them when serializing the request content.
		requestContent.Headers.TryAddWithoutValidation("Content-Type", $"multipart/form-data; boundary=--{boundary}");

		foreach (IFormFile file in files)
		{
			StreamContent streamContent = new StreamContent(file.OpenReadStream());
			requestContent.Add(streamContent, file.Name, file.FileName);
			streamContent.Headers.ContentDisposition.FileNameStar = "";
		}

		request.Content = requestContent;
		request.Method = new HttpMethod("POST");
		request.RequestUri = new Uri($"api/v3/Services/WorkItem/Ticket/Attachment?ticketID={ticketID}", UriKind.Relative);

		HttpClient client = await CreateHttpClientAsync(cancellationToken).ConfigureAwait(false);

		using (HttpResponseMessage response =
			await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false)
		)
		{
			if (response == null)
				throw new PlatformAPIClientException("Could not retrieve a response.");

			Dictionary<string, IEnumerable<string>> responseHeaders = Enumerable.ToDictionary(response.Headers, h => h.Key, h => h.Value);
			if (response.Content?.Headers != null)
			{
				Dictionary<string, IEnumerable<string>> contentHeaders = Enumerable.ToDictionary(response.Content.Headers, h => h.Key, h => h.Value);
				responseHeaders.Concat(contentHeaders);
			}

			int responseStatusCode = (int)response.StatusCode;
			switch (responseStatusCode)
			{
				case StatusCodes.Status200OK:
				case StatusCodes.Status204NoContent:
					return;

				case StatusCodes.Status400BadRequest:
					string badRequestResponse = response.Content == null ? "No Response Content" : 
						await response.Content.ReadAsStringAsync().ConfigureAwait(false);
					ValidationProblemDetails validationResult = default;
					try
					{
						validationResult = JsonConvert.DeserializeObject<ValidationProblemDetails>(badRequestResponse, _settings.Value);
					}
					catch(Exception ex)
					{
						throw new PlatformAPIClientException("Could not deserialize the response body.", responseStatusCode, badRequestResponse, 
							responseHeaders, ex);
					}
					throw new PlatformAPIClientException<ValidationProblemDetails>("A server side error occurred.", responseStatusCode, 
						badRequestResponse, responseHeaders, validationResult, innerException: null);

				case StatusCodes.Status401Unauthorized:
				case StatusCodes.Status403Forbidden:
				case StatusCodes.Status500InternalServerError:
					string errorResponse = response.Content == null ? "No Response Content" : 
						await response.Content.ReadAsStringAsync().ConfigureAwait(false);
					ProblemDetails problemResult = default;
					try
					{
						problemResult = JsonConvert.DeserializeObject<ProblemDetails>(errorResponse, _settings.Value);
					}
					catch(Exception ex)
					{
						throw new PlatformAPIClientException("Could not deserialize the response body.", responseStatusCode, errorResponse, 
							responseHeaders, ex);
					}
					throw new PlatformAPIClientException<ProblemDetails>("A server side error occurred.", responseStatusCode,
						errorResponse, responseHeaders, problemResult, innerException: null);

				default:
					string unexpectedResponse = response.Content == null ? "No Response Content" :
						await response.Content.ReadAsStringAsync().ConfigureAwait(false);
					throw new PlatformAPIClientException($"The HTTP status code ({responseStatusCode}) of the response was not expected.",
						responseStatusCode, unexpectedResponse, responseHeaders, innerException: null);
			}
		}
	}
}

Google / Stack Overflow suggest that the exception should be resolved by using either the attribute [RequestFormLimits(MultipartBodyLengthLimit = long.MaxValue)] or configured globally in Startup:

services.Configure<FormOptions>(options =>
{
	options.MultipartBodyLengthLimit = long.MaxValue;
});

However, neither suggested solution prevents the InvalidDataException from being thrown when the endpoint is called by the C# client.

Can you please provide guidance as to whether this is a bug in the framework or my C# client code?

To Reproduce

Steps to reproduce the behavior:

  1. Using this version of ASP.NET Core '2.2'

Expected behavior

The C# client successfully posts the request without triggering the InvalidDataException.

Additional context

dotnet-info.txt
testfile.jpg
successful-postman-request.txt
failed-csharp-request.txt

Metadata

Metadata

Assignees

Labels

area-mvcIncludes: MVC, Actions and Controllers, Localization, CORS, most templatesinvestigate

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions