Determine only Required Validation Attributes

Topics: Developer Forum
Feb 19, 2007 at 2:34 PM
Hi Everyone,

I have some code which currently determines if a property on my business object has validation attributes, kindly given by Simon:

// Add an asterix to any required fields
// TODO: This check gets all validation fields, I only need required
ValidationFramework.Reflection.TypeDescriptor descriptor = ValidationFramework.Reflection.TypeCache.GetType(dataItemType.TypeHandle);

if (descriptor.Properties.Contains(fieldProperties.DataField))
{
// This method is called multiple times - prevent repeat appending of lots of stars!
if (!fieldProperties.Name.EndsWith(" *"))
{
fieldProperties.Name += " *";
}
}

My problem is that it will put an asterisk next to any property that has validation applied, when actually what i need is simply to determine the ones which have required validation attributes applied.

Is my requirement possible?

Thanks,

Dan
Coordinator
Feb 19, 2007 at 9:47 PM
Dan this code is based on the latest check-in. So, if you have an earlier check-in, you may need to change it a little to get it to work.

So here is a method that should achieve your aim
public static bool PropertyContainsRuleType<TRule>(PropertyDescriptor propertyDescriptor) where TRule : class, IPropertyRule
{
  foreach (IPropertyRule propertyRule in propertyDescriptor.Rules)
  {
    if (propertyRule is TRule)
    {
      return true;
    }
  }
  return false;
}
And here is the usage
TypeDescriptor descriptor = TypeCache.GetType(typeof (Person).TypeHandle);
foreach (PropertyDescriptor propertyDescriptor in descriptor.Properties)
{
  Debug.WriteLine(propertyDescriptor.Name);
  Debug.WriteLine(PropertyContainsRuleType<RequiredStringRule>(propertyDescriptor));
}

On thing to note is that this is not the most efficient code. In your, quite specific case, it would be better (more efficient) to preprocess all the properties and dump it to a list of strings. You could then do a Contains on this list to see if you want to add the "*" The method would look like this
public static IList<string> GetPropertyNamesContainRuleType<TRule, TTarget>() where TRule : class, IPropertyRule
{
  List<string> strings = new List<string>();
  TypeDescriptor typeDescriptor = TypeCache.GetType(typeof(TTarget).TypeHandle);
  foreach (PropertyDescriptor propertyDescriptor in typeDescriptor.Properties)
  {
    foreach (IPropertyRule propertyRule in propertyDescriptor.Rules)
    {
      if (propertyRule is TRule)
      {
        strings.Add(propertyDescriptor.Name);
        break;
      }
    }
  }
  return strings;
}

And the usage would look like this
IList<string> list = GetPropertyNamesContainRuleType<RequiredStringRule, Person>();
foreach (string propertyName in list)
{
  Debug.WriteLine(propertyName);
} 

Feb 20, 2007 at 12:49 PM
Hi Simon,

Yes - the last two code snippets worked nicely for me. I also need to check against other rules at the same time, but I notice that there is no RequiredIntRule.

Is there not a generic RequiredRule that i can use which would encompass all data types that are required - that would be cool.

Cheers,

Dan
Coordinator
Feb 21, 2007 at 8:42 AM
This discussion has been copied to Work Item 8393. You may wish to continue further discussion there.
Coordinator
Feb 21, 2007 at 7:45 PM
Dan.

On the latest checkin I have included a base class for the required rules (RequiredRule<T>). Since it is a generic the code to check for it s a little more difficult.
Here is the usage
     IList<string> list = GetPropertyNamesContainRuleType<Person>(typeof(RequiredRule<>));
     foreach (string propertyName in list)
     {
       Debug.WriteLine(propertyName);
     } 
And here are the helper methods

 
   public static bool IsSubclassOf(Type typeToCheck, Type baseType)
   {
     while (typeToCheck != null)
     {
       if (typeToCheck == baseType)
       {
         return true;
       }
       if (typeToCheck.IsGenericType && (typeToCheck.GetGenericTypeDefinition() == baseType))
       {
         return true;
       }
       typeToCheck = typeToCheck.BaseType;
     }
     return false;
   }
 
 
      public static IList<string> GetPropertyNamesContainRuleType<TTarget>(Type ruleType) 
   {
     List<string> strings = new List<string>();
     TypeDescriptor typeDescriptor = TypeCache.GetType(typeof(TTarget).TypeHandle);
     foreach (PropertyDescriptor propertyDescriptor in typeDescriptor.Properties)
     {
       foreach (IPropertyRule propertyRule in propertyDescriptor.Rules)
       {
         if (IsSubclassOf(propertyRule.GetType(),ruleType))
         {
           strings.Add(propertyDescriptor.Name);
           break;
         }
       }
     }
     return strings;
   }
