billbogaiv/hybrid-model-binding

IFormFile binding

artyom-p opened this issue · 6 comments

Is it possible to bind IFormFile to a model when posting a form with multiple fields and one or more files?

Currently I am able to bind only form key/values with HybridModelBinding.
Binding works for Id property but not for file

    public class SetImageCommand
    {
        [HybridBindProperty(Source.Form, "UniqueName")]
        public string Id { get; set; }

        [HybridBindProperty(Source.Form, "MyImage")]
        public IFormFile Image { get; set; }
    }

Workaround:
public async Task<ActionResult> SetImage([FromForm(Name = "UniqueName")]string id, [FromForm(Name = "MyImage")]IFormFile image)

Updating to https://www.nuget.org/packages/HybridModelBinding/0.16.0 should fix your issue. You will also be able to bind to a collection of files:

    public class SetImageCommand
    {
        [HybridBindProperty(Source.Form, "UniqueName")]
        public string Id { get; set; }

        [HybridBindProperty(Source.Form, "MyImages")]
        public IEnumerable<IFormFile> Images { get; set; }
    }

The approach I implemented is a bit brittle since it uses reflection to get the collection of files from the FormValueProvider–which via FormCollection does not directly expose bound-files. I may take a look at a different approach in the future and don't anticipate it affecting the end-user usage.

Perfect, thanks!

Hi @billbogaiv,

I saw the update on your package and this is exactly what I'm searching for but I can't get it to work... If I use a parameter IEnumerable<IFormFile> documents in my action, it works.

Do you see anything wrong with this setup?

// xunit integration test
[Fact]
public async Task Upload_SavesPhotoAndReturnSuccess()
{
    // Arrange
    var fileFakerHttpClient = new HttpClient();
    var expectedContentType = "text/html; charset=utf-8";
    var url = $"api/foos/{_foo1Id}/documents/add";

    HttpResponseMessage response = null;
    try
    {
        // Act
        using (var file1 = await fileFakerHttpClient.GetAsync(faker.Image.PicsumUrl()))
        using (var content1 = new StreamContent(await file1.Content.ReadAsStreamAsync()))
        using (var file2 = await fileFakerHttpClient.GetAsync(faker.Image.PicsumUrl()))
        using (var content2 = new StreamContent(await file2.Content.ReadAsStreamAsync()))
        using (var formData = new MultipartFormDataContent())
        {
            // Add file (file, field name, file name)
            formData.Add(content1, "Documents", file1.Content.Headers.ContentDisposition.FileName);
            formData.Add(content2, "Documents", file2.Content.Headers.ContentDisposition.FileName);

            response = await _httpClient.PostAsync(url, formData);
        }

        // Assert
        response.EnsureSuccessStatusCode();
        var responseString = await response.Content.ReadAsStringAsync();

        responseString.ShouldNotBeEmpty();
        expectedContentType.ShouldBe(response.Content.Headers.ContentType.ToString());
    }
    finally
    {
        fileFakerHttpClient.Dispose();
        response.Dispose();
        _httpClient.Dispose();
    }
}

// model. In FooBaseDto there only more route parameters that I removed for clarity. Those parameters work
public class AddDocumentByFooDto : FooBaseDto
{
    [HybridBindProperty(Source.Route)]
    public Guid FooId { get; set; }

    [HybridBindProperty(Source.Form, "Documents")]
    public IEnumerable<IFormFile> Documents { get; set; }
}

// Action where model.Documents is always null
[HttpPost("api/foos/{fooId}/documents/add")]
public async Task<IActionResult> AddDocumentByFoo([FromHybrid]AddDocumentByCaseDto model, CancellationToken cancellationToken)

Can you setup a Postman request similar to the following and see if model.Documents gets the files? I don't see anything immediately wrong in your setup, but haven't tried running a test like that before.

Screenshot of Postman request

I don't know what went wrong. Everything is working now without a code change... Your postman worked as well. Thanks.