c# - net - ¿Cómo se muestra el valor del atributo DisplayAttribute.Description?
asp.net mvc tutorial español (11)
Tengo una clase modelo, con una propiedad como esta:
[Display(Name = "Phone", Description="Hello World!")]
public string Phone1 { get; set; }
Mostrar una etiqueta y renderizar un cuadro de texto para la entrada en mi vista es bastante fácil:
@Html.LabelFor(model => model.Organization.Phone1)
@Html.EditorFor(model => model.Organization.Phone1)
@Html.ValidationMessageFor(model => model.Organization.Phone1)
Pero ¿cómo puedo representar el valor del atributo de anotación Descripción, es decir, "Hola mundo!"
... y si prefiere tener la descripción como información sobre herramientas en la etiqueta del formulario, agregue un Tag Helper como este:
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Razor.TagHelpers;
/// <summary>
/// <see cref="ITagHelper"/> implementation targeting <label> elements with an <c>asp-for</c> attribute.
/// Adds a <c>title</c> attribute to the <label> with the Description property from the model data annotation DisplayAttribute.
/// </summary>
[HtmlTargetElement("label", Attributes = ForAttributeName)]
public class LabelTitleTagHelper : TagHelper
{
private const string ForAttributeName = "asp-for";
/// <summary>
/// Creates a new <see cref="LabelTitleTagHelper"/>.
/// </summary>
/// <param name="generator">The <see cref="IHtmlGenerator"/>.</param>
public LabelTitleTagHelper(IHtmlGenerator generator)
{
Generator = generator;
}
/// <inheritdoc />
public override int Order
{
get
{
return -1000;
}
}
[HtmlAttributeNotBound]
[ViewContext]
public ViewContext ViewContext { get; set; }
protected IHtmlGenerator Generator { get; }
/// <summary>
/// An expression to be evaluated against the current model.
/// </summary>
[HtmlAttributeName(ForAttributeName)]
public ModelExpression TitleFor { get; set; }
/// <inheritdoc />
/// <remarks>Does nothing if <see cref="TitleFor"/> is <c>null</c>.</remarks>
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (output == null)
{
throw new ArgumentNullException(nameof(output));
}
var metadata = TitleFor.Metadata;
if (metadata == null)
{
throw new InvalidOperationException(string.Format("No provided metadata ({0})", ForAttributeName));
}
if (!string.IsNullOrWhiteSpace(metadata.Description))
output.Attributes.SetAttribute("title", metadata.Description);
}
}
Eso creará un nuevo atributo de title
con la propiedad Description
de la anotación de datos del modelo DisplayAttribute
.
¡La parte más bonita es que no necesitas tocar tus vistas andamiadas generadas! ¡Porque Tag Helper está apuntando al atributo asp-for
del elemento de label
que ya está allí!
Además de la gran respuesta de Jakob Gade''a:
Si necesita DisplayAttribute
un DisplayAttribute
DescriptionAttribute
lugar de un DisplayAttribute
de DisplayAttribute
, su gran solución aún funciona si DisplayAttribute
metadatos:
public class ExtendedModelMetadataProvider : DataAnnotationsModelMetadataProvider
{
protected override ModelMetadata CreateMetadata(IEnumerable<System.Attribute> attributes, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName)
{
//Possible Multiple Enumerations on IEnumerable fix
var attributeList = attributes as IList<System.Attribute> ?? attributes.ToList();
//Default behavior
var data = base.CreateMetadata(attributeList, containerType, modelAccessor, modelType, propertyName);
//Bind DescriptionAttribute
var description = attributeList.SingleOrDefault(a => typeof(DescriptionAttribute) == a.GetType());
if (description != null)
{
data.Description = ((DescriptionAttribute)description).Description;
}
return data;
}
}
Esto necesita ser registrado en el Método Application_Start
en Global.asax.cs
:
ModelMetadataProviders.Current = new ExtendedModelMetadataProvider();
En ASP.NET MVC Core puede usar los nuevos Tag Helpers, que hacen que su HTML se vea como ... HTML :)
Me gusta esto:
<div class="form-group row">
<label asp-for="Name" class="col-md-2 form-control-label"></label>
<div class="col-md-10">
<input asp-for="Name" class="form-control" aria-describedby="Name-description" />
<span asp-description-for="Name" class="form-text text-muted" />
<span asp-validation-for="Name" class="text-danger" />
</div>
</div>
Nota 1: Puede usar el atributo aria-describedby
en el elemento de entrada ya que ese id. Se creará automáticamente en el elemento span con el atributo asp-description-for
.
Nota 2: en Bootstrap 4, las clases form-text
y text-muted
reemplazan la clase v3 help-block
por texto de ayuda de nivel de bloque.
Para que esta magia suceda, solo necesitas crear un nuevo Tag Helper:
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Razor.TagHelpers;
/// <summary>
/// <see cref="ITagHelper"/> implementation targeting <span> elements with an <c>asp-description-for</c> attribute.
/// Adds an <c>id</c> attribute and sets the content of the <span> with the Description property from the model data annotation DisplayAttribute.
/// </summary>
[HtmlTargetElement("span", Attributes = DescriptionForAttributeName)]
public class SpanDescriptionTagHelper : TagHelper
{
private const string DescriptionForAttributeName = "asp-description-for";
/// <summary>
/// Creates a new <see cref="SpanDescriptionTagHelper"/>.
/// </summary>
/// <param name="generator">The <see cref="IHtmlGenerator"/>.</param>
public SpanDescriptionTagHelper(IHtmlGenerator generator)
{
Generator = generator;
}
/// <inheritdoc />
public override int Order
{
get
{
return -1000;
}
}
[HtmlAttributeNotBound]
[ViewContext]
public ViewContext ViewContext { get; set; }
protected IHtmlGenerator Generator { get; }
/// <summary>
/// An expression to be evaluated against the current model.
/// </summary>
[HtmlAttributeName(DescriptionForAttributeName)]
public ModelExpression DescriptionFor { get; set; }
/// <inheritdoc />
/// <remarks>Does nothing if <see cref="DescriptionFor"/> is <c>null</c>.</remarks>
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (output == null)
{
throw new ArgumentNullException(nameof(output));
}
var metadata = DescriptionFor.Metadata;
if (metadata == null)
{
throw new InvalidOperationException(string.Format("No provided metadata ({0})", DescriptionForAttributeName));
}
output.Attributes.SetAttribute("id", metadata.PropertyName + "-description");
if( !string.IsNullOrWhiteSpace( metadata.Description))
{
output.Content.SetContent(metadata.Description);
output.TagMode = TagMode.StartTagAndEndTag;
}
}
}
Y haga que sus Tag Helpers estén disponibles para todas nuestras vistas Razor. Agregue la directiva Views/_ViewImports.cshtml
archivo Views/_ViewImports.cshtml
:
@addTagHelper "*, YourAssemblyName"
Nota 1: YourAssemblyName
con el nombre de conjunto de su proyecto.
Nota 2: ¡Solo necesita hacer esto una vez, para todos sus Tag Helpers!
Más información sobre Tag Helpers aquí: https://docs.asp.net/en/latest/mvc/views/tag-helpers/intro.html
¡Eso es! ¡Diviértete con los nuevos Tag Helpers!
Estaba a punto de utilizar la respuesta aceptada , pero no funcionó para ASP.NET Core 1/2 (también conocido como MVC 6) porque ModelMetadata.FromLambdaExpression
ya no existe y se ha movido a ExpressionMetadataProvider
(también se ha modificado ligeramente el uso).
Este es un método de extensión actualizado que puede usar con ASP.NET Core 1.1 y 2 :
using System;
using System.Linq.Expressions;
using Microsoft.AspNetCore.Html;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal;
public static class HtmlExtensions
{
public static IHtmlContent DescriptionFor<TModel, TValue>(this IHtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression)
{
if (html == null) throw new ArgumentNullException(nameof(html));
if (expression == null) throw new ArgumentNullException(nameof(expression));
var modelExplorer = ExpressionMetadataProvider.FromLambdaExpression(expression, html.ViewData, html.MetadataProvider);
if (modelExplorer == null) throw new InvalidOperationException($"Failed to get model explorer for {ExpressionHelper.GetExpressionText(expression)}");
return new HtmlString(modelExplorer.Metadata.Description);
}
}
ASP.NET Core 1
Para ASP.NET Core 1, el mismo código funciona, pero necesitará diferentes usings
espacio de nombres:
using System;
using System.Linq.Expressions;
using Microsoft.AspNet.Html.Abstractions;
using Microsoft.AspNet.Mvc.ViewFeatures;
Uso
@Html.DescriptionFor(model => model.Phone1)
La respuesta de HANDL, actualizada para ASP.NET Core 2.0
using System;
using System.Linq.Expressions;
using Microsoft.AspNetCore.Html;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal;
public static class HtmlExtensions
{
public static IHtmlContent DescriptionFor<TModel, TValue>(this IHtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression)
{
if (html == null) throw new ArgumentNullException(nameof(html));
if (expression == null) throw new ArgumentNullException(nameof(expression));
var modelExplorer = ExpressionMetadataProvider.FromLambdaExpression(expression, html.ViewData, html.MetadataProvider);
if (modelExplorer == null) throw new InvalidOperationException($"Failed to get model explorer for {ExpressionHelper.GetExpressionText(expression)}");
return new HtmlString(modelExplorer.Metadata.Description);
}
}
Siempre puedes crear tu propia extensión personalizada como esta:
public static MvcHtmlString ToolTipLabel (string resourceKey, string text, bool isRequired, string labelFor = "", string labelId = "",string className="")
{
string tooltip = string.Empty;
StringBuilder sb = new StringBuilder();
if (!string.IsNullOrEmpty(resourceKey))
{
var resources = GetAllResourceValues();
if (resources.ContainsKey(resourceKey))
{
tooltip = resources[resourceKey].Value;
}
}
sb.Append("<label");
if (!string.IsNullOrEmpty(labelFor))
{
sb.AppendFormat(" for=/"{0}/"", labelFor);
}
if (!string.IsNullOrEmpty(labelId))
{
sb.AppendFormat(" Id=/"{0}/"", labelId);
}
if (!string.IsNullOrEmpty(className))
{
sb.AppendFormat(" class=/"{0}/"", className);
}
if (!string.IsNullOrEmpty(tooltip))
{
sb.AppendFormat(" data-toggle=''tooltip'' data-placement=''auto left'' title=/"{0}/"",tooltip);
}
if (isRequired)
{
sb.AppendFormat("><em class=''required''>*</em> {0} </label></br>", text);
}
else
{
sb.AppendFormat(">{0}</label></br>", text);
}
return MvcHtmlString.Create(sb.ToString());
}
y puede verlo de esta manera:
@HtmlExtension.ToolTipLabel(" "," ",true," "," "," ")
Solo por inspección (es decir, no lo he probado), pero:
var attrib = (DisplayAttribute)Attribute.GetCustomAttribute(
member, typeof(DisplayAttribute));
var desc = attrib == null ? "" : attrib.GetDescription()
Tendría que escribir un ayudante personalizado que refleje en su modelo para dar el valor del atributo Descripción.
Terminé con un ayudante como este:
using System;
using System.Linq.Expressions;
using System.Web.Mvc;
public static class MvcHtmlHelpers
{
public static MvcHtmlString DescriptionFor<TModel, TValue>(this HtmlHelper<TModel> self, Expression<Func<TModel, TValue>> expression)
{
var metadata = ModelMetadata.FromLambdaExpression(expression, self.ViewData);
var description = metadata.Description;
return MvcHtmlString.Create(string.Format(@"<span>{0}</span>", description));
}
}
Gracias a quienes me guiaron en la dirección correcta. :)
Usando la técnica de este artículo sobre cómo mostrar sugerencias visuales para los campos en su formulario , puede acceder al valor a través de lo siguiente:
@Html.TextBoxFor(
model => model.Email ,
new { title = ModelMetadata.FromLambdaExpression<RegisterModel , string>(
model => model.Email , ViewData ).Description } )
@ViewData.ModelMetadata.Properties
.Where(m => m.PropertyName == "Phone1").FirstOrDefault().Description
Entonces, si usabas bootstrap, algo como
<div class="form-group col-sm-6">
@Html.LabelFor(m => m.Organization.Phone1)
@Html.EditorFor(m => m.Organization.Phone1)
<p class="help-block">
@ViewData.ModelMetadata.Properties
.Where(m => m.PropertyName == "DayCount").FirstOrDefault().Description
</p>
</div>