Mar 19, 2007 at 9:44 AM
Hi Simon,

With the latest source code, your sample above no longer works. This is because it looks like IPropertyRule has been renamed. I tried changing it to IPropertyRuleAttribute or IRuleAttribute but i get an InvalidCastException. What is the correct object to use?

Also i notice that you have implemented a public version of IsSubclassOf - so have changed my code to use that.

Cheers,

Dan
Coordinator
Mar 19, 2007 at 11:29 AM
Edited Mar 21, 2007 at 9:47 AM
Dan
Use “Rule” instead of “IPropertyRule”.

Sorry about the changes. I have been consolidating classes to simplify things a little. So there is no longer a “IRule”, “IPropertyRule” or “IParameterRule”. It was too complex to extend the framework using interface and/or base class approach. Hence people must now inherit from “Rule” to if they want to define custom rules.

Hopefully I will be up to a release state within a few weeks. After that I will attempt to limit breaking changes and where possible be backwards compatible.

Regards
Simon
Mar 21, 2007 at 8:39 AM
Hi Simon,

Thanks - Rule works fine.

Dan
Jan 18, 2008 at 10:22 AM
Hi Simon,

Are you still working on this project?

I am getting an exception on the code you provided me:

ValidationFramework.Reflection.TypeDescriptor descriptor = ValidationFramework.Reflection.TypeCache.GetType(listRecord.GetType().TypeHandle);


returns a NullReferenceException - even though the TypeHandle passed in is valid.


Here is a portion of the stacktrace:

NullReferenceException: Object reference not set to an instance of an object.
ValidationFramework.Reflection.PropertyDescriptor..ctor(TypeDescriptor typeDescriptor, PropertyInfo propertyInfo) +29
ValidationFramework.Reflection.TypeDescriptor..ctor(RuntimeTypeHandle runtimeTypeHandle) +246
ValidationFramework.Reflection.TypeCache.GetType(RuntimeTypeHandle runtimeTypeHandle) +106


Any ideas?

Many Thanks,

Dan
Jan 18, 2008 at 10:52 AM
Some further info from debugging:

The exception occurs on this line in PropertyDescriptor.cs:

/// <summary>
/// Initialize a new instance of <see cref="PropertyDescriptor"/>.
/// </summary>
/// <param name="propertyInfo">The <see cref="PropertyInfo"/> to wrap.</param>
/// <param name="typeDescriptor">The <see cref="Reflection.TypeDescriptor"/> this <see cref="PropertyDescriptor"/> belongs to.</param>
/// <exception cref="NullReferenceException"><paramref name="propertyInfo"/> is null.</exception>
internal PropertyDescriptor(TypeDescriptor typeDescriptor, PropertyInfo propertyInfo)
: base(propertyInfo.GetGetMethod(true).ReturnType.TypeHandle, propertyInfo.Name)

..because propertyInfo.GetGetMethod(true) returns null.

The propertyInfo 's PropertyType.BaseType is a System.Enum, which incidentally will never have validation attributes assigned.

If i remove the Enum the problem goes away, but i need the Enum in the record for specific business reasons.

Thanks,

Dan
Jan 18, 2008 at 11:37 AM
A further update (sorry) :)

It seems the problem was because my public enum property on the object to be validated only had a set{} method and no get, which was to prevent it from being seen due to databinding. Adding the get{} to it removed it problem above.

I have since changed it to use public get and set methods instead of having a property, so the problem is no longer mine, but it might be worth considering write-only public properties in the VF :)

Cheers,

Dan
Coordinator
Jan 18, 2008 at 7:58 PM
Dan
Glad u worked it out.
I have added a more descriptive exception that will be thrown in the case you described. That way the next person should not have as much trouble working it out.
Also the problem with a property having no set is that how do I get the value? On the latest version there is a FieldValidationManager if you want to validate fields.
If you want a read only property try using a private set. The reflective code of the validation framework will pick that up and use it.
 
        [RequiredIntRule]
        public int MyInstanceProperty
        {
            get
            {
                return myInstanceProperty;
            }
            private set
            {
                myInstanceProperty = value;
            }
        }
 
 
        [RequiredIntRule]
        public static int MyStaticProperty
        {
            get
            {
                return myStaticProperty;
            }
            private set
            {
                myStaticProperty = value;
            }
        }
 
And yes I am still working on the project. I am about to upgrade it to VS 2008.
So i will be researching how it works with linq and adding wpf samples.