views:

42

answers:

1

I'm using the data annotations attributes for validation in my app, and I want to have a RequiredAsSet attribute, which will require either all properties decorated with the attribute to be populated, or none of them. The set cannot be part populated.

I thought a clever way to do it would be like this:

public class RequiredAsSetAttribute : RequiredAttribute
{
    /// <summary>
    /// Initializes a new instance of the <see cref="RequiredAsSetAttribute"/> class.
    /// </summary>
    /// <param name="viewModel">The view model.</param>
    public RequiredAsSetAttribute(IViewModel viewModel)
    {
        this.ViewModel = viewModel;
    }

    /// <summary>
    /// Gets or sets the view model.
    /// </summary>
    /// <value>The view model.</value>
    private IViewModel ViewModel { get; set; }

    /// <summary>
    /// Determines whether the specified value is valid.
    /// </summary>
    /// <param name="value">The value.</param>
    /// <returns>
    ///  <c>true</c> if the specified value is valid; otherwise, <c>false</c>.
    /// </returns>
    public override bool IsValid(object value)
    {
        IEnumerable<PropertyInfo> properties = GetPropertiesWihRequiredAsSetAttribute();
        bool aValueHasBeenEnteredInTheRequiredFieldSet = properties.Any(property => !string.IsNullOrEmpty(property.GetValue(this.ViewModel, null).ToString()));
        if (aValueHasBeenEnteredInTheRequiredFieldSet)
        {
            return base.IsValid(value);
        }

        return true;
    }

    /// <summary>
    /// Gets the properties with required as set attribute.
    /// </summary>
    /// <returns></returns>
    private IEnumerable<PropertyInfo> GetPropertiesWithRequiredAsSetAttribute()
    {
        return this.ViewModel.GetType()
            .GetProperties()
            .Where(p => GetValidatorsFromProperty(p).Length != 0 && !GetValidatorsFromProperty(p).Any(x => x == this));                
    }

    /// <summary>
    /// Gets the validators from property.
    /// </summary>
    /// <param name="property">The property.</param>
    /// <returns></returns>
    private static RequiredAsSetAttribute[] GetValidatorsFromProperty(PropertyInfo property)
    {
        return (RequiredAsSetAttribute[])property.GetCustomAttributes(typeof(RequiredAsSetAttribute), true);
    }
}

It basically takes my view model as a constructor argument and uses reflection to find the other properties decorated with the RequiredAsSet attribute to check if anything has been entered.

Turns out this isn't such a clever idea though, because you can't pass instances into the constructor of an attribute..only constant expresssions, typeof expressions or array creation expressions, as the compiler helpfully pointed out.

So is there another way to do this?

A: 

If I understand the problem correcty, the way to do this is with a class level validation attribute. You then have access to the entire object and can use reflection to access whatever properties you wish. The instance is passed in during validation.

[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)]
public class RequiredAsSetAttribute : ValidationAttribute
{
    public override bool IsValid(object value)
    {
        var properties = TypeDescriptor.GetProperties(value);
        ...
    }
}
TheCodeKing