0

I just migrated an ASP.NET WebAPI to ASP.NET Core 2.1 (I also tried 2.2). It contains a file upload route which receives a multipart request a binary file, with a known key / name.

My issue is that request.Form.Files collection is empty. The binary content is received as a normal form value (which only shows weird characters when parsed).

My understanding is that the clients' implementation is wrong. They are however mobile applications, so I have to stay backwards compatible. This is basically how the client is sending the file:

var client = new HttpClient();
var content = new MultipartFormDataContent();
content.Add(new ByteArrayContent(File.ReadAllBytes("someimage.jpg")), "file");
await client.PutAsync("https://myapi/api/document", content);

The old ASP.NET Implementation parsed it like this (some parts removed):

var provider = new MultipartMemoryStreamProvider();
await request.Content.ReadAsMultipartAsync(provider);

Stream file = null;
foreach (var contentPart in provider.Contents)
{
    if (partName.Equals("file", StringComparison.OrdinalIgnoreCase))
    {
        file = await contentPart.ReadAsStreamAsync();
    }
}

In ASP.NET Core, file/form parsing is built in and MultipartMemoryStreamProvider no longer exists, so this is what I implemented:

public async Task<IActionResult> Put(IFormFileCollection files) // files is empty list
public async Task<IActionResult> Put(IFormFile file) // file is null

// ...

var formFile = request.Form.Files.GetFile("file"); 
// formFile is null
// requests.Form.Files is empty
Stream file = formFile.OpenReadStream();

The file can be retrieved via request.Form["file"], but its content is displayed as {����. No idea if I can get that back to my binary content. cryptic string content

I tried this code, but the file cannot be opened afterwards.

var fff = request.Form["file"];
using (var stream = System.IO.File.OpenWrite("out.jpg"))
using (StreamWriter sw = new StreamWriter(stream))
{
    sw.Write(fff);
}
Alex AIT
  • 17,361
  • 3
  • 36
  • 73
  • Does this help you? Potential duplicate: https://stackoverflow.com/questions/35379309/how-to-upload-files-in-asp-net-core – AndreasHassing Apr 07 '20 at 06:56
  • Thank you, but I am talking about the API endpoint that is receiving the files, not the uploader. I have improved the title to make this more clear. – Alex AIT Apr 07 '20 at 06:58
  • That is covered in the answer on the referenced question (assuming that you use `IFormFile`). – AndreasHassing Apr 07 '20 at 06:59
  • Only indirectly. But I had a look at it: `IFormFile file` is also `null` in my case if I put it as an action parameter, so that fails the same way unfortunately. `IFormFileCollection ...` is also always null. – Alex AIT Apr 07 '20 at 07:03
  • Do you perhaps need to set the `[FromForm]` attribute on your `file` parameter? – AndreasHassing Apr 07 '20 at 07:07
  • Tried `[FromForm(Name = "file")] IFormFile file`, and `[FromForm] IFormFile file` without success. – Alex AIT Apr 07 '20 at 07:17

1 Answers1

0

I was able to fix the issue by using MultipartReader instead of the old MultipartMemoryStreamProvider. The Microsoft docs and the corresponding GitHub sample helped a lot.

This is a stripped-down version of my final code. It is by no means a "general file upload" endpoint, see the example linked above if you need something that can handle more different scenarios.



[DisableFormValueModelBinding]
public async Task<IActionResult> Put(CancellationToken cancellationToken)
{

Stream fileStream = null;

var boundary = MultipartRequestHelper.GetBoundary(MediaTypeHeaderValue.Parse(Request.ContentType), new FormOptions().MultipartBoundaryLengthLimit);
var reader = new MultipartReader(boundary, Request.Body);

var section = await reader.ReadNextSectionAsync();

while (section != null)
{
    var hasContentDispositionHeader =
        ContentDispositionHeaderValue.TryParse(
            section.ContentDisposition, out var contentDisposition);

    if (hasContentDispositionHeader)
    {
        if (contentDisposition.Name.Equals("file", StringComparison.OrdinalIgnoreCase))
        {
            fileStream = new MemoryStream();
            await section.Body.CopyToAsync(fileStream);
            fileStream.Position = 0;
        }
    }
    section = await reader.ReadNextSectionAsync();
}
    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
    public class DisableFormValueModelBindingAttribute : Attribute, IResourceFilter
    {
        public void OnResourceExecuting(ResourceExecutingContext context)
        {
            var factories = context.ValueProviderFactories;
            factories.RemoveType<FormValueProviderFactory>();
            factories.RemoveType<JQueryFormValueProviderFactory>();
        }

        public void OnResourceExecuted(ResourceExecutedContext context)
        {
        }
    }

Alex AIT
  • 17,361
  • 3
  • 36
  • 73