Note: This is a follow-up to an answer on a previous question.
I'm decorating a property's setter with an Attribute called TestMaxStringLength
that's used in method called from the setter for validation.
The property currently looks like this:
public string CompanyName
{
get
{
return this._CompanyName;
}
[TestMaxStringLength(50)]
set
{
this.ValidateProperty(value);
this._CompanyName = value;
}
}
But I would rather it look like this:
[TestMaxStringLength(50)]
public string CompanyName
{
get
{
return this._CompanyName;
}
set
{
this.ValidateProperty(value);
this._CompanyName = value;
}
}
The code for ValidateProperty
that is responsible for looking up the attributes of the setter is:
private void ValidateProperty(string value)
{
var attributes =
new StackTrace()
.GetFrame(1)
.GetMethod()
.GetCustomAttributes(typeof(TestMaxStringLength), true);
//Use the attributes to check the length, throw an exception, etc.
}
How can I change the ValidateProperty
code to look for attributes on the property instead of the set method?
As far as I know, there's no way to get a PropertyInfo from a MethodInfo of one of its setters. Though, of course, you could use some string hacks, like using the name for the lookup, and such. I'm thinking something like:
var method = new StackTrace().GetFrame(1).GetMethod();
var propName = method.Name.Remove(0, 4); // remove get_ / set_
var property = method.DeclaringType.GetProperty(propName);
var attribs = property.GetCustomAttributes(typeof(TestMaxStringLength), true);
Needless to say, though, that's not exactly performant.
Also, be careful with the StackTrace class - it's a performance hog, too, when used too often.
In the class that declares the method, you could search for the property that contains that setter. It's not performant, but neither is StackTrace
.
void ValidateProperty(string value)
{
var setter = (new StackTrace()).GetFrame(1).GetMethod();
var property =
setter.DeclaringType
.GetProperties()
.FirstOrDefault(p => p.GetSetMethod() == setter);
Debug.Assert(property != null);
var attributes = property.GetCustomAttributes(typeof(TestMaxStringLengthAttribute), true);
//Use the attributes to check the length, throw an exception, etc.
}
You could consider, as an alternative approach, delaying validation until later, thus removing the need to inspect the stack trace.
This example provides an attribute...
public class MaxStringLengthAttribute : Attribute
{
public int MaxLength { get; set; }
public MaxStringLengthAttribute(int length) { this.MaxLength = length; }
}
... a POCO with the attribute applied to a property...
public class MyObject
{
[MaxStringLength(50)]
public string CompanyName { get; set; }
}
... and a utility class stub that validates the object.
public class PocoValidator
{
public static bool ValidateProperties<TValue>(TValue value)
{
var type = typeof(TValue);
var props = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (var prop in props)
{
var atts = prop.GetCustomAttributes(typeof(MaxStringLengthAttribute), true);
var propvalue = prop.GetValue(value, null);
// With the atts in hand, validate the propvalue ...
// Return false if validation fails.
}
return true;
}
}