File: Forms\InputSelect.cs
Web Access
Project: src\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.Diagnostics.CodeAnalysis;
using System.Globalization;
using Microsoft.AspNetCore.Components.Rendering;
 
namespace Microsoft.AspNetCore.Components.Forms;
 
/// <summary>
/// A dropdown selection component.
/// </summary>
public class InputSelect<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TValue> : InputBase<TValue>
{
    private readonly bool _isMultipleSelect;
 
    /// <summary>
    /// Constructs an instance of <see cref="InputSelect{TValue}"/>.
    /// </summary>
    public InputSelect()
    {
        _isMultipleSelect = typeof(TValue).IsArray;
    }
 
    /// <summary>
    /// Gets or sets the child content to be rendering inside the select element.
    /// </summary>
    [Parameter] public RenderFragment? ChildContent { get; set; }
 
    /// <summary>
    /// Gets or sets the <c>select</c> <see cref="ElementReference"/>.
    /// <para>
    /// May be <see langword="null"/> if accessed before the component is rendered.
    /// </para>
    /// </summary>
    [DisallowNull] public ElementReference? Element { get; protected set; }
 
    /// <inheritdoc />
    protected override void BuildRenderTree(RenderTreeBuilder builder)
    {
        builder.OpenElement(0, "select");
        builder.AddMultipleAttributes(1, AdditionalAttributes);
        builder.AddAttributeIfNotNullOrEmpty(2, "name", NameAttributeValue);
        builder.AddAttributeIfNotNullOrEmpty(3, "class", CssClass);
        builder.AddAttribute(4, "multiple", _isMultipleSelect);
 
        if (_isMultipleSelect)
        {
            builder.AddAttribute(5, "value", BindConverter.FormatValue(CurrentValue)?.ToString());
            builder.AddAttribute(6, "onchange", EventCallback.Factory.CreateBinder<string?[]?>(this, SetCurrentValueAsStringArray, default));
            builder.SetUpdatesAttributeName("value");
        }
        else
        {
            builder.AddAttribute(7, "value", CurrentValueAsString);
            builder.AddAttribute(8, "onchange", EventCallback.Factory.CreateBinder<string?>(this, __value => CurrentValueAsString = __value, default));
            builder.SetUpdatesAttributeName("value");
        }
 
        builder.AddElementReferenceCapture(9, __selectReference => Element = __selectReference);
        builder.AddContent(10, ChildContent);
        builder.CloseElement();
    }
 
    /// <inheritdoc />
    protected override bool TryParseValueFromString(string? value, [MaybeNullWhen(false)] out TValue result, [NotNullWhen(false)] out string? validationErrorMessage)
        => this.TryParseSelectableValueFromString(value, out result, out validationErrorMessage);
 
    /// <inheritdoc />
    protected override string? FormatValueAsString(TValue? value)
    {
        // We special-case bool values because BindConverter reserves bool conversion for conditional attributes.
        if (typeof(TValue) == typeof(bool))
        {
            return (bool)(object)value! ? "true" : "false";
        }
        else if (typeof(TValue) == typeof(bool?))
        {
            return value is not null && (bool)(object)value ? "true" : "false";
        }
 
        return base.FormatValueAsString(value);
    }
 
    private void SetCurrentValueAsStringArray(string?[]? value)
    {
        CurrentValue = BindConverter.TryConvertTo<TValue>(value, CultureInfo.CurrentCulture, out var result)
            ? result
            : default;
    }
}