ModelBindingContext.ValueProvider.GetValue(key) 总是 returns null
ModelBindingContext.ValueProvider.GetValue(key) always returns null
我正在使用 AngularJS 来操作一个相当复杂的父对象,其中的子对象需要在服务器端表现得非常不同。基于看起来相当可靠的 this answer,我创建了下面的测试用例。我 运行 遇到的问题是,每当我输入 CreateModel
函数时,对 bindingContext.ValueProvider.GetValue(key)
returns 的任何调用都是空的。我已经检查了调试器中的所有值。似乎已加载对象类型,但尚未绑定任何值。
我的模特:
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;
});
}
}]);
})();
我试过的东西
我试过只使用字符串来指示 class 名称,如 this answer and this one 中所示。但是,对 bindingContext.ValueProvider.GetValue(key)
的调用仍然 returns 为空,导致 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 数据时,似乎所有按键都带有型号名称前缀。这可以简单地通过将 CreateModel
的第一行更改为:
来解决
CreatureType creatureType = GetValue<CreatureType>(bindingContext, bindingContext.ModelName + ".Type");
上一个答案:
我的 AngularJS 函数发布的数据被 Chrome 标记为 "Request Payload",无法(据我所知)在 CreateModel
中访问。当我逐行实施@DarinDimitrov 的解决方案时,数据发布为 "Form Data",在 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 发帖时,可以使用以下方法访问模型数据:
我正在使用 AngularJS 来操作一个相当复杂的父对象,其中的子对象需要在服务器端表现得非常不同。基于看起来相当可靠的 this answer,我创建了下面的测试用例。我 运行 遇到的问题是,每当我输入 CreateModel
函数时,对 bindingContext.ValueProvider.GetValue(key)
returns 的任何调用都是空的。我已经检查了调试器中的所有值。似乎已加载对象类型,但尚未绑定任何值。
我的模特:
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;
});
}
}]);
})();
我试过的东西
我试过只使用字符串来指示 class 名称,如 this answer and this one 中所示。但是,对 bindingContext.ValueProvider.GetValue(key)
的调用仍然 returns 为空,导致 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 数据时,似乎所有按键都带有型号名称前缀。这可以简单地通过将 CreateModel
的第一行更改为:
CreatureType creatureType = GetValue<CreatureType>(bindingContext, bindingContext.ModelName + ".Type");
上一个答案:
我的 AngularJS 函数发布的数据被 Chrome 标记为 "Request Payload",无法(据我所知)在 CreateModel
中访问。当我逐行实施@DarinDimitrov 的解决方案时,数据发布为 "Form Data",在 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 发帖时,可以使用以下方法访问模型数据: