最近遇到一个很古怪的问题,以前倒是没有发现!!!记录下
测试中预设的故障环境
模型,模型两个都是必须的不能为null
public class TestModel
{
//#region 基元
[Key]
public Guid id { get; set; }
[Required]
public string Keywords { get; set; }
[Required]
public string Title { get; set; }
}
执行方法
public ActionResult test(TestModel im)
{
try
{
var s = new TestModel { id = Guid.Empty, Title = "1", Keywords = "2" };
UpdateModel<TestModel>(s);
return Json(new { success = true, s, ModelState.IsValid }, "text/html", JsonRequestBehavior.AllowGet);
}
catch (InvalidOperationException ex)
{
return Json(new { success = false, error = ex.Message }, "text/html", JsonRequestBehavior.AllowGet);
}
}
请求数据 故意丢失一个模型的值 keyword
http://localhost:3981/Article/test?id=00000000-0000-0000-0000-000000000201&islock=true&Title=1
得到结果
{"success":false,"error":"未能更新类型“Model.TestModel”的模型。"}
但是我的 方法内是有 keyword 值的..为什么会更新错误,为什么会出现这个问题? 抛出错误的是 UpdateModel<TestModel>(s), 抛出 InvalidOperationException 异常.
中间还有很多猜想写在这里就不去验证了:
1.猜想先验证 TestModel 如果不符合模型要求就抛出错误.
结论,如同使用上面的方法 是验证TestModel,但是不是在UpdateModel 中验证.
2.猜想UpdateModel 数据以后再验证是否符合模型,如果不符合就抛出错误.
结论,不是这样的逻辑
3.更换Action参数.为Guid Id
结论,运行正常了,那里除了问题? 查找源代码结论和验证就写在下面了
Action 修正参数,可以正常保存 public ActionResult test(Guid id)
{
try
{
var s = new TestModel { id = Guid.Empty, Title = "1", Keywords = "2" };
UpdateModel<TestModel>(s);
return Json(new { success = true, s, ModelState.IsValid }, "text/html", JsonRequestBehavior.AllowGet);
}
catch (InvalidOperationException ex)
{
return Json(new { success = false, error = ex.Message }, "text/html", JsonRequestBehavior.AllowGet);
}
}
请求数据
http://localhost:3981/Article/test?id=00000000-0000-0000-0000-000000000201&islock=true&Title=1
得到结果
{"success":true,"s":{"id":"00000000-0000-0000-0000-000000000201","Keywords":"2","Title":"1"},"IsValid":true}
一切正常了,why???
直接检查MVC源代码: 真是的很坑爹的结果:
UpdateModel最终都要执行下面这两个方法
protected internal void UpdateModel<TModel>(TModel model, string prefix, string[] includeProperties, string[] excludeProperties, IValueProvider valueProvider) where TModel : class {
bool success = TryUpdateModel(model, prefix, includeProperties, excludeProperties, valueProvider);
if (!success) {
string message = String.Format(CultureInfo.CurrentCulture, MvcResources.Controller_UpdateModel_UpdateUnsuccessful,
typeof(TModel).FullName);
throw new InvalidOperationException(message);
}
}
protected internal bool TryUpdateModel<TModel>(TModel model, string prefix, string[] includeProperties, string[] excludeProperties, IValueProvider valueProvider) where TModel : class {
if (model == null) {
throw new ArgumentNullException("model");
}
if (valueProvider == null) {
throw new ArgumentNullException("valueProvider");
}
Predicate<string> propertyFilter = propertyName => BindAttribute.IsPropertyAllowed(propertyName, includeProperties, excludeProperties);
IModelBinder binder = Binders.GetBinder(typeof(TModel));
ModelBindingContext bindingContext = new ModelBindingContext() {
ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, typeof(TModel)),
ModelName = prefix,
ModelState = ModelState,
PropertyFilter = propertyFilter,
ValueProvider = valueProvider
};
binder.BindModel(ControllerContext, bindingContext);
return ModelState.IsValid;
}
请注意最后一句,是什么? 是不是检查当前Action 中参数的 ModelState.IsValid,这个就是原因所在了,当我从 TestModel 转换到 id 的时候 ModelState.IsValid 从假变成了真
所以不会抛出了错误 InvalidOperationException
结论:要使用UpdateModel 方法必须保证Action中所有参数的模型都必须符合模型要求.即 ModelState.IsValid 必须为真