【问题标题】:ModelBindingContext.ValueProvider.GetValue(key) always returns nullModelBindingContext.ValueProvider.GetValue(key) 总是返回 null
【发布时间】:2018-03-24 11:08:17
【问题描述】:

我正在使用 AngularJS 来操作一个相当复杂的父对象,其中包含需要在服务器端表现完全不同的子对象。基于看起来相当可靠的this answer,我创建了下面的测试用例。我遇到的问题是,每当我输入CreateModel 函数时,对bindingContext.ValueProvider.GetValue(key) 的任何调用都会返回null。我检查了调试器中的所有值。对象类型似乎已加载,但尚未绑定任何值。

我的模特:

public class Menagerie
{
    public Menagerie()
    {
        Critters = new List<Creature>();
    }

    public string MakeNoise()
    {
        return String.Join(" ", Critters.Select(c => c.MakeNoise()));
    }

    public List<Creature> Critters { get; set; }
}

public class Tiger : Creature
{
    public Tiger() { }
    public override CreatureType Type => CreatureType.Tiger;
    public override string Sound => "ROAR";
}

public class Kitty : Creature
{
    public Kitty() { }
    public override CreatureType Type => CreatureType.Kitty;
    public override string Sound => "meow";
}

public class Creature
{
    public Creature() { }
    public string Name { get; set; }
    public virtual CreatureType Type { get; set; }
    public virtual string Sound { get; }
    public string MakeNoise()
    {
        return $"{Name} says {Sound ?? "nothing"}.";
    }

    public static Type SelectFor(CreatureType type)
    {
        switch (type)
        {
            case CreatureType.Tiger:
                return typeof(Tiger);
            case CreatureType.Kitty:
                return typeof(Kitty);
            default:
                throw new Exception();
        }
    }
}

public enum CreatureType
{
    Tiger,
    Kitty,
}

public class CreatureModelBinder : DefaultModelBinder
{
    protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
    {
        CreatureType creatureType = GetValue<CreatureType>(bindingContext, "Type");
        Type model = Creature.SelectFor(creatureType);
        Creature instance = (Creature)base.CreateModel(controllerContext, bindingContext, model);
        bindingContext.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => instance, model);
        return instance;
    }

    private T GetValue<T>(ModelBindingContext bindingContext, string key)
    {
        ValueProviderResult valueResult = bindingContext.ValueProvider.GetValue(key); // valueResult is null
        bindingContext.ModelState.SetModelValue(key, valueResult);
        return (T)valueResult.ConvertTo(typeof(T)); // NullReferenceException
    }
}

我的脚本:

(function () {
    'use strict';

    angular.module('app', []).controller('CritterController', ['$scope', '$http', function ($scope, $http) {

        $http.post('/a/admin/get-menagerie', { }).success(function (data) {
            $scope.menagerie = data.menagerie;
        });

        $scope.makeNoise = function () {
            $http.post('/a/admin/make-noise', { menagerie: $scope.menagerie }).success(function (data) {
                $scope.message = data.message;
            });
        }
    }]);

})();

我尝试过的事情

我试过只使用一个字符串来表示类名,如this answerthis one。但是,对bindingContext.ValueProvider.GetValue(key) 的调用仍然返回null,从而导致NullReferenceException

我还检查以确保模型正确绑定。当我将CreatureModelBinder 更改为以下内容时,一切都很好。但是每个生物都失去了继承的类型,变成了Creature

public class CreatureModelBinder : DefaultModelBinder
{
    protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
    {
        return base.CreateModel(controllerContext, bindingContext, modelType);
    }
} // MakeNoise returns: "Shere Khan says nothing. Mr. Boots says nothing. Dr. Evil says nothing."

【问题讨论】:

    标签: json angular asp.net-mvc-5 model-binding


    【解决方案1】:

    我希望存在的解决方案:

    发布 JSON 数据时,似乎所有键都带有模型名称。这可以通过将CreateModel 的第一行更改为:

    CreatureType creatureType = GetValue<CreatureType>(bindingContext, bindingContext.ModelName + ".Type");
    

    上一个答案:

    我的 AngularJS 函数发布的数据被 Chrome 标记为“请求有效负载”,不能(据我所知)在 CreateModel 中访问。当我逐行实施@DarinDimitrov 的解决方案时,数据被发布为“表单数据”,可在CreateModel 中找到。我发现了更多关于数据类型 here 之间差异的信息,尽管 AngularJS 似乎无法在没有一些花哨的杂技的情况下发送具有 application/json 以外的内容类型的数据。

    然而,我确实发现我可以在BindModel 函数中访问我的对象的一个​​实例(并创建一个具有不同类型的新实例),如下所示:

    public class CreatureModelBinder : DefaultModelBinder
    {
        public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
        {
            Creature instance = (Creature)base.BindModel(controllerContext, bindingContext);
            Creature newInstance = (Creature)Activator.CreateInstance(Creature.SelectFor((instance).Type));
            newInstance.Name = instance.Name;
    
            return newInstance;
        }
    } // MakeNoise() returns: "Shere Khan says ROAR. Mr. Boots says meow. Dr. Evil says meow."
    

    我能看到的最大缺点是对象中的每个属性都必须手动映射到新实例,这会变得很麻烦并产生难以跟踪的错误。

    这是我目前正在使用的,但如果有人可以提供更优雅的解决方案,我愿意接受建议。

    编辑

    我发现当作为 application/json 发布时,可以使用以下方式访问模型数据:

    【讨论】:

      猜你喜欢
      • 2012-12-15
      • 2014-03-04
      • 2016-11-02
      • 2014-05-06
      • 2012-06-05
      • 2014-05-30
      • 2014-02-23
      • 2017-10-12
      • 2013-08-16
      相关资源
      最近更新 更多