Reputation: 38499
I have the following custom attribute, which can be applied on properties:
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class IdentifierAttribute : Attribute
{
}
For example:
public class MyClass
{
[Identifier()]
public string Name { get; set; }
public int SomeNumber { get; set; }
public string SomeOtherProperty { get; set; }
}
There will also be other classes, to which the Identifier attribute could be added to properties of different type:
public class MyOtherClass
{
public string Name { get; set; }
[Identifier()]
public int SomeNumber { get; set; }
public string SomeOtherProperty { get; set; }
}
I then need to be able to get this information in my consuming class. For example:
public class TestClass<T>
{
public void GetIDForPassedInObject(T obj)
{
var type = obj.GetType();
//type.GetCustomAttributes(true)???
}
}
What's the best way of going about this? I need to get the type of the [Identifier()] field (int, string, etc...) and the actual value, obviously based on the type.
Upvotes: 41
Views: 91696
Reputation: 4255
I have extended your example with an attribute the accepts parameter and works one property
[AttributeUsage(AttributeTargets.Property,AllowMultiple = true)]
public class PersonAttribute : Attribute
{
public readonly string FieldName;
public readonly string FieldType;
public PersonAttribute(string name,string type)
{
FieldName = name;
FieldType = type;
}
}
That attribute is applied on the applied on the Person
class as:
public class Person
{
public string FirstName {get;set;} = "FirstName";
public string LastName {get;set;} = "LastName";
[Person("addressline1","db")]
[Person("new_address1","system")]
public string AddressLine1 {get;set;} = "Banglore";
[Person("addressline2","system")]
public string AddressLine2 {get;set;} = "Karnataka";
[Person("addressline3","dto")]
public string AddressLine3 {get;set;} = "INDIA";
}
I have created the following extension function which is generic and will be able to read all properties and attributes values from the class.
public static class AttributesExt
{
public static IEnumerable<PropertyInfo> AllAttributes<T>(this object obj,string name)
{
var allProperties = obj.GetType().GetProperties()
.Where(_ => _.GetCustomAttributes(typeof(T), true).Length >= 1 && _.Name == name);
return allProperties;
}
public static IEnumerable<PropertyInfo> AllAttributes<T>(this object obj)
{
var allProperties = obj.GetType().GetProperties()
.Where(_ => _.GetCustomAttributes(typeof(T), true).Length >= 1);
return allProperties;
}
public static T ReadAttribute<T>(this PropertyInfo propertyInfo)
{
var returnType = propertyInfo.GetCustomAttributes(typeof(T), true)
.Cast<T>().FirstOrDefault();
return returnType;
}
}
So now in the main method if we write
void Main()
{
Person p = new Person();
var all = p.AllAttributes<PersonAttribute>().Dump(); //Get All custom attributes
p.AllAttributes<PersonAttribute>("AddressLine3").Dump();
all.First(_=> _.Name == "AddressLine2").ReadAttribute<PersonAttribute>().Dump();
all.First(_=> _.Name == "AddressLine2").ReadAttribute<PersonAttribute>().FieldName.Dump();
}
We can read those values as per the screenshot below.
Upvotes: 1
Reputation: 3796
Here is a more real-word example. We use an extension method and check if a property contains a FieldMetaDataAttribute (a custom attribute in my source code base) with valid Major and MinorVersion. What is of general interest is the part where we use the parent class type and GetProperties and retrieve the ProperyInfo and then use GetCustomAttribute to retrieve a attribute FieldMetaDataAttribute in this special case. Use this code for inspiration how to do more generic way of retrieving a custom attribute. This can of course be polished to make a general method to retrieve a given attribute of any property of a class instance.
/// <summary>
/// Executes the action if not the field is deprecated
/// </summary>
/// <typeparam name="TProperty"></typeparam>
/// <typeparam name="TForm"></typeparam>
/// <param name="form"></param>
/// <param name="memberExpression"></param>
/// <param name="actionToPerform"></param>
/// <returns>True if the action was performed</returns>
public static bool ExecuteActionIfNotDeprecated<TForm, TProperty>(this TForm form, Expression<Func<TForm, TProperty>> memberExpression, Action actionToPerform)
{
var memberExpressionConverted = memberExpression.Body as MemberExpression;
if (memberExpressionConverted == null)
return false;
string memberName = memberExpressionConverted.Member.Name;
PropertyInfo matchingProperty = typeof(TForm).GetProperties(BindingFlags.Public | BindingFlags.Instance)
.FirstOrDefault(p => p.Name == memberName);
if (matchingProperty == null)
return false; //should not occur
var fieldMeta = matchingProperty.GetCustomAttribute(typeof(FieldMetadataAttribute), true) as FieldMetadataAttribute;
if (fieldMeta == null)
{
actionToPerform();
return true;
}
var formConverted = form as FormDataContract;
if (formConverted == null)
return false;
if (fieldMeta.DeprecatedFromMajorVersion > 0 && formConverted.MajorVersion > fieldMeta.DeprecatedFromMajorVersion)
{
//major version of formConverted is deprecated for this field - do not execute action
return false;
}
if (fieldMeta.DeprecatedFromMinorVersion > 0 && fieldMeta.DeprecatedFromMajorVersion > 0
&& formConverted.MinorVersion >= fieldMeta.DeprecatedFromMinorVersion
&& formConverted.MajorVersion >= fieldMeta.DeprecatedFromMajorVersion)
return false; //the field is expired - do not invoke action
actionToPerform();
return true;
}
Upvotes: -1
Reputation: 4303
A bit late but here is something I did for enums (could be any object also) and getting the description attribute value using an extension (this could be a generic for any attribute):
public enum TransactionTypeEnum
{
[Description("Text here!")]
DROP = 1,
[Description("More text here!")]
PICKUP = 2,
...
}
Getting the value:
var code = TransactionTypeEnum.DROP.ToCode();
Extension supporting all my enums:
public static string ToCode(this TransactionTypeEnum val)
{
return GetCode(val);
}
public static string ToCode(this DockStatusEnum val)
{
return GetCode(val);
}
public static string ToCode(this TrailerStatusEnum val)
{
return GetCode(val);
}
public static string ToCode(this DockTrailerStatusEnum val)
{
return GetCode(val);
}
public static string ToCode(this EncodingType val)
{
return GetCode(val);
}
private static string GetCode(object val)
{
var attributes = (DescriptionAttribute[])val.GetType().GetField(val.ToString()).GetCustomAttributes(typeof(DescriptionAttribute), false);
return attributes.Length > 0 ? attributes[0].Description : string.Empty;
}
Upvotes: 3
Reputation: 16018
Something like the following,, this will use only the first property it comes accross that has the attribute, of course you could place it on more than one..
public object GetIDForPassedInObject(T obj)
{
var prop = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance)
.FirstOrDefault(p => p.GetCustomAttributes(typeof(IdentifierAttribute), false).Count() ==1);
object ret = prop !=null ? prop.GetValue(obj, null) : null;
return ret;
}
Upvotes: 56
Reputation: 43207
public class TestClass<T>
{
public void GetIDForPassedInObject(T obj)
{
PropertyInfo[] properties =
obj.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
PropertyInfo IdProperty = (from PropertyInfo property in properties
where property.GetCustomAttributes(typeof(Identifier), true).Length > 0
select property).First();
if(null == IdProperty)
throw new ArgumentException("obj does not have Identifier.");
Object propValue = IdProperty.GetValue(entity, null)
}
}
Upvotes: 3