Implementing a Custom Attribute contd
A custom attribute class should be derived from the base class Attribute defined
in System namespace and housed in mscorlib.dll assembly. By convention, name
of an attribute class is postfixed with "Attribute" and the suffix "Attribute"
can be dropped when the custom attribute is applied to a target. Using this
convention, we define our custom attribute as:
[AttributeUsage(AttributeTargets.Property|AttributeTargets.Field)]
public class ValidLengthAttribute : Attribute{
private int _min;
private int _max;
private string _message;
public ValidLengthAttribute(int min,int max){
_min=min;
_max=max;
}
public string Message{
get {return(_message);}
set {_message=value;}
}
public string Min{
get{return _min.ToString();}
}
public string Max{
get{return _max.ToString();}
}
public bool IsValid(string theValue){
int length=theValue.Length;
if(length >= _min && length <= _max)
return true;
return false;
}
}
The custom attribute definition is mostly self-explanatory, however, we will
discuss a few things before we proceed to define our Validator class. Like any
other class, a custom attribute class can be a target of other attributes as
we have in the definition above. The attribute AttributeUsage specifies the
type of targets the attribute can be applied to. A custom attribute class should
be public. By default, the custom attribute defined above can only be used once
per target.
The Validator class to validate an object is defined as:
public class Validator{
public ArrayList Messages=new ArrayList();
public bool IsValid(object anObject){
bool isValid=true;
FieldInfo[] fields = anObject.GetType().GetFields(BindingFlags.Public|BindingFlags.Instance);
foreach (FieldInfo field in
fields)
if(!isValidField(field,anObject))
isValid=false;
return isValid;
}
private bool isValidField(FieldInfo aField,object anObject){
object[] attributes=aField.GetCustomAttributes(typeof(ValidLengthAttribute),true);
if(attributes.GetLength(0) ==0) return true;
return isValidField(aField,anObject,(ValidLengthAttribute)attributes[0]);
}
private bool isValidField(FieldInfo aField, object anObject,ValidLengthAttribute
anAttr){
string theValue=(string)aField.GetValue(anObject);
if (anAttr.IsValid(theValue)) return true;
addMessages(aField,anAttr);
return false;
}
private void addMessages(FieldInfo aField,ValidLengthAttribute
anAttr){
if(anAttr.Message !=null){
Messages.Add(anAttr.Message);
return;
}
Messages.Add("Invalid range for "+aField.Name+".
Valid range is between "+anAttr.Min+" and "+anAttr.Max);
}
}
The Validator class uses reflection classes to validate the object passed
as a parameter to its IsValid method. First, it extracts all the public fields
in the object using GetType().GetFields(BindingFlags.Public|BindingFlags.Instance)
method. For each field, it extracts the custom attribute of type ValidLengthAttribute
using GetCustomAttributes(typeof(ValidLengthAttribute),true). If it does not
find our custom attribute for a field, it assumes the field to be valid. If
it finds our custom attribute for a field, it calls the IsValid method of ValidLengthAttribute
to validate the value of the field.