web-dev-qa-db-fra.com

Comment afficher la valeur de l'attribut DisplayAttribute.Description?

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!" ??

65
Jakob Gade

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. :)

79
Jakob Gade

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 } )  
40
Adam Tuliper - MSFT

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 &lt;span&gt; elements with an <c>asp-description-for</c> attribute.
/// Adds an <c>id</c> attribute and sets the content of the &lt;span&gt; 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! 

4
Filipe Carneiro

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

3
Adel Mourad

Vous devrez écrire un assistant personnalisé qui refléterait votre modèle pour donner la valeur d'attribut Description. 

2
Illuminati
@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>
2

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()
2
Marc Gravell

... 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 &lt;label&gt; elements with an <c>asp-for</c> attribute.
/// Adds a <c>title</c> attribute to the &lt;label&gt; 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!

0
Filipe Carneiro

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();
0
Christian Gollhardt