Search code examples
c#blazorblazor-webassembly

Blazor Binding - Get underlying field name


If I have a basic <input /> field in a razor page, I bind it using @bind-value="MyModel.Name", is there anyway at all to get to know what that input's binding field name is? (i.e I want know it's bound to a field called Name within MyModel.


Solution

  • There is no underlying field. It's bound directly to the assigned field.

    Razor is markup. The Razor markup gets compiled into a c# class.

    This:

    @page "/"
    <input type="number" @bind-value=this.value />
    @code {
        private int value;
    }
    

    gets compiled into this Render Fragment:

    protected override void BuildRenderTree(Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder __builder)
    {
        __builder.OpenElement(2, "input");
        __builder.AddAttribute(3, "type", "number");
        __builder.AddAttribute(4, "value", Microsoft.AspNetCore.Components.BindConverter.FormatValue(this.value, culture: global::System.Globalization.CultureInfo.InvariantCulture));
        __builder.AddAttribute(5, "onchange", Microsoft.AspNetCore.Components.EventCallback.Factory.CreateBinder(this, __value => this.value = __value, this.value, culture: global::System.Globalization.CultureInfo.InvariantCulture));
        __builder.SetUpdatesAttributeName("value");
        __builder.CloseElement();
    }
    

    Update

    You can wrap the input up as a component and then use some expression code to get the actual fieldname or the object/fieldname you are binding to like this:

    @using System.Linq;
    @using System.Linq.Expressions;
    
    <input value="@this.Value" @oninput=this.OnValueChange class="@this.CssClass" />
    
    <div class="p-2">
        FieldName: @this.FieldName
    </div>
    
    @code {
        private string? FieldName;
    
        [Parameter] public string? Value { get; set; }
        [Parameter] public EventCallback<string> ValueChanged { get; set; }
        [Parameter] public Expression<Func<string>> ValueExpression { get; set; } = default!;
        [Parameter] public string? CssClass { get; set; }
    
        private void OnValueChange(ChangeEventArgs e)
        {
            this.ValueChanged.InvokeAsync(e.Value?.ToString() ?? null);
        }
    
        protected override void OnInitialized()
        {
            this.FieldName = ParseFieldName();
        }
    
        private string ParseFieldName()
        {
            if (this.ValueExpression is null)
                throw new ArgumentException($"You must provide a ValueExpression for this component.");
    
            var accessorBody = this.ValueExpression.Body;
    
            if (accessorBody is UnaryExpression unaryExpression
                && unaryExpression.NodeType == ExpressionType.Convert
                && unaryExpression.Type == typeof(object))
            {
                accessorBody = unaryExpression.Operand;
            }
    
            if (!(accessorBody is MemberExpression memberExpression))
                throw new ArgumentException($"The provided expression is not supported.");
    
            return memberExpression.Member.Name;
        }
    
        private void ParseAccessor(out object model, out string fieldName)
        {
            if (this.ValueExpression is null)
                throw new ArgumentException($"You must provide a ValueExpression for this component.");
    
            var accessorBody = this.ValueExpression.Body;
    
            if (accessorBody is UnaryExpression unaryExpression
                && unaryExpression.NodeType == ExpressionType.Convert
                && unaryExpression.Type == typeof(object))
            {
                accessorBody = unaryExpression.Operand;
            }
    
            if (!(accessorBody is MemberExpression memberExpression))
                throw new ArgumentException($"The provided expression is not supported.");
    
            fieldName = memberExpression.Member.Name;
    
            if (memberExpression.Expression is ConstantExpression constantExpression)
            {
                if (constantExpression.Value is null)
                {
                    throw new ArgumentException("The provided expression must evaluate to a non-null value.");
                }
                model = constantExpression.Value;
                return;
            }
    
            if (memberExpression.Expression != null)
            {
                var modelLambda = Expression.Lambda(memberExpression.Expression);
                var modelLambdaCompiled = (Func<object?>)modelLambda.Compile();
                var result = modelLambdaCompiled();
                if (result is null)
                {
                    throw new ArgumentException("The provided expression must evaluate to a non-null value.");
                }
                model = result;
                return;
            }
    
            throw new ArgumentException($"The provided expression is not supported.");
        }
    }
    

    My test page looks like this:

    @page "/"
    
    <PageTitle>Index</PageTitle>
    
    <h1>Hello, world!</h1>
    
    Welcome to your new app.
    <div class="p-2">
    <TextInput @bind-Value="@mySpecialValue"/>
    </div>
    
    <div class="p-2">
        Value : @mySpecialValue
    </div>
    
    @code {
        public string? mySpecialValue;
    
        public void SetValue(string e)
           => mySpecialValue = e;
    }