CustomTypeDescriptor с проверкой MVC - как получить значение свойства с помощью property.GetValue (компонент)?

Я создал собственный TypeDescriptionProvider для одной из моих моделей MVC. Я использую его для динамического присвоения ValidationAttribute.

Я использую значение одного свойства, чтобы решить, какие атрибуты добавить к другим свойствам. В веб-сервисе, где я использую DataAnnotationsValidationRunner, проверка работает нормально.

Источник раннера: здесь

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 я получаю следующее исключение:

Значение не может быть нулевым. Имя параметра: primary Описание: необработанное исключение произошло во время выполнения текущего веб-запроса. Просмотрите трассировку стека для получения дополнительных сведений об ошибке и ее происхождении в коде.

Сведения об исключении: System.ArgumentNullException: значение не может быть нулевым. Имя параметра: первичный

Как правильно получить значение свойства в TypeDescriptor? Я использую этот дескриптор через поставщика для типа модели, а не для экземпляра (например, global.asax).

РЕДАКТИРОВАТЬ: Я нашел обходной путь. В методе GetTypeDescriptor MyTypeDescriptorProvider я использую параметр экземпляра и передаю его конструктору MyCustomTypeDescriptor. Однако проверка MVC не работает. Я думаю, что он использует эти динамические данные автоматически (аналогично упомянутому выше бегуну).

РЕДАКТИРОВАТЬ 2: Используя workaroud, я почти всегда вижу нулевой экземпляр. Таким образом, невозможно получить значение там и передать его конструктору TypeDescriptor ...

Спасибо!


person Jozef Krchňavý    schedule 26.04.2013    source источник


Ответы (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, других классов MVC, кроме контроллеров, помощников html (ненавязчивая проверка) и в других проектах, таких как службы WCF ...

Все это нормально, но кажется неправильным. Было бы здорово, если бы я мог каким-то образом подключить MyCustomTypeDescriptor напрямую к MVC, однако, как утверждает эта ссылка, это кажется невозможным.

Как я могу предоставить свой собственный ICustomTypeDescriptor в ASP. NET MVC?

Приветствуются любые улучшения, которые могут помочь сделать это решение более элегантным. Спасибо.

person Jozef Krchňavý    schedule 22.07.2013
comment
Я отправил запрос на перенос для этой функции, см .: aspnetwebstack.codeplex .com / SourceControl / network / forks /. - person LunicLynx; 17.04.2015