Good day!
I've such method to get [DisplayName]
attribute value of a property (which is attached directly or using [MetadataType]
attribute). I use it in rare cases where I need to get [DisplayName]
in controller code.
public static class MetaDataHelper
{
public static string GetDisplayName(Type dataType, string fieldName)
{
// First look into attributes on a type and it's parents
DisplayNameAttribute attr;
attr = (DisplayNameAttribute)dataType.GetProperty(fieldName).GetCustomAttributes(typeof(DisplayNameAttribute), true).SingleOrDefault();
// Look for [MetadataType] attribute in type hierarchy
// http://stackoverflow.com/questions/1910532/attribute-isdefined-doesnt-see-attributes-applied-with-metadatatype-class
if (attr == null)
{
MetadataTypeAttribute metadataType = (MetadataTypeAttribute)dataType.GetCustomAttributes(typeof(MetadataTypeAttribute), true).FirstOrDefault();
if (metadataType != null)
{
var property = metadataType.MetadataClassType.GetProperty(fieldName);
if (property != null)
{
attr = (DisplayNameAttribute)property.GetCustomAttributes(typeof(DisplayNameAttribute), true).SingleOrDefault();
}
}
}
return (attr != null) ? attr.DisplayName : String.Empty;
}
}
It works, but it has two drawbacks:
Is it possible to overcome both problems using lambdas, something like we have in ASP.NET MVC:
Html.LabelFor(m => m.Property.Can.Be.Very.Complex.But.Strongly.Typed);
Update
Here is an updated and checked version from BuildStarted solution. It is modified to use DisplayName
attribute (you can modify back to Display
attribute if you use it). And fixed minor bugs to get attribute of nested properties.
public static string GetDisplayName<TModel>(Expression<Func<TModel, object>> expression)
{
Type type = typeof(TModel);
string propertyName = null;
string[] properties = null;
IEnumerable<string> propertyList;
//unless it's a root property the expression NodeType will always be Convert
switch (expression.Body.NodeType)
{
case ExpressionType.Convert:
case ExpressionType.ConvertChecked:
var ue = expression.Body as UnaryExpression;
propertyList = (ue != null ? ue.Operand : null).ToString().Split(".".ToCharArray()).Skip(1); //don't use the root property
break;
default:
propertyList = expression.Body.ToString().Split(".".ToCharArray()).Skip(1);
break;
}
//the propert name is what we're after
propertyName = propertyList.Last();
//list of properties - the last property name
properties = propertyList.Take(propertyList.Count() - 1).ToArray(); //grab all the parent properties
foreach (string property in properties)
{
PropertyInfo propertyInfo = type.GetProperty(property);
type = propertyInfo.PropertyType;
}
DisplayNameAttribute attr;
attr = (DisplayNameAttribute)type.GetProperty(propertyName).GetCustomAttributes(typeof(DisplayNameAttribute), true).SingleOrDefault();
// Look for [MetadataType] attribute in type hierarchy
// http://stackoverflow.com/questions/1910532/attribute-isdefined-doesnt-see-attributes-applied-with-metadatatype-class
if (attr == null)
{
MetadataTypeAttribute metadataType = (MetadataTypeAttribute)type.GetCustomAttributes(typeof(MetadataTypeAttribute), true).FirstOrDefault();
if (metadataType != null)
{
var property = metadataType.MetadataClassType.GetProperty(propertyName);
if (property != null)
{
attr = (DisplayNameAttribute)property.GetCustomAttributes(typeof(DisplayNameAttribute), true).SingleOrDefault();
}
}
}
return (attr != null) ? attr.DisplayName : String.Empty;
}
There are two ways to do this:
Models.Test test = new Models.Test();
string DisplayName = test.GetDisplayName(t => t.Name);
string DisplayName = Helpers.GetDisplayName<Models.Test>(t => t.Name);
The first one works by virtue of writing a generic extension method to any TModel (which is all types). This means it will be available on any object and not just your model. Not really recommended but nice because of it's concise syntax.
The second method requires you to pass in the Type of the model it is - which you're already doing but as a parameter instead. This method is required to define type via Generics because Func expects it.
Here are the methods for you to check out.
public static string GetDisplayName<TModel, TProperty>(this TModel model, Expression<Func<TModel, TProperty>> expression) {
Type type = typeof(TModel);
MemberExpression memberExpression = (MemberExpression)expression.Body;
string propertyName = ((memberExpression.Member is PropertyInfo) ? memberExpression.Member.Name : null);
// First look into attributes on a type and it's parents
DisplayAttribute attr;
attr = (DisplayAttribute)type.GetProperty(propertyName).GetCustomAttributes(typeof(DisplayAttribute), true).SingleOrDefault();
// Look for [MetadataType] attribute in type hierarchy
// http://stackoverflow.com/questions/1910532/attribute-isdefined-doesnt-see-attributes-applied-with-metadatatype-class
if (attr == null) {
MetadataTypeAttribute metadataType = (MetadataTypeAttribute)type.GetCustomAttributes(typeof(MetadataTypeAttribute), true).FirstOrDefault();
if (metadataType != null) {
var property = metadataType.MetadataClassType.GetProperty(propertyName);
if (property != null) {
attr = (DisplayAttribute)property.GetCustomAttributes(typeof(DisplayNameAttribute), true).SingleOrDefault();
}
}
}
return (attr != null) ? attr.Name : String.Empty;
}
public static string GetDisplayName<TModel>(Expression<Func<TModel, object>> expression) { }
The reason you can't just use Something.GetDisplayName(t => t.Name)
on it's own is because in the Razor engine you're actually passing an instantiated object of HtmlHelper<TModel>
which is why the first method requires an instantiated object - This is only required for the compiler to infer what types belong to which generic name.
public static string GetDisplayName<TModel>(Expression<Func<TModel, object>> expression) {
Type type = typeof(TModel);
string propertyName = null;
string[] properties = null;
IEnumerable<string> propertyList;
//unless it's a root property the expression NodeType will always be Convert
switch (expression.Body.NodeType) {
case ExpressionType.Convert:
case ExpressionType.ConvertChecked:
var ue = expression.Body as UnaryExpression;
propertyList = (ue != null ? ue.Operand : null).ToString().Split(".".ToCharArray()).Skip(1); //don't use the root property
break;
default:
propertyList = expression.Body.ToString().Split(".".ToCharArray()).Skip(1);
break;
}
//the propert name is what we're after
propertyName = propertyList.Last();
//list of properties - the last property name
properties = propertyList.Take(propertyList.Count() - 1).ToArray(); //grab all the parent properties
Expression expr = null;
foreach (string property in properties) {
PropertyInfo propertyInfo = type.GetProperty(property);
expr = Expression.Property(expr, type.GetProperty(property));
type = propertyInfo.PropertyType;
}
DisplayAttribute attr;
attr = (DisplayAttribute)type.GetProperty(propertyName).GetCustomAttributes(typeof(DisplayAttribute), true).SingleOrDefault();
// Look for [MetadataType] attribute in type hierarchy
// http://stackoverflow.com/questions/1910532/attribute-isdefined-doesnt-see-attributes-applied-with-metadatatype-class
if (attr == null) {
MetadataTypeAttribute metadataType = (MetadataTypeAttribute)type.GetCustomAttributes(typeof(MetadataTypeAttribute), true).FirstOrDefault();
if (metadataType != null) {
var property = metadataType.MetadataClassType.GetProperty(propertyName);
if (property != null) {
attr = (DisplayAttribute)property.GetCustomAttributes(typeof(DisplayNameAttribute), true).SingleOrDefault();
}
}
}
return (attr != null) ? attr.Name : String.Empty;
}