I have a class that is declared Internal. It is decorated with various annotations. In particular is the [DisplayName(“My Display Name”)] annotation. I have some code that will retrieve the value but only works if the class is declared public. I am sort of new to using reflection. I believe I need to specify that the BindingFlags.NonPublic be used but I am not sure where.
LinqPAD code:
void Main()
{
List<SpGetProfileInfoResult> p = new List<SpGetProfileInfoResult>();
p.Add(new SpGetProfileInfoResult() { FName = "Eric" });
p.Add(new SpGetProfileInfoResult() { FName = "Mike" });
p.Dump();
foreach (var item in p)
{
Console.WriteLine(item.DisplayName(i => i.FName));
Console.WriteLine(item.FName);
}
}
public partial class SpGetProfileInfoResult
{
// Uncomment this annotation to see that this part will work
// [System.ComponentModel.DisplayNameAttribute("[BILLTO-FNAME]")]
public string FName { get; set; }
}
public partial class SpGetProfileInfoResult
{
internal class Metadata
{
// This attribute is never available seems.
[System.ComponentModel.DisplayNameAttribute("[BILL-FNAME]")]
public string FName { get; set; }
}
}
public static class Tag
{
public static T GetAttribute<T>(this MemberInfo member, bool isRequired) where T : Attribute
{
var attribute = member.GetCustomAttributes(typeof(T), false).SingleOrDefault();
if (attribute == null && isRequired)
{
throw new ArgumentException(
string.Format(
"The {0} attribute must be defined on member {1}",
typeof(T).Name,
member.Name));
}
return (T)attribute;
}
public static string DisplayName<T>(this T src,Expression<Func<T, object>> propertyExpression)
{
Type metadata = null;
var memberInfo = GetPropertyInformation(propertyExpression.Body);
if (memberInfo == null)
{
throw new ArgumentException(
"No property reference expression was found.",
"propertyExpression");
}
var attr = memberInfo.GetAttribute<DisplayNameAttribute>(false);
if (attr == null)
{
return memberInfo.Name;
}
return attr.DisplayName;
}
public static MemberInfo GetPropertyInformation(Expression propertyExpression)
{
MemberExpression memberExpr = propertyExpression as MemberExpression;
if (memberExpr == null)
{
UnaryExpression unaryExpr = propertyExpression as UnaryExpression;
if (unaryExpr != null && unaryExpr.NodeType == ExpressionType.Convert)
{
memberExpr = unaryExpr.Operand as MemberExpression;
}
}
if (memberExpr != null && memberExpr.Member.MemberType == MemberTypes.Property)
{
return memberExpr.Member;
}
return null;
}
}
Usage:
If you don’t have LinqPAD, you should download it then you can test this pretty easily by just creating a new C# Program in LinkPAD
Debug.WriteLine(item.DisplayName(i => i.FName));
So it looks like you want to be able to decorate existing members of a partial class, by providing metadata in a separate partial piece. There’s no built-in mechanism for that (see eg this question and the classes mentioned in the answer), but if you’re willing to stick to a convention, you can roll your own:
So suppose we have
in a partial piece we can’t change, and
in a partial piece we can change. You already have most of the pieces: in
DisplayName(), you successfully determine that we are looking at theFNameproperty; you then look for aDisplayNameAttributeonT.FName, but there isn’t one, so that’s where it stops.What you need to do is, in the case where you don’t find the attribute you need,
Look for a nested class named
Metadata– note here is one place we useBindingFlags.NonPublicIf we find one:
Look for a member of the same name as was originally being talked about (
BindingFlags.NonPublicagain)If there is one, use your helper method, but this time pass it the metadata type’s member:
(I’ve omitted a final nullity check here, as well as closing the control flow)
Depending on how distasteful you find that
"Metadata"string, you could instead do something declarative with attributes:SpGetProfileInfoResult(the piece you can change) that points at itsMetadatausingtypeof(this is the approach taken bySystem.ComponentModel), orMetadata, to have it claim ‘I am a metadata type’. Then instead of searching for a nested class named a fixed string, we would instead search for a nested class having this particular attribute.