Search code examples
c#.net-coremultipartform-data

dotnet core get dictionary from form data


How do I get a Dictionary<string, string> from form-data?

View model:

public class SubmitRequestModel
{
  public Dictionary<string, string> InputFields { get; set; }
 
  public List<IFormFile> Attachments { get; set; }
}

Action:

[HttpPut("{id:int}")]
public async Task<IActionResult> Submit(int id, [FromForm] SubmitRequestModel model)
{
  // model.InputFields is initialized but its count is 0
  // do whatever
}

This is an API controller. Not cshtml or razor related.

So the model.InputFields is not null but it's count is 0. When I look at the raw request. I can see that the input is received, but it is not bound to the dictionary in the model.

The values of the Request.Form collection: https://prnt.sc/11x532p

I need to use form data because we are uploading files with the request. This requires multipart/form-data.

How do I successfully parse the data to the model?


Info on how I tested this:

I have swagger set-up. I import the generated Swagger OpenAPI json in to Postman and test this way. I believe that this should be the correct request, that Swagger generated. But I'm not sure if it formatted the dictionary the right way. It would be the right way for a JSON data request. But I'm not sure if that applicates here.


Solution

  • You can achieve this with custom IModelBinder:

    public class DictionaryBinder<TKey, TValue> : IModelBinder
    {
        public Task BindModelAsync(ModelBindingContext bindingContext)
        {
            if (bindingContext == null)
            {
                throw new ArgumentNullException(nameof(bindingContext));
            }
    
            if (bindingContext.HttpContext.Request.HasFormContentType)
            {
                var form = bindingContext.HttpContext.Request.Form;
                var data = JsonSerializer.Deserialize<Dictionary<TKey, TValue>>(form[bindingContext.FieldName]);
                bindingContext.Result = ModelBindingResult.Success(data);
            }
    
            return Task.CompletedTask;
        }
    }
    

    The model class will become:

    public class SubmitRequestModel
    {
        [FromForm]
        [ModelBinder(BinderType = typeof(DictionaryBinder<string, string>))]
        public Dictionary<string, string> InputFields { get; set; }
    
        public List<IFormFile> Attachments { get; set; }
    }