jbmintjb
jbmintjb

Reputation: 133

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.

Upvotes: 1

Views: 1279

Answers (2)

lonix
lonix

Reputation: 20591

Assuming you have a custom component, or are willing to wrap the input as a custom component, then there's a way to do this without reflection.

Component.razor

@inherits InputText

...markup...

@code {
  public string FieldName => base.FieldIdentifier.FieldName;
}

Parent.razor

<Component @ref=_component ... />
...other markup...

@code {
  private EditContext _editContext;  // create this in OnInitialized
  private Component _component;

  //...

  private void Foo() {
   var fieldName       = _component.FieldName;
   var fieldIdentifier = _editContext.Field(_component.FieldName);
  }

}

Upvotes: 1

MrC aka Shaun Curtis
MrC aka Shaun Curtis

Reputation: 30046

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;
}

Upvotes: 5

Related Questions