【问题标题】:How does DataAnnotationsModelBinder work with custom ViewModels?DataAnnotationsModelBinder 如何与自定义 ViewModel 一起使用?
【发布时间】:2009-05-04 14:56:14
【问题描述】:

我正在尝试使用 DataAnnotationsModelBinder 以便在 ASP.NET MVC 中使用数据注释进行服务器端验证。

只要我的 ViewModel 只是一个具有直接属性的简单类,一切都可以正常工作,例如

public class Foo
{
    public int Bar {get;set;}
}

但是,DataAnnotationsModelBinder 在尝试使用复杂的ViewModel 时会导致NullReferenceException,例如

public class Foo
{
    public class Baz
    {
        public int Bar {get;set;}
    }

    public Baz MyBazProperty {get;set;}
}

这对于呈现多个 LINQ 实体的视图来说是一个大问题,因为我更喜欢使用包含多个 LINQ 实体的自定义 ViewModels,而不是无类型的 ViewData 数组。

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 可以很好地处理这样的层次结构,所以我想 DataAnnotationsModelBinder 也应该这样。

第二次编辑:看起来这确实是DataAnnotationsModelBinder 中的一个错误。但是,希望在下一个 ASP.NET MVC 框架版本发布之前解决这个问题。详情请见this forum thread

【问题讨论】:

  • 我有一个类似的模型,但通常一次编辑单个对象。例如:我有一个 Announcement 对象,它可能有也可能没有一系列附件(图像、PDF),但我自己编辑 Announcement,所以不要强制验证器进入 Announcement 的子对象。然后,我将分别编辑子对象 - 相同的视图,但不同的 POST 操作。我现在很感兴趣-您的操作如何验证巨大的对象树?用户界面是如何工作的?
  • 关于视图:我已经在devermind.com/linq/… 描述了如何执行此操作关于验证:到目前为止,我一直在以与 Scott Gu 的教程中所述相同的方式使用服务器端验证:weblogs.asp.net/scottgu/archive/2009/03/10/… .然后我的控制器从不同的实体收集验证错误,如下所示: ModelState.AddRuleViolations(model.User.GetRuleViolations(),"User"); ModelState.AddRuleViolations(model.Company.GetRuleViolations(),"Company");
  • 我确定这是我的模型绑定代码中的某个错误,但我没有时间去追踪它。更紧迫的可交付成果一直在路上。 :(

标签: asp.net-mvc data-binding viewmodel


【解决方案1】:

我今天遇到了完全相同的问题。像你自己一样,我不会将 View 直接绑定到我的模型,而是使用一个中间 ViewDataModel 类来保存模型的实例以及我想发送到视图的任何参数/配置。

我最终修改了 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)
}

我的Model类(Person)来自一个webservice,所以我不能直接给它们加上属性,我解决这个问题的方法如下:

嵌套 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[ViewDataModel.]Person.NearestGeographicRegion.Value,ModelState 将开始正确验证它们,DataAnnotationsModelBinder 也会正确绑定它们。

这个答案不是确定的,这是我今天下午摸不着头脑的产物。 尽管它通过了the project Brian Wilson 开始的单元测试和我自己的大部分有限测试,但它没有经过适当的测试。为了真正结束这件事,我很想听听 Brad Wilson 对此解决方案的想法。

【讨论】:

  • 似乎对我也很有效,至少从我的第一次快速测试来看。非常感谢!我会让布拉德知道你的错误修复,也许他有时间看看。
  • 很高兴知道它也为您工作。如果您绑定到自定义对象数组,最后一个提示(请参阅:haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx)与那篇文章提到的这个模型绑定器不同,您不需要索引字段,实际上它会出错但没有它也可以正常工作。
【解决方案2】:

正如 Martijn 所述,此问题的解决方法很简单。

在 BindProperty 方法中,你会发现这行代码:

if (modelState.Errors.Count == 0) {

应该改为:

if (modelState == null || modelState.Errors.Count == 0) {

我们打算在 MVC 2 中包含 DataAnnotations 支持,其中将包含 DataAnnotationsModelBinder。此功能将成为第一个 CTP 的一部分。

【讨论】:

    猜你喜欢
    • 2017-11-09
    • 2021-05-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-10-18
    相关资源
    最近更新 更多