J'ai une classe de modèle, avec une propriété comme celle-ci:
[Display(Name = "Phone", Description="Hello World!")]
public string Phone1 { get; set; }
Afficher une étiquette et rendre une zone de texte à saisir est assez facile:
@Html.LabelFor(model => model.Organization.Phone1)
@Html.EditorFor(model => model.Organization.Phone1)
@Html.ValidationMessageFor(model => model.Organization.Phone1)
Mais comment rendre la valeur de l'attribut d'annotation Description, c'est-à-dire "Hello World!" ??
Je me suis retrouvé avec un assistant comme celui-ci:
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));
}
}
Merci à ceux qui m'ont conduit dans la bonne direction. :)
En utilisant la technique de cet article sur la manière de afficher des astuces visuelles pour les champs de votre formulaire , vous pouvez accéder à la valeur via les éléments suivants:
@Html.TextBoxFor(
model => model.Email ,
new { title = ModelMetadata.FromLambdaExpression<RegisterModel , string>(
model => model.Email , ViewData ).Description } )
Dans ASP.NET MVC Core, vous pouvez utiliser les nouveaux Tag Helpers, qui donnent à votre HTML un aspect similaire à ... HTML :)
Comme ça:
<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>
Remarque 1: Vous pouvez utiliser l'attribut aria-describedby
dans l'élément d'entrée car cet identifiant sera créé automatiquement dans l'élément span avec l'attribut asp-description-for
.
Remarque 2: dans Bootstrap 4, les classes form-text
et text-muted
remplacent la classe v3 help-block
pour le texte d'aide de niveau bloc.
Pour que cette magie se produise, il vous suffit de créer un nouvel assistant de tag:
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;
}
}
}
Et rendez vos assistants de balises disponibles pour toutes nos vues Razor. Ajoutez la directive addTagHelper au fichier Views/_ViewImports.cshtml
:
@addTagHelper "*, YourAssemblyName"
Remarque 1: remplacez YourAssemblyName
par le nom d'assemblage de votre projet.
Note 2: Vous devez juste le faire une fois pour tous vos Tag Helpers!
Plus d'informations sur Tag Helpers ici: https://docs.asp.net/en/latest/mvc/views/tag-helpers/intro.html
C'est tout! Amusez-vous avec les nouveaux Tag Helpers!
Si quelqu'un se demande comment utiliser la réponse acceptée
1- Dans votre solution Explorer> Ajouter un nouveau dossier> nommez-le "Helpers" par exemple
2- Ajoutez une nouvelle classe, nommez-la "CustomHtmlHelpers" par exemple
3- Collez le code:
public static class MvcHtmlHelpers
{
public static string DescriptionFor<TModel, TValue>(this HtmlHelper<TModel> self, Expression<Func<TModel, TValue>> expression)
{
var metadata = ModelMetadata.FromLambdaExpression(expression, self.ViewData);
var description = metadata.Description;
return string.IsNullOrWhiteSpace(description) ? "" : description;
}
}
4- Dans votre modèle ou viewModel, utilisez ceci:
[Display(Name = "User Name", Description = "Enter your User Name")]
public string FullName { get; set; }
5- Dans votre vue Razor, après le @model, tapez cette ligne
@using YOUR_PROJECT.Helpers
6- Affichez la description comme ceci:
@Html.DescriptionFor(m => m.FullName)
7- Vous pouvez utiliser la description pour afficher du texte dans l’espace réservé aux entrées:
@Html.DisplayNameFor(m => m.FullName)
@Html.TextBoxFor(m => m.FullName, new { @class = "form-control", placeholder = Html.DescriptionFor(m => m.FullName) })
Merci
Vous devrez écrire un assistant personnalisé qui refléterait votre modèle pour donner la valeur d'attribut Description.
@ViewData.ModelMetadata.Properties
.Where(m => m.PropertyName == "Phone1").FirstOrDefault().Description
Donc, si vous utilisiez bootstrap, quelque chose comme
<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>
Par inspection seulement (c’est-à-dire que je n’ai pas testé cela), mais:
var attrib = (DisplayAttribute)Attribute.GetCustomAttribute(
member, typeof(DisplayAttribute));
var desc = attrib == null ? "" : attrib.GetDescription()
... et si vous préférez que la description soit une info-bulle dans l'étiquette du formulaire, ajoutez un assistant de balise comme celui-ci:
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);
}
}
Cela créera un nouvel attribut title
avec la propriété Description
à partir de l'annotation de données du modèle DisplayAttribute
.
La belle partie est que vous n'avez pas besoin de toucher vos vues générées d'échafaudage! Parce que cette assistance de balises cible l'attribut asp-for
de l'élément label
qui est déjà présent!
En plus de Jakob Gade'a excellente réponse:
Si vous devez prendre en charge une DescriptionAttribute
au lieu d'une DisplayAttribute
, sa solution géniale fonctionne toujours si nous remplaçons MetadataProvider:
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;
}
}
Cela doit être enregistré dans la méthode Application_Start
dans Global.asax.cs
:
ModelMetadataProviders.Current = new ExtendedModelMetadataProvider();