So I have written a simple Azure Function (AF) that accepts (via Http Post method) an IFormCollection, loops through the file collection, pushes each file into an Azure Blob storage container and returns the url to each file.
The function itself works perfectly when I do a single file or multiple file post through Postman using the ‘multipart/form-data’ header. However when I try to post a file through an xUnit test, I get the following error:
System.IO.InvalidDataException : Multipart body length limit 16384 exceeded.
I have searched high and low for a solution, tried different things, namely;
- Replicating the request object to be as close as possible to Postmans request.
- Playing around with the ‘boundary’ in the header.
- Setting ‘RequestFormLimits’ on the function.
None of these have helped so far.
The details are the project are as follows:
Azure Function v3: targeting .netcoreapp3.1
Startup.cs
public class Startup : FunctionsStartup { public IConfiguration Configuration { get; private set; } public override void Configure(IFunctionsHostBuilder builder) { var x = builder; InitializeConfiguration(builder); builder.Services.AddSingleton(Configuration.Get<UploadImagesAppSettings>()); builder.Services.AddLogging(); builder.Services.AddSingleton<IBlobService,BlobService>(); } private void InitializeConfiguration(IFunctionsHostBuilder builder) { var executionContextOptions = builder .Services .BuildServiceProvider() .GetService<IOptions<ExecutionContextOptions>>() .Value; Configuration = new ConfigurationBuilder() .SetBasePath(executionContextOptions.AppDirectory) .AddJsonFile("appsettings.json") .AddJsonFile("appsettings.Development.json", optional: true) .AddEnvironmentVariables() .Build(); } }
UploadImages.cs
public class UploadImages { private readonly IBlobService BlobService; public UploadImages(IBlobService blobService) { BlobService = blobService; } [FunctionName("UploadImages")] [RequestFormLimits(ValueLengthLimit = int.MaxValue, MultipartBodyLengthLimit = 60000000, ValueCountLimit = 10)] public async Task<IActionResult> Run( [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = "images")] HttpRequest req) { List<Uri> returnUris = new List<Uri>(); if (req.ContentLength == 0) { string badResponseMessage ="{file.FileName} is not a valid/accepted Image file"; return new BadRequestObjectResult(badResponseMessage); } var uri = await BlobService.CreateBlobAsync(file); if (uri == null) { return new ObjectResult(
"----------------------------{DateTime.Now.Ticks.ToString("x")}" }; var boundary = MultipartRequestHelper.GetBoundary( contentTypeWithBoundary, (int)body.Length); request.Method = method; request.Headers.Add("Cache-Control", "no-cache"); request.Headers.Add("Content-Type", contentType); request.ContentType =
"Multipart boundary length limit {lengthLimit} exceeded."); } return boundary.Value; } }
The MultipartRequestHelper.cs class is available here
And Finally the Test class:
[Collection(TestCollection.Name)] public class UploadImagesTests { readonly UploadImages UploadImagesFunction; public UploadImagesTests(TestHost testHost) { UploadImagesFunction = new UploadImages(testHost.ServiceProvider.GetRequiredService<IBlobService>()); } [Theory] [InlineData("testfile2.jpg")] public async void HttpTrigger_ShouldReturnListOfUploadedUris(string fileNames) { var formFile = GetFormFile(fileNames); var fileStream = formFile.OpenReadStream(); var request = HttpRequestFactory.Create("POST", "multipart/form-data", fileStream); var response = (OkObjectResult)await UploadImagesFunction.Run(request); //fileStream.Close(); Assert.True(response.StatusCode == StatusCodes.Status200OK); } private static IFormFile GetFormFile(string fileName) { string fileExtension = fileName.Substring(fileName.IndexOf('.') + 1); string fileNameandPath = GetFilePathWithName(fileName); IFormFile formFile; var stream = File.OpenRead(fileNameandPath); switch (fileExtension) { case "jpg": formFile = new FormFile(stream, 0, stream.Length, fileName.Substring(0, fileName.IndexOf('.')), fileName) { Headers = new HeaderDictionary(), ContentType = "image/jpeg" }; break; case "png": formFile = new FormFile(stream, 0, stream.Length, fileName.Substring(0, fileName.IndexOf('.')), fileName) { Headers = new HeaderDictionary(), ContentType = "image/png" }; break; case "pdf": formFile = new FormFile(stream, 0, stream.Length, fileName.Substring(0, fileName.IndexOf('.')), fileName) { Headers = new HeaderDictionary(), ContentType = "application/pdf" }; break; default: formFile = null; break; } return formFile; } private static string GetFilePathWithName(string filename) { var outputFolder = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location); return $"{outputFolder.Substring(0, outputFolder.IndexOf("bin"))}testfiles\{filename}"; } }
The test seems to be hitting the function and req.ContentLength does have a value. Considering this, could it have something to do with the way the File Streams are being managed? Perhaps not the right way?
Any inputs on this would be greatly appreciated.
Thanks
Recent Comments