DataAnnotationsModelBinder 如何与自定义 ViewModel 一起工作? [英] How does DataAnnotationsModelBinder work with custom ViewModels?

查看:25
本文介绍了DataAnnotationsModelBinder 如何与自定义 ViewModel 一起工作?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I'm trying to use the DataAnnotationsModelBinder in order to use data annotations for server-side validation in ASP.NET MVC.

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

Everything works fine as long as my ViewModel is just a simple class with immediate properties such as

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

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

However, the DataAnnotationsModelBinder causes a NullReferenceException when trying to use a complex ViewModel, such as

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

    public Baz MyBazProperty {get;set;}
}

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

This is a big problem for views that render more than one LINQ entity because I really prefer using custom ViewModels that include several LINQ entities instead of untyped ViewData arrays.

DefaultModelBinder 没有这个问题,所以看起来像是DataAnnotationsModelBinder 中的一个错误.有什么解决方法吗?

The DefaultModelBinder does not have this problem, so it seems like a bug in DataAnnotationsModelBinder. Is there any workaround to this?

一种可能的解决方法当然是在 ViewModel 类中公开子对象的属性,如下所示:

A possible workaround is of course to expose the child object's properties in the ViewModel class like this:

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 也应该如此.

But I'd prefer not to have to write all this extra code. The DefaultModelBinder works fine with such hiearchies, so I suppose the DataAnnotationsModelBinder should as well.

第二次 看起来这确实是 DataAnnotationsModelBinder 中的一个错误.但是,希望在下一个 ASP.NET MVC 框架版本发布之前可以解决此问题.有关详细信息,请参阅此论坛主题.

Second It looks like this is indeed a bug in DataAnnotationsModelBinder. However, there is hope this might be fixed before the next ASP.NET MVC framework version ships. See this forum thread for more details.

推荐答案

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

I faced the exact same issue today. Like yourself I don't tie my View directly to my Model but use an intermediate ViewDataModel class that holds an instance of the Model and any parameters / configurations I'd like to sent of to the view.

我最终修改了 DataAnnotationsModelBinder 上的 BindProperty 以规避 NullReferenceException,而且我个人不喜欢仅在属性有效时才绑定属性(请参阅下面的原因).

I ended up modifying BindProperty on the DataAnnotationsModelBinder to circumvent the NullReferenceException, and I personally didn't like properties only being bound if they were valid (see reasons below).

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.

I also modified it so that it always binds the data on the property no matter if it's valid or not. This way I can just pass the model back to the view withouth invalid properties being reset to 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)来自一个webservice,所以我不能直接给它们添加属性,我解决这个问题的方法如下:

My Model class (Person) comes from a webservice so I can't put attributes on them directly, the way I solved this is as follows:

嵌套数据注释的示例

[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 也正确绑定它们.

Now if I bind a form field to [ViewDataModel.]Person.NearestGeographicRegion.Text & [ViewDataModel.]Person.NearestGeographicRegion.Value the ModelState starts validating them correctly and DataAnnotationsModelBinder binds them correctly as well.

这个答案不是确定的,它是今天下午我挠头的产物.尽管它通过了 项目 Brian Wilson 开始了我自己的大部分有限测试.对于这件事的真正结束,我很想听听 Brad威尔逊对此解决方案的看法.

This answer is not definitive, it's the product of scratching my head this afternoon. It's not been properly tested, eventhough it passed the unit tests in the project Brian Wilson started and most of my own limited testing. For true closure on this matter I would love to hear Brad Wilson thoughts on this solution.

这篇关于DataAnnotationsModelBinder 如何与自定义 ViewModel 一起工作?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