1

I'm building WebAPI & WebApp, both of them using ASP.NET Core 2.1

My Web App is trying to send post request to the Web API using ViewModel that contains IFormFile and other properties. I know I have to use MultipartFormDataContent to post IFormFile, but I don't know how to implement it with my ViewModel because my ViewModel has List of other model.

I already try to google some solutions, but I only found solutions with simple ViewModel without List like these :

https://stackoverflow.com/a/41511354/7906006

https://stackoverflow.com/a/55424886/7906006.


Is there any solution like

var multiContent = new MultipartFormDataContent();
var viewModelHttpContent= new StreamContent(viewModel);
MultiContent.Add(viewModelHttpContent, "viewModel");
var response = await client.PostAsJsonAsync("/some/url", multiContent);

so i don't have to add my property to MultipartFormDataContent one by one and post it as json.


Here's my Web App ViewModel

public class CreateDataViewModel
{
    public string PrimaryKeyNumber{ get; set; }

    public List<Currency> ListOfCurrency { get; set; }

    public IList<DataDetail> dataDetails { get; set; }

    [DataType(DataType.Upload)]
    public IFormFile Attachment { get; set; }

    //And other properties like Boolean, Datetime?, string
}

Here's my Web App controller

    [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> Create(CreateDataViewModel viewModel)
    {
        //How to implement MultipartFormDataContent with my ViewModel in here ?

        //My code below returns Could not create an instance of type Microsoft.AspNetCore.Http.IHeaderDictionary. Type is an interface or abstract class and cannot be instantiated. Path 'Attachment.Headers.Content-Disposition', line 1, position 723.
        //It works fine if I don't upload a file
        HttpResponseMessage res = await _client.PostAsJsonAsync<CreateDataViewModel>("api/data/create", viewModel);

        var result = res.Content.ReadAsStringAsync().Result;

        if (res.IsSuccessStatusCode)
        {
            TempData["FlashMessageSuccess"] = "Data have been submitted";
            return RedirectToAction("Index", "Home"); ;
        }


        //Code for error checking

    }

Here's my Web API controller that catches the post response using CreateDataViewModel as parameter.

[HttpPost]
[Route("[action]")]
public async Task<IActionResult> Create(CreateDataViewModel viewModel)
{
    //Code to validate then save the data
}

2 Answers 2

3

don't know how to implement it with my ViewModel because my ViewModel has List of other model

You can refer to following code snippet and implement a custom model binder to achieve your requirement.

var multipartContent = new MultipartFormDataContent();

multipartContent.Add(new StringContent(viewModel.PrimaryKeyNumber), "PrimaryKeyNumber");


multipartContent.Add(new StringContent(JsonConvert.SerializeObject(viewModel.ListOfCurrency)), "ListOfCurrency");
multipartContent.Add(new StringContent(JsonConvert.SerializeObject(viewModel.dataDetails)), "dataDetails");


multipartContent.Add(new StreamContent(viewModel.Attachment.OpenReadStream()), "Attachment", viewModel.Attachment.FileName);


var response = await client.PostAsync("url_here", multipartContent);

Implement a custom model binder to convert incoming request data

public Task BindModelAsync(ModelBindingContext bindingContext)
{
    if (bindingContext == null)
    {
        throw new ArgumentNullException(nameof(bindingContext));
    }
    // code logic here
    // ...


    // ...
    // fetch the value of the argument by name
    // and populate corresponding properties of your view model

    var model = new CreateDataViewModel()
    {
        PrimaryKeyNumber = bindingContext.ValueProvider.GetValue("PrimaryKeyNumber").FirstOrDefault(),
        ListOfCurrency = JsonConvert.DeserializeObject<List<Currency>>(bindingContext.ValueProvider.GetValue("ListOfCurrency").FirstOrDefault()),
        dataDetails = JsonConvert.DeserializeObject<List<DataDetail>>(bindingContext.ValueProvider.GetValue("dataDetails").FirstOrDefault()),
        Attachment = bindingContext.ActionContext.HttpContext.Request.Form.Files.FirstOrDefault()
    };

    bindingContext.Result = ModelBindingResult.Success(model);
    return Task.CompletedTask;
}

Apply it on API action method

public async Task<IActionResult> Create([ModelBinder(BinderType = typeof(CustomModelBinder))]CreateDataViewModel viewModel)

Test Result

enter image description here

Sign up to request clarification or add additional context in comments.

Comments

0

Below soltuion worked for me:-

var multiContent = new MultipartFormDataContent();

var viewModelHttpContent= new StreamContent(viewModel);

multiContent.Add(viewModelHttpContent, "viewModel");

multiContent.Add(new StreamContent(file.OpenReadStream()), "Attachment", file.FileName);

var request = new HttpRequestMessage(HttpMethod.Post, "/some/url") { Content = multiContent};

var response = await client.SendAsync(request);

At Api end:-

public async Task Upload([FromForm] CreateDataViewModel postRequest)

1 Comment

Hello, please see meta.stackoverflow.com/editing-help Thanks!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.