ASP.NET Core Blazor data binding

By Luke Latham and Daniel Roth

Razor components provide data binding features via an HTML element attribute named @bind with a field, property, or Razor expression value.

The following example binds the CurrentValue property to the text box's value:

<input @bind="CurrentValue" />

@code {
    private string CurrentValue { get; set; }
}

When the text box loses focus, the property's value is updated.

The text box is updated in the UI only when the component is rendered, not in response to changing the property's value. Since components render themselves after event handler code executes, property updates are usually reflected in the UI immediately after an event handler is triggered.

Using @bind with the CurrentValue property (<input @bind="CurrentValue" />) is essentially equivalent to the following:

<input value="@CurrentValue"
    @onchange="@((ChangeEventArgs __e) => CurrentValue = 
        __e.Value.ToString())" />
        
@code {
    private string CurrentValue { get; set; }
}

When the component is rendered, the value of the input element comes from the CurrentValue property. When the user types in the text box and changes element focus, the onchange event is fired and the CurrentValue property is set to the changed value. In reality, the code generation is more complex because @bind handles cases where type conversions are performed. In principle, @bind associates the current value of an expression with a value attribute and handles changes using the registered handler.

Bind a property or field on other events by also including an @bind:event attribute with an event parameter. The following example binds the CurrentValue property on the oninput event:

<input @bind="CurrentValue" @bind:event="oninput" />

@code {
    private string CurrentValue { get; set; }
}

Unlike onchange, which fires when the element loses focus, oninput fires when the value of the text box changes.

Use @bind-{ATTRIBUTE} with @bind-{ATTRIBUTE}:event syntax to bind element attributes other than value. In the following example, the paragraph's style is updated when the paragraphStyle value changes:

@page "/binding-example"

<p>
    <input type="text" @bind="paragraphStyle" />
</p>

<p @bind-style="paragraphStyle" @bind-style:event="onchange">
    Blazorify the app!
</p>

@code {
    private string paragraphStyle = "color:red";
}

Attribute binding is case sensitive:

  • @bind is valid.
  • @Bind and @BIND are invalid.

Unparsable values

When a user provides an unparsable value to a databound element, the unparsable value is automatically reverted to its previous value when the bind event is triggered.

Consider the following scenario:

  • An <input> element is bound to an int type with an initial value of 123:

    <input @bind="MyProperty" />
    
    @code {
        [Parameter]
        public int MyProperty { get; set; } = 123;
    }
    
  • The user updates the value of the element to 123.45 in the page and changes the element focus.

In the preceding scenario, the element's value is reverted to 123. When the value 123.45 is rejected in favor of the original value of 123, the user understands that their value wasn't accepted.

By default, binding applies to the element's onchange event (@bind="{PROPERTY OR FIELD}"). Use @bind="{PROPERTY OR FIELD}" @bind:event={EVENT} to trigger binding on a different event. For the oninput event (@bind:event="oninput"), the reversion occurs after any keystroke that introduces an unparsable value. When targeting the oninput event with an int-bound type, a user is prevented from typing a . character. A . character is immediately removed, so the user receives immediate feedback that only whole numbers are permitted. There are scenarios where reverting the value on the oninput event isn't ideal, such as when the user should be allowed to clear an unparsable <input> value. Alternatives include:

  • Don't use the oninput event. Use the default onchange event (only specify @bind="{PROPERTY OR FIELD}"), where an invalid value isn't reverted until the element loses focus.
  • Bind to a nullable type, such as int? or string, and provide custom logic to handle invalid entries.
  • Use a form validation component, such as InputNumber<TValue> or InputDate<TValue>. Form validation components have built-in support to manage invalid inputs. Form validation components:
    • Permit the user to provide invalid input and receive validation errors on the associated EditContext.
    • Display validation errors in the UI without interfering with the user entering additional webform data.

Format strings

Data binding works with DateTime format strings using @bind:format. Other format expressions, such as currency or number formats, aren't available at this time.

<input @bind="StartDate" @bind:format="yyyy-MM-dd" />

@code {
    [Parameter]
    public DateTime StartDate { get; set; } = new DateTime(2020, 1, 1);
}

In the preceding code, the <input> element's field type (type) defaults to text. @bind:format is supported for binding the following .NET types:

The @bind:format attribute specifies the date format to apply to the value of the <input> element. The format is also used to parse the value when an onchange event occurs.

Specifying a format for the date field type isn't recommended because Blazor has built-in support to format dates. In spite of the recommendation, only use the yyyy-MM-dd date format for binding to work correctly if a format is supplied with the date field type:

<input type="date" @bind="StartDate" @bind:format="yyyy-MM-dd">

Parent-to-child binding with component parameters

Binding recognizes component parameters, where @bind-{PROPERTY} can bind a property value from a parent component down to a child component. Binding from a child to a parent is covered in the Child-to-parent binding with chained bind section.

The following child component (ChildComponent) has a Year component parameter and YearChanged callback:

