I'm having trouble implementing a custom model binder that can bind custom "form fields" to correct array indexes.
Here are my viewmodels:
public class NewOrderViewModel
{
public Guid DeliveryAddressId { get; set; }
public Guid BillingAddressId { get; set; }
public OrderItemViewModel[] OrderItems { get; set; }
}
public class OrderItemViewModel
{
public Guid ProductId { get; set; }
public int Quantity { get; set; }
public ProductInputFieldValue[] ProductInputFieldValues { get; set; }
}
And this is my post request send as form data:
DeliveryAddressId:44fbc2e3-07eb-47bc-810c-21dbc8d6924c
BillingAddressId:44fbc2e3-07eb-47bc-810c-21dbc8d6924c
OrderItems[0].ProductId:5124b7f1-8845-4c85-bacf-167dc53f187a
OrderItems[0].Quantity:1
OrderItems[0].file-to-read:44fbc2e3-07eb-47bc-810c-21dbc8d6924c
OrderItems[1].ProductId:5124b7f1-8845-4c85-bacf-167dc53f187a
OrderItems[1].Quantity:1
OrderItems[1].file-to-read:44fbc2e3-07eb-47bc-810c-21dbc8d6924c
"file-to-read" is just one example of a dynamic field that can be anything, and this is the reason for posting FORM data. I want to bind all fields that do not exist inside my ViewModels to bind into ProductInputFieldValue[]. Note that custom field can also be a file. All custom fields and files have to be assigned to the correct OrderItems index.
Here is the ModelBinder I'm trying to write:
public class ProductInputFieldValueModelBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null)
{
throw new ArgumentNullException(nameof(bindingContext));
}
// BindingSource seems to always be null here, but not the main issue now
if (bindingContext.BindingSource != BindingSource.Custom)
{
return Task.CompletedTask;
}
var inputFieldValues = new List<ProductInputFieldValue>();
if (bindingContext.HttpContext.Request.Form.Files.Count > 0)
{
var fileInputValues = BindFileInputs(bindingContext.HttpContext.Request.Form.Files);
inputFieldValues.AddRange(fileInputValues);
}
foreach (var key in bindingContext.HttpContext.Request.Form.Keys)
{
// Skip existing model fields
if (bindingContext.ModelState.ContainsKey(key))
{
continue;
}
var valueProviderResult = bindingContext.ValueProvider.GetValue(key);
bindingContext.ModelState.SetModelValue(key, valueProviderResult);
inputFieldValues.AddRange(valueProviderResult.Values.Select(value =>
new ProductInputFieldValue
{
Name = key,
Value = value
}));
}
bindingContext.Result = ModelBindingResult.Success(inputFieldValues);
return Task.CompletedTask;
}
ModelBinder is assigned with BinderProvider
public class ProductInputFieldValuesBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (context.Metadata.ModelType == typeof(List<ProductInputFieldValue>))
{
return new BinderTypeModelBinder(typeof(ProductInputFieldValueModelBinder));
}
return null;
}
}
bindingContext.HttpContext.Request.Form
seems to always contain the whole Form data, so I'm not sure how can I process form data by indexing them? Is what I'm trying to achieve even possible or should I try to think about a different approach? Any help much appreciated.