DataAnnotationsModelBinder如何与自定义ViewModels配合使用? [英] How does DataAnnotationsModelBinder work with custom ViewModels?

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

问题描述

我正在尝试使用 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 导致 NullReferenceException 当尝试使用复杂的 ViewModel ,例如

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实体的视图,因为我更喜欢使用自定义 ViewMode l s,其中包含几个LINQ实体,而不是无类型的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.

推荐答案

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

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)
}


$ b $我的模型类(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:

嵌套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也正确绑定它们。

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 Wilson 对此解决方案的想法。

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如何与自定义ViewModels配合使用?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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