ModelBinder反序列化任意json

时间:2014-08-30 00:07:08

标签: c# asp.net-mvc

我无法让默认模型绑定器按预期的方式运行以下数据结构:

JSON:

{
    template: "path/to/template",
    slides: [{
        index: 0,
        context: {
            foo: "bar"
        }
    }, {
        index: 1,
        context: {
            foo: 'bar!',
            table: [
                ['Price', 'Revenue', 'Profit'],
                [$5', 100, 20],
                ['$10', 320, 4],
                ['$7', 50, 2]
            ]
        }

    }]
}

IPresentationData型号:

public interface IPresentationData
{
    public string Template { get; set; }

    public ICollection<SlideData> Slides { get; set; }
}

ISlideData型号:

public interface ISlideData
{
    public int Index { get; set; }

    public IContext Context { get; set; }
}

IContext型号:

public interface IContext : IDictionary<string, dynamic>
{
}

默认模型绑定器工作正常,但SlideData.Context除外,它可以是任意大/深的对象。

例如,json.context.table被反序列化为:

{
  'table[0][0]': 'Price',
  'table[0][1]': 'Revenue',
  ...
}

而不是:

{
  'table': [
    [...]
  ]
}

我认为我需要仅为System.Web.Mvc.DefaultModelBinder#BindModel属性覆盖SlideData.Context,但我不确定从哪里开始。

非常感谢任何帮助。

1 个答案:

答案 0 :(得分:4)

原来需要的是支持IModelBinder的{​​{1}}。

以下是代码:

ExpandoObject

HttpApplication#Application_Start

自定义活页夹:

ModelBinders.Binders.Add(typeof(Context), new DynamicDictionaryModelBinder());
ModelBinders.Binders.Add(typeof(ExpandoObject), new DynamicDictionaryModelBinder());

json已经被内置的public class DynamicDictionaryModelBinder : DefaultModelBinder { public override object BindModel( ControllerContext controllerContext, ModelBindingContext bindingContext) { var model = bindingContext.Model; var modelType = bindingContext.ModelType; if (model == null) { model = this.CreateModel(controllerContext, bindingContext, modelType); } var dictionaryBindingContext = new ModelBindingContext() { ModelMetadata = ModelMetadataProviders.Current .GetMetadataForType(() => model, modelType), ModelName = bindingContext.ModelName, ModelState = bindingContext.ModelState, PropertyFilter = bindingContext.PropertyFilter, ValueProvider = bindingContext.ValueProvider }; return this.UpdateDynamicDictionary(controllerContext, dictionaryBindingContext); } private static KeyValuePair<string, object> CreateEntryForModel( ControllerContext controllerContext, ModelBindingContext bindingContext, Type valueType, IModelBinder valueBinder, string modelName, string modelKey) { var valueBindingContext = new ModelBindingContext() { ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, valueType), ModelName = modelName, ModelState = bindingContext.ModelState, PropertyFilter = bindingContext.PropertyFilter, ValueProvider = bindingContext.ValueProvider }; var thisValue = valueBinder.BindModel(controllerContext, valueBindingContext); return new KeyValuePair<string, object>(modelKey, thisValue); } private object UpdateDynamicDictionary( ControllerContext controllerContext, ModelBindingContext bindingContext) { var modelList = new List<KeyValuePair<string, object>>(); var enumerableValueProvider = bindingContext.ValueProvider as IEnumerableValueProvider; if (enumerableValueProvider != null) { var keys = enumerableValueProvider.GetKeysFromPrefix(bindingContext.ModelName); var groups = keys.GroupBy((k) => k.Key.Split('[')[0]); foreach (var group in groups) { if (group.Count() > 1) { var valueType = typeof(ICollection<ExpandoObject>); modelList.Add( CreateEntryForModel( controllerContext, bindingContext, valueType, Binders.GetBinder(valueType), bindingContext.ModelName + '.' + group.Key, group.Key)); } else { var item = group.Single(); var value = bindingContext.ValueProvider.GetValue(item.Value); var valueType = value != null && value.RawValue != null ? typeof(object) : typeof(ExpandoObject); modelList.Add( CreateEntryForModel( controllerContext, bindingContext, valueType, Binders.GetBinder(valueType), item.Value, item.Key)); } } } var dictionary = (IDictionary<string, object>)bindingContext.Model; foreach (var kvp in modelList) { dictionary[kvp.Key] = kvp.Value; } return dictionary; } } 堆栈正确地序列化了,只需要为IValueProvider提供正确的类型以及正确的访问者。例如Binder#GetBinder(Type) + ICollection<ExpandoObject> vs revenue + object