【问题标题】:CustomTypeDescriptor with MVC Validation - How to get property value with property.GetValue(component)?带有 MVC 验证的 CustomTypeDescriptor - 如何使用 property.GetValue(component) 获取属性值?
【发布时间】:2013-04-20 13:13:52
【问题描述】:

我为我的一个 MVC 模型创建了自定义 TypeDescriptionProvider。我用它来动态分配ValidationAttribute。

我使用一个属性的值来决定将哪些属性添加到其他属性。在我使用 DataAnnotationsValidationRunner 的 Web 服务中,验证工作正常。

跑者来源:here

internal static class DataAnnotationsValidationRunner
{
    public static IEnumerable<ErrorInfo> GetErrors(object instance)
    {
        return from prop in TypeDescriptor.GetProperties(instance).Cast<PropertyDescriptor>()
               from attribute in prop.Attributes.OfType<ValidationAttribute>()
               where !attribute.IsValid(prop.GetValue(instance))
               select new ErrorInfo(prop.Name, attribute.FormatErrorMessage(string.Empty), instance);
    }
}

要获取属性值,我使用以下代码(在 MyCustomTypeDescriptor 中)

public override PropertyDescriptorCollection GetProperties()
    {
        var originalProperties = base.GetProperties();
        var newProperties = new List<PropertyDescriptor>();
        var myProperty = originalProperties.Find("CountryCodeID", false)

        var myId = (int)countryProperty.GetValue(base.GetPropertyOwner(myProperty));

        foreach (PropertyDescriptor pd in originalProperties)
        {
            AttributeCollection runtimeAttributes = pd.Attributes;

            // add new attributes based on myId value
            ....
        }

        return new PropertyDescriptorCollection(newProperties.ToArray());
    }

在 MVC 视图中将此模型与此描述符一起使用时,出现以下异常:

值不能为空。参数名称:primary 说明:An 当前 web 执行过程中发生未处理的异常 要求。请查看堆栈跟踪以获取有关 错误及其源自代码的位置。

异常详细信息:System.ArgumentNullException:值不能为空。 参数名称:primary

在 TypeDescriptor 中获取属性值的正确方法是什么?我通过提供者在模型类型上使用这个描述符,而不是实例(例如 global.asax)。

编辑:我找到了解决方法。在 MyTypeDescriptorProvider 的 GetTypeDescriptor 方法中,我使用实例参数并将其传递给 MyCustomTypeDescriptor 的构造函数。但是,MVC 验证不起作用。我虽然它自动使用这些动态数据(类似于上面提到的跑步者)。

编辑 2:使用 workaroud 我几乎总是看到实例为空。所以不可能在那里获得价值并将其交给 TypeDescriptor 的构造函数......

谢谢!

【问题讨论】:

    标签: asp.net-mvc validation validationattribute typedescriptor customtypedescriptor


    【解决方案1】:

    最后,我能够使用 customTypeDescriptor 生成客户端验证所需的 HTML 标记,以及在绑定期间验证模型。

    第一个 MyCustomTypeDescriptor.cs:

    /// <summary>
    /// CustomTypeDescriptor that provides validation in both MVC Web and WCF services.
    /// </summary>
    public class MyCustomTypeDescriptionProvider : TypeDescriptionProvider
    {
        public MyCustomTypeDescriptionProvider(TypeDescriptionProvider parent)
            :base(parent)
        {
    
        }
    
        public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType, object instance)
        {
            return new MyCustomTypeDescriptor(base.GetTypeDescriptor(objectType, instance));
        }
    }
    
    public class MyCustomTypeDescriptor : CustomTypeDescriptor
    {
        public MyCustomTypeDescriptor(ICustomTypeDescriptor parent)
            : base(parent)
        { }
    
        public override PropertyDescriptorCollection GetProperties()
        {
            var originalProperties = base.GetProperties();
    
            if (this.IsRequired(originalProperties))
            {
                var newProperties = new List<PropertyDescriptor>();
    
                foreach (PropertyDescriptor property in originalProperties)
                {
                    var attrs = property.Attributes;
                    var newAttrs = new Attribute[attrs.Count + 1];
                    attrs.CopyTo(newAttrs, 0);
                    newAttrs[attrs.Count] = new RequiredAttribute();
                    newProperties.Add(TypeDescriptor.CreateProperty(property.ComponentType, property, newAttrs));
                }
    
                return new PropertyDescriptorCollection(newProperties.ToArray());
            }
            else
            {
                return originalProperties;
            }
        }
    
        /// <summary>
        /// IsRequired just simulates more complex validation rule (dependant on another value in model)
        /// </summary>
        /// <param name="originalProperties"></param>
        /// <returns></returns>
        private bool IsRequired(PropertyDescriptorCollection originalProperties)
        {
            if (originalProperties == null || originalProperties.Count == 0)
            {
                throw new ArgumentNullException();
            }
    
            var dependantProperty = originalProperties.Find("DependantValue", false);
    
            if (dependantProperty == null)
            {
                throw new InvalidOperationException();
            }
    
            var value = (int)dependantProperty.GetValue(base.GetPropertyOwner(dependantProperty));
    
            return value > 0;
        }
    }
    

    然后绑定这个描述符(每个实例!)我使用 MyModelValidatorProvider:

    /// <summary>
    /// validator provider is used only for unobtrusive validation
    /// </summary>
    public class MyModelValidatorProvider : DataAnnotationsModelValidatorProvider
    {
        protected override IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context, IEnumerable<Attribute> attributes)
        {
            var isPropertyValidation = metadata.ContainerType != null && !String.IsNullOrEmpty(metadata.PropertyName);
            var model = context.Controller.ViewData.Model as TestCustomizedModel;            
    
            if (isPropertyValidation && model != null)
            {
                TypeDescriptor.AddProvider(new MyCustomTypeDescriptionProvider(TypeDescriptor.GetProvider(model)), model);
    
                AttributeCollection newAttributes;
    
                newAttributes = TypeDescriptor.GetProperties(model).Find(metadata.PropertyName, false).Attributes;
    
                var attrArray = new Attribute[newAttributes.Count];
    
                newAttributes.CopyTo(attrArray, 0);
    
                attributes = attrArray;
            }
    
            return base.GetValidators(metadata, context, attributes);
        }
    }
    

    这工作正常,但是,在 ModelBinding 期间,没有设置 ViewData,因此 ValidatorProvider 不会挂钩。作为解决方案,我使用了 MyModelBinder:

    /// <summary>
    /// Model binder that attaches CustomTypeDescriptor and validates model. 
    /// </summary>
    public class MyModelBinder : DefaultModelBinder
    {
        protected override void OnModelUpdated(ControllerContext controllerContext, ModelBindingContext bindingContext)
        {
            base.OnModelUpdated(controllerContext, bindingContext);
    
            TypeDescriptor.AddProvider(new MyCustomTypeDescriptionProvider(TypeDescriptor.GetProvider(bindingContext.Model)), bindingContext.Model);
    
            var errors = DataAnnotationRunner.GetErrors(bindingContext.Model);
    
            if (errors != null)
            {
                foreach (var error in errors)
                {
                    bindingContext.ModelState.AddModelError(error.MemberNames.FirstOrDefault() ?? string.Empty, error.ErrorMessage);
                }
            }
        }
    }
    

    现在我可以使用 MyCustomTypeDescriptor 和 DataAnnotationRunner 来验证所有 MVC Web、MVC 其他类而不是控制器、html 助手(不显眼的验证)以及 WCF 服务等其他项目中...

    这一切都很好,但是感觉不对。如果我能够以某种方式将 MyCustomTypeDescriptor 直接连接到 MVC,那就太好了,但是正如这个链接所声称的那样,这似乎是不可能的。

    How can I provide my own ICustomTypeDescriptor in ASP.NET MVC?

    欢迎任何有助于使此解决方案更优雅的改进。谢谢。

    【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-11-10
    • 1970-01-01
    • 2014-12-04
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多