<h2>Child Component</h2>

<p>Year: @Year</p>

@code {
    [Parameter]
    public int Year { get; set; }

    [Parameter]
    public EventCallback<int> YearChanged { get; set; }
}

The EventCallback<TValue> must be named as the component parameter name followed by the Changed suffix ({PARAMETER NAME}Changed), YearChanged in the preceding example. For more information on EventCallback<TValue>, see ASP.NET Core Blazor event handling.

The following parent component uses:

  • ChildComponent and binds the ParentYear parameter from the parent to the Year parameter on the child component.
  • The onclick event is used to trigger the ChangeTheYear method. For more information, see ASP.NET Core Blazor event handling.
@page "/ParentComponent"

<h1>Parent Component</h1>

<p>ParentYear: @ParentYear</p>

<ChildComponent @bind-Year="ParentYear" />

<button class="btn btn-primary" @onclick="ChangeTheYear">
    Change Year to 1986
</button>

@code {
    [Parameter]
    public int ParentYear { get; set; } = 1978;

    private void ChangeTheYear()
    {
        ParentYear = 1986;
    }
}

Loading the ParentComponent produces the following markup:

<h1>Parent Component</h1>

<p>ParentYear: 1978</p>

<h2>Child Component</h2>

<p>Year: 1978</p>

If the value of the ParentYear property is changed by selecting the button in the ParentComponent, the Year property of the ChildComponent is updated. The new value of Year is rendered in the UI when the ParentComponent is rerendered:

<h1>Parent Component</h1>

<p>ParentYear: 1986</p>

<h2>Child Component</h2>

<p>Year: 1986</p>

The Year parameter is bindable because it has a companion YearChanged event that matches the type of the Year parameter.

By convention, <ChildComponent @bind-Year="ParentYear" /> is essentially equivalent to writing:

<ChildComponent @bind-Year="ParentYear" @bind-Year:event="YearChanged" />

In general, a property can be bound to a corresponding event handler by including an @bind-{PROPRETY}:event attribute. For example, the property MyProp can be bound to MyEventHandler using the following two attributes:

<MyComponent @bind-MyProp="MyValue" @bind-MyProp:event="MyEventHandler" />

Child-to-parent binding with chained bind

A common scenario is chaining a data-bound parameter to a page element in the component's output. This scenario is called a chained bind because multiple levels of binding occur simultaneously.

A chained bind can't be implemented with @bind syntax in the page's element. The event handler and value must be specified separately. A parent component, however, can use @bind syntax with the component's parameter.

The following PasswordField component (PasswordField.razor):

  • Sets an <input> element's value to a Password property.
  • Exposes changes of the Password property to a parent component with an EventCallback.
  • Uses the onclick event to trigger the ToggleShowPassword method. For more information, see ASP.NET Core Blazor event handling.
<h1>Child Component</h1>

Password: 

<input @oninput="OnPasswordChanged" 
       required 
       type="@(showPassword ? "text" : "password")" 
       value="@Password" />

<button class="btn btn-primary" @onclick="ToggleShowPassword">
    Show password
</button>

@code {
    private bool showPassword;

    [Parameter]
    public string Password { get; set; }

    [Parameter]
    public EventCallback<string> PasswordChanged { get; set; }

    private Task OnPasswordChanged(ChangeEventArgs e)
    {
        Password = e.Value.ToString();

        return PasswordChanged.InvokeAsync(Password);
    }

    private void ToggleShowPassword()
    {
        showPassword = !showPassword;
    }
}

The PasswordField component is used in another component:

@page "/ParentComponent"

<h1>Parent Component</h1>

<PasswordField @bind-Password="password" />

@code {
    private string password;
}

To perform checks or trap errors on the password in the preceding example:

  • Create a backing field for Password (password in the following example code).
  • Perform the checks or trap errors in the Password setter.

The following example provides immediate feedback to the user if a space is used in the password's value:

<h1>Child Component</h1>

Password: 

<input @oninput="OnPasswordChanged" 
       required 
       type="@(showPassword ? "text" : "password")" 
       value="@Password" />

<button class="btn btn-primary" @onclick="ToggleShowPassword">
    Show password
</button>

<span class="text-danger">@validationMessage</span>

@code {
    private bool showPassword;
    private string password;
    private string validationMessage;

    [Parameter]
    public string Password
    {
        get { return password ?? string.Empty; }
        set
        {
            if (password != value)
            {
                if (value.Contains(' '))
                {
                    validationMessage = "Spaces not allowed!";
                }
                else
                {
                    password = value;
                    validationMessage = string.Empty;
                }
            }
        }
    }

    [Parameter]
    public EventCallback<string> PasswordChanged { get; set; }

    private Task OnPasswordChanged(ChangeEventArgs e)
    {
        Password = e.Value.ToString();

        return PasswordChanged.InvokeAsync(Password);
    }

    private void ToggleShowPassword()
    {
        showPassword = !showPassword;
    }
}

Additional resources