提问者:小点点

CustomTypeSetptor与MVC验证-如何获得属性值与properties. GetValue(组件)?


我已经为我的一个MVC模型创建了自定义的类型描述提供程序。我用它来动态分配验证属性。

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

转轮来源:这里

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

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

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视图中使用此模型与此描述符时,我得到以下异常:

值不能为空。参数名称:主要描述:在执行当前Web请求期间发生了未处理的异常。请查看堆栈跟踪以获取有关错误及其在代码中起源的更多信息。

异常详情:System. ArgumentNullException:值不能为空。参数名称:主

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

编辑:我找到了解决方法。在MyTypeSetptorProvider的GetTypeSetptor方法中,我使用实例参数并将其传递给MyCustomTypeSetptor的consctructor。然而,MVC验证不起作用。我认为它会自动使用这些动态数据(类似于上面提到的运行器)。

编辑2:使用workaroud我几乎总是看到实例null。所以不可能在那里获取值并将其放入TypeSpeptor的consctructor…

谢谢你!


共1个答案

匿名用户

最后,我能够在绑定期间为客户端验证和验证模型所需的超文本标记语言标签的生成使用自定义类型描述器。

第一个MyCustomTypeDESptor. 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);
    }
}

但是,这工作得很好,在ModelBind期间,没有设置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);
            }
        }
    }
}

现在我可以使用MyCustomTypeSpeptor与DataAnnotationRunner验证所有MVCweb,MVC其他类比控制器,html助手(不显眼的验证)和其他项目,如WCF服务…

所有这些都很好,但是感觉不对。如果我能以某种方式将MyCustomTypeSpeptor直接挂钩到MVC,那就太好了,但是正如这个链接所声称的,这似乎是不可能的。

如何在ASP.NETMVC中提供自己的ICustomTypeSpeptor?

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