2

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.

mgajver
  • 21
  • 2

0 Answers0