asp.net-mvc - 具有 MVC 验证的 CustomTypeDescriptor - 如何使用 property.GetValue(component) 获取属性值?

标签 asp.net-mvc validation validationattribute typedescriptor customtypedescriptor

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

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

运行者来源: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 View 中将此模型与此描述符一起使用时,出现以下异常:

Value cannot be null. Parameter name: primary Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.

Exception Details: System.ArgumentNullException: Value cannot be null. Parameter name: primary

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

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

编辑2:使用解决方法我几乎总是看到实例为空。因此不可能在那里获取值并将其放入 TypeDescriptor 的构造函数...

谢谢!

最佳答案

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

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

然后,为了绑定(bind)此描述符(每个实例!),我使用 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、除 Controller 之外的 MVC 其他类、html 帮助程序(不显眼的验证)以及其他项目(例如 WCF 服务)...

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

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

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

关于asp.net-mvc - 具有 MVC 验证的 CustomTypeDescriptor - 如何使用 property.GetValue(component) 获取属性值?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/16235142/

相关文章:

validation - ASP.NET MVC 将 null 转换为零长度字符串

javascript - 当模型数据在 angularjs 中的表单之外更改时,如何正确检查观察者中的表单有效性?

c# - 在 C# MVC 中验证枚举值。发生部分验证 - 如何更改验证行为?

silverlight - 更改RIA服务中的默认“输入格式不正确”验证错误

asp.net-mvc - 在我的 asp.net MVC 解决方案中保持 Script 文件夹轻巧、干净和有条理

javascript - 0x800a138f - JavaScript 运行时错误 : Unable to get property 'fn' of undefined or null reference

validation - 在 Go 中使用 unicode 包进行验证?

c# - 如何在 ASP.NET Core 中本地化验证属性的标准错误消息

asp.net - 共享主机中的 "The transaction log for database is full due to ' LOG_BACKUP '"

c# - ASP.NET MVC 实体、DTO、业务对象、ViewModels?我该如何管理?