asp.net-mvc - route - select asp-for asp-items
Obtiene el atributo[DisplayName] de una propiedad de manera fuertemente tipada (7)
¡Buen día!
Tengo dicho método para obtener el valor del atributo [DisplayName]
de una propiedad (que se adjunta directamente o con el atributo [MetadataType]
). Lo uso en casos raros donde necesito obtener [DisplayName]
en el código del controlador.
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;
}
}
Funciona, pero tiene dos inconvenientes:
- Requiere nombre de campo como cadena
- No funciona si quiero obtener la propiedad de una propiedad
¿Es posible superar ambos problemas utilizando lambdas, algo así como lo que tenemos en ASP.NET MVC:
Html.LabelFor(m => m.Property.Can.Be.Very.Complex.But.Strongly.Typed);
Actualizar
Aquí hay una versión actualizada y comprobada de la solución BuildStarted . Se modifica para usar el atributo DisplayName
(puede volver a modificar el atributo Display
si lo usa). Y corrigió errores menores para obtener el atributo de propiedades anidadas.
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;
}
Encontré otro bonito fragmento de código here , y lo modifiqué ligeramente para el propósito ''DisplayName''
public static string GetDisplayName<TSource, TProperty>(Expression<Func<TSource, TProperty>> expression)
{
var attribute = Attribute.GetCustomAttribute(((MemberExpression)expression.Body).Member, typeof(DisplayNameAttribute)) as DisplayNameAttribute;
if (attribute == null)
{
throw new ArgumentException($"Expression ''{expression}'' doesn''t have DisplayAttribute");
}
return attribute.DisplayName;
}
Y usos
GetDisplayName<ModelName, string>(i => i.PropertyName)
Estoy totalmente de acuerdo con la solución BuildStarted provista. Lo único que cambiaría es que ExtensionsMethode no es compatible con las traducciones. Para apoyar esto es simple se necesita un cambio menor. Hubiera puesto esto en los comentarios, pero no tengo suficientes puntos para hacerlo. Busque la última línea en el método.
El método de extensión
public static string GetDisplayName<TModel, TProperty>(this TModel model, Expression<Func<TModel, TProperty>> expression)
{
Type type = typeof(TModel);
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
string propertyName = propertyList.Last();
//list of properties - the last property name
string[] properties = propertyList.Take(propertyList.Count() - 1).ToArray();
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 = (DisplayAttribute)type.GetProperty(propertyName).GetCustomAttributes(typeof(DisplayAttribute), true).SingleOrDefault();
// Look for [MetadataType] attribute in type hierarchy
// http://.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();
}
}
}
//To support translations call attr.GetName() instead of attr.Name
return (attr != null) ? attr.GetName() : String.Empty;
}
Hago un pequeño cambio porque estás usando recursos para obtener DisplayName
public static string GetDisplayName<TModel>(Expression<Func<TModel, object>> expression)
{
string _ReturnValue = string.Empty;
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://.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();
}
}
}
if (attr != null && attr.ResourceType != null)
_ReturnValue = attr.ResourceType.GetProperty(attr.Name).GetValue(attr).ToString();
else if (attr != null)
_ReturnValue = attr.Name;
return _ReturnValue;
}
Happy Coding
Hay dos maneras de hacer esto:
Models.Test test = new Models.Test();
string DisplayName = test.GetDisplayName(t => t.Name);
string DisplayName = Helpers.GetDisplayName<Models.Test>(t => t.Name);
El primero funciona en virtud de escribir un método de extensión genérico para cualquier TModel (que es de todos los tipos). Esto significa que estará disponible en cualquier objeto y no solo en su modelo. No es realmente recomendable, pero es agradable por su sintaxis concisa.
El segundo método requiere que pases el Tipo de modelo que es, lo que ya estás haciendo, pero como un parámetro. Este método es necesario para definir el tipo a través de Generics porque Func lo espera.
Estos son los métodos para que usted pueda verificar.
Método de extensión estático para todos los objetos
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://.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;
}
Firma para el método específico del tipo: el mismo código que el anterior solo una llamada diferente
public static string GetDisplayName<TModel>(Expression<Func<TModel, object>> expression) { }
La razón por la que no puedes usar Something.GetDisplayName(t => t.Name)
por sí solo es porque en el motor Razor estás pasando un objeto instanciado de HtmlHelper<TModel>
lo que el primer método requiere una instancia objeto: esto solo es necesario para que el compilador infiera qué tipos pertenecen a qué nombre genérico.
Actualización con propiedades recursivas
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://.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;
}
Otro fragmento de código con el código .Net se usa para realizar esto
public static class WebModelExtensions
{
public static string GetDisplayName<TModel, TProperty>(
this HtmlHelper<TModel> html,
Expression<Func<TModel, TProperty>> expression)
{
// Taken from LabelExtensions
var metadata = ModelMetadata.FromLambdaExpression(expression, html.ViewData);
string displayName = metadata.DisplayName;
if (displayName == null)
{
string propertyName = metadata.PropertyName;
if (propertyName == null)
{
var htmlFieldName = ExpressionHelper.GetExpressionText(expression);
displayName = ((IEnumerable<string>) htmlFieldName.Split(''.'')).Last<string>();
}
else
displayName = propertyName;
}
return displayName;
}
}
// Usage
Html.GetDisplayName(model => model.Password)
Solo haz esto:
using System.ComponentModel;
using System.Linq;
using System.Reflection;
namespace yournamespace
{
public static class ExtensionMethods
{
public static string GetDisplayName(this PropertyInfo prop)
{
if (prop.CustomAttributes == null || prop.CustomAttributes.Count() == 0)
return prop.Name;
var displayNameAttribute = prop.CustomAttributes.Where(x => x.AttributeType == typeof(DisplayNameAttribute)).FirstOrDefault();
if (displayNameAttribute == null || displayNameAttribute.ConstructorArguments == null || displayNameAttribute.ConstructorArguments.Count == 0)
return prop.Name;
return displayNameAttribute.ConstructorArguments[0].Value.ToString() ?? prop.Name;
}
}
}
Ejemplo como solicitado:
var props = typeof(YourType).GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(p => p.CanRead);
var propFriendlyNames = props.Select(x => x.GetDisplayName());
Tarde en el juego, pero ...
Creé un método de ayuda utilizando ModelMetadata como @Daniel mencionado y pensé en compartirlo:
public static string GetDisplayName<TModel, TProperty>(
this TModel model
, Expression<Func<TModel, TProperty>> expression)
{
return ModelMetadata.FromLambdaExpression<TModel, TProperty>(
expression,
new ViewDataDictionary<TModel>(model)
).DisplayName;
}
Ejemplo de uso:
Models
:
public class MySubObject
{
[DisplayName("Sub-Awesome!")]
public string Sub { get; set; }
}
public class MyObject
{
[DisplayName("Awesome!")]
public MySubObject Prop { get; set; }
}
Use
:
HelperNamespace.GetDisplayName(Model, m => m.Prop) // "Awesome!"
HelperNamespace.GetDisplayName(Model, m => m.Prop.Sub) // "Sub-Awesome!"