我正在尝试使用DataAnnotationsModelBinder,以便在ASP.NET MVC中使用数据注释进行服务器端验证。
只要我的ViewModel只是一个具有直接属性的简单类,例如
,一切正常public class Foo
{
public int Bar {get;set;}
}
但是,DataAnnotationsModelBinder
在尝试使用复杂NullReferenceException
时会导致ViewModel
,例如
public class Foo
{
public class Baz
{
public int Bar {get;set;}
}
public Baz MyBazProperty {get;set;}
}
对于渲染多个LINQ实体的视图来说,这是一个很大的问题,因为我更喜欢使用包含多个LINQ实体而不是无类型ViewData数组的自定义ViewModel
。
DefaultModelBinder
没有此问题,因此它似乎是DataAnnotationsModelBinder
中的错误。这有什么解决方法吗?
编辑:可能的解决方法当然是在ViewModel类中公开子对象的属性,如下所示:
public class Foo
{
private Baz myBazInstance;
[Required]
public string ExposedBar
{
get { return MyBaz.Bar; }
set { MyBaz.Bar = value; }
}
public Baz MyBaz
{
get { return myBazInstance ?? (myBazInstance = new Baz()); }
set { myBazInstance = value; }
}
#region Nested type: Baz
public class Baz
{
[Required]
public string Bar { get; set; }
}
#endregion
}
#endregion
但我不想写所有这些额外的代码。 DefaultModelBinder
适用于此类hiearchies,因此我认为DataAnnotationsModelBinder
也应该如此。
第二次编辑:看起来这确实是DataAnnotationsModelBinder
中的错误。但是,希望在下一个ASP.NET MVC框架版本发布之前可以解决这个问题。有关详细信息,请参阅this forum thread。
答案 0 :(得分:8)
我今天遇到了同样的问题。就像你自己一样,我没有将我的View直接绑定到我的模型,而是使用一个中间的ViewDataModel类,它包含Model的一个实例以及我想发送给视图的任何参数/配置。
我最终修改了DataAnnotationsModelBinder上的BindProperty
来绕过NullReferenceException
,我个人不喜欢属性只有在它们有效时才被绑定(参见下面的原因)。
protected override void BindProperty(ControllerContext controllerContext,
ModelBindingContext bindingContext,
PropertyDescriptor propertyDescriptor) {
string fullPropertyKey = CreateSubPropertyName(bindingContext.ModelName, propertyDescriptor.Name);
// Only bind properties that are part of the request
if (bindingContext.ValueProvider.DoesAnyKeyHavePrefix(fullPropertyKey)) {
var innerContext = new ModelBindingContext() {
Model = propertyDescriptor.GetValue(bindingContext.Model),
ModelName = fullPropertyKey,
ModelState = bindingContext.ModelState,
ModelType = propertyDescriptor.PropertyType,
ValueProvider = bindingContext.ValueProvider
};
IModelBinder binder = Binders.GetBinder(propertyDescriptor.PropertyType);
object newPropertyValue = ConvertValue(propertyDescriptor, binder.BindModel(controllerContext, innerContext));
ModelState modelState = bindingContext.ModelState[fullPropertyKey];
if (modelState == null)
{
var keys = bindingContext.ValueProvider.FindKeysWithPrefix(fullPropertyKey);
if (keys != null && keys.Count() > 0)
modelState = bindingContext.ModelState[keys.First().Key];
}
// Only validate and bind if the property itself has no errors
//if (modelState.Errors.Count == 0) {
SetProperty(controllerContext, bindingContext, propertyDescriptor, newPropertyValue);
if (OnPropertyValidating(controllerContext, bindingContext, propertyDescriptor, newPropertyValue)) {
OnPropertyValidated(controllerContext, bindingContext, propertyDescriptor, newPropertyValue);
}
//}
// There was an error getting the value from the binder, which was probably a format
// exception (meaning, the data wasn't appropriate for the field)
if (modelState.Errors.Count != 0) {
foreach (var error in modelState.Errors.Where(err => err.ErrorMessage == "" && err.Exception != null).ToList()) {
for (var exception = error.Exception; exception != null; exception = exception.InnerException) {
if (exception is FormatException) {
string displayName = GetDisplayName(propertyDescriptor);
string errorMessage = InvalidValueFormatter(propertyDescriptor, modelState.Value.AttemptedValue, displayName);
modelState.Errors.Remove(error);
modelState.Errors.Add(errorMessage);
break;
}
}
}
}
}
}
我还修改了它,以便始终绑定属性上的数据,无论它是否有效。这样我就可以将模型传递回视图,而无需将无效属性重置为null。
控制器摘录
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(ProfileViewDataModel model)
{
FormCollection form = new FormCollection(this.Request.Form);
wsPerson service = new wsPerson();
Person newPerson = service.Select(1, -1);
if (ModelState.IsValid && TryUpdateModel<IPersonBindable>(newPerson, "Person", form.ToValueProvider()))
{
//call wsPerson.save(newPerson);
}
return View(model); //model.Person is always bound no null properties (unless they were null to begin with)
}
我的模型类(Person)来自web服务,所以我不能直接将属性放在它们上面,我解决这个问题的方法如下:
嵌套DataAnnotations示例
[Validation.MetadataType(typeof(PersonValidation))]
public partial class Person : IPersonBindable { } //force partial.
public class PersonValidation
{
[Validation.Immutable]
public int Id { get; set; }
[Validation.Required]
public string FirstName { get; set; }
[Validation.StringLength(35)]
[Validation.Required]
public string LastName { get; set; }
CategoryItemNullable NearestGeographicRegion { get; set; }
}
[Validation.MetadataType(typeof(CategoryItemNullableValidation))]
public partial class CategoryItemNullable { }
public class CategoryItemNullableValidation
{
[Validation.Required]
public string Text { get; set; }
[Validation.Range(1,10)]
public string Value { get; set; }
}
现在,如果我将表单字段绑定到[ViewDataModel.]Person.NearestGeographicRegion.Text
&amp; [ViewDataModel.]Person.NearestGeographicRegion.Value
ModelState开始正确验证它们,DataAnnotationsModelBinder也正确绑定它们。
这个答案不是确定的,它是今天下午挠头的产物。 它没有经过适当的测试,尽管它已经通过了the project布莱恩威尔逊开始的单元测试,并且我自己进行了大部分有限的测试。对于这个问题的真正关闭,我很想听到Brad Wilson关于这个解决方案的想法。
答案 1 :(得分:3)
正如Martijn所指出的那样,解决这个问题很简单。
在BindProperty方法中,您将找到以下代码行:
if (modelState.Errors.Count == 0) {
应该改为:
if (modelState == null || modelState.Errors.Count == 0) {
我们打算在MVC 2中包含DataAnnotations支持,其中包括DataAnnotationsModelBinder。此功能将成为第一个CTP的一部分。