| File: Forms\Label.cs | Web Access |
| Project: src\aspnetcore\src\Components\Web\src\Microsoft.AspNetCore.Components.Web.csproj (Microsoft.AspNetCore.Components.Web) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Linq.Expressions; using Microsoft.AspNetCore.Components.Rendering; namespace Microsoft.AspNetCore.Components.Forms; /// <summary> /// Renders a <c><label></c> element for a specified field, reading the display name from /// <see cref="System.ComponentModel.DataAnnotations.DisplayAttribute"/> or /// <see cref="System.ComponentModel.DisplayNameAttribute"/> if present, or falling back to the property name. /// </summary> /// <remarks> /// <para> /// The component supports two usage patterns: /// </para> /// <para> /// <strong>Nested (wrapping) pattern:</strong> When <see cref="ChildContent"/> is provided, the label wraps /// the input component, providing implicit HTML association without requiring matching for/id attributes. /// </para> /// <para> /// <strong>Non-nested pattern:</strong> When <see cref="ChildContent"/> is not provided, the label renders /// with a <c>for</c> attribute matching the field expression. The corresponding input component must have /// a matching <c>id</c> attribute (which is automatically generated by input components derived from /// <see cref="InputBase{TValue}"/>). /// </para> /// </remarks> /// <typeparam name="TValue">The type of the field.</typeparam> public sealed class Label<TValue> : IComponent { private RenderHandle _renderHandle; private string? _displayName; private string? _fieldId; [CascadingParameter] private HtmlFieldPrefix FieldPrefix { get; set; } = default!; /// <summary> /// Specifies the field for which the label should be rendered. /// </summary> [Parameter, EditorRequired] public Expression<Func<TValue>>? For { get; set; } /// <summary> /// Gets or sets the child content to be rendered inside the label element. /// Typically this contains an input component that will be implicitly associated with the label. /// </summary> [Parameter] public RenderFragment? ChildContent { get; set; } /// <summary> /// Gets or sets a collection of additional attributes that will be applied to the label element. /// </summary> [Parameter(CaptureUnmatchedValues = true)] public IReadOnlyDictionary<string, object>? AdditionalAttributes { get; set; } /// <inheritdoc /> void IComponent.Attach(RenderHandle renderHandle) { _renderHandle = renderHandle; } /// <inheritdoc /> Task IComponent.SetParametersAsync(ParameterView parameters) { var previousFor = For; var previousChildContent = ChildContent; var previousAdditionalAttributes = AdditionalAttributes; parameters.SetParameterProperties(this); if (For is null) { throw new InvalidOperationException($"{GetType()} requires a value for the " + $"{nameof(For)} parameter."); } // Only recalculate display name and field id if the expression changed var displayNameChanged = false; if (For != previousFor) { var newDisplayName = ExpressionMemberAccessor.GetDisplayName(For); if (newDisplayName != _displayName) { _displayName = newDisplayName; displayNameChanged = true; } _fieldId = FieldIdGenerator.SanitizeHtmlId( FieldPrefix != null ? FieldPrefix.GetFieldName(For) : ExpressionFormatter.FormatLambda(For)); } var otherParamsChanged = ChildContent != previousChildContent || AdditionalAttributes != previousAdditionalAttributes; if (displayNameChanged || otherParamsChanged) { _renderHandle.Render(BuildRenderTree); } return Task.CompletedTask; } private void BuildRenderTree(RenderTreeBuilder builder) { builder.OpenElement(0, "label"); // For non-nested usage (no ChildContent), add 'for' attribute to associate with input by id if (ChildContent is null) { builder.AddAttribute(1, "for", _fieldId); } builder.AddMultipleAttributes(2, AdditionalAttributes); builder.AddContent(3, _displayName); builder.AddContent(4, ChildContent); builder.CloseElement(); } }