Enlace de datos de ASP.NET Core Blazor

En este artículo se explican las características de enlace de datos para componentes Razor y elementos Document Object Model (DOM) en aplicaciones de Blazor.

Los componentes de Razor proporcionan características de enlace de datos con el atributo de directiva @bindRazor con un valor de campo, propiedad o expresión de Razor.

En el ejemplo siguiente se enlaza:

  • Un valor del elemento <input> al campo inputValue de C#.
  • Un segundo valor del elemento <input> a la propiedad InputValue de C#.

Cuando un elemento <input> pierde el foco, se actualiza su campo o propiedad enlazada.

Pages/Bind.razor:

@page "/bind"

<p>
    <input @bind="inputValue" />
</p>

<p>
    <input @bind="InputValue" />
</p>

<ul>
    <li><code>inputValue</code>: @inputValue</li>
    <li><code>InputValue</code>: @InputValue</li>
</ul>

@code {
    private string? inputValue;

    private string? InputValue { get; set; }
}

El cuadro de texto se actualiza en la interfaz de usuario solo cuando se representa el componente, no en respuesta al cambio de valor del campo o la propiedad. Como los componentes se representan a sí mismos después de que se ejecute el código del controlador de eventos, las actualizaciones de campos y propiedades normalmente se reflejan en la interfaz de usuario justo después de que se desencadene un controlador de eventos.

Como demostración de cómo el enlace de datos se compone en HTML, en el ejemplo siguiente se enlaza la propiedad InputValue al segundo <input> del elemento value y a los atributos onchange. El segundo elemento <input> del ejemplo siguiente es una demostración de concepto y no está pensado para sugerir cómo debe enlazar los datos en los componentes de Razor .

Pages/BindTheory.razor:

@page "/bind-theory"

<p>
    <label>
        Normal Blazor binding: 
        <input @bind="InputValue" />
    </label>
</p>

<p>
    <label>
        Demonstration of equivalent HTML binding: 
        <input value="@InputValue"
            @onchange="@((ChangeEventArgs __e) => InputValue = __e?.Value?.ToString())" />
    </label>
</p>

<p>
    <code>InputValue</code>: @InputValue
</p>

@code {
    private string? InputValue { get; set; }
}

Cuando se representa el componente BindTheory, el valor value del elemento <input> de demostración HTML procede de la propiedad InputValue. Cuando el usuario especifica un valor en el cuadro de texto y cambia el foco del elemento, se desencadena el evento onchange y la propiedad InputValue se establece en el valor modificado. En realidad, la generación de código es más compleja, porque @bind administra los casos en los que se realizan conversiones de tipos. En general, @bind asocia el valor actual de una expresión a un atributo value y controla los cambios mediante el controlador registrado.

Enlace una propiedad o un campo en otros eventos de Document Object Model (DOM) mediante la inclusión de un atributo @bind:event="{EVENT}" con un evento DOM para el marcador de posición {EVENT}. En el siguiente ejemplo se enlaza la propiedad InputValue al valor del elemento <input> cuando se desencadena el evento oninput del elemento. A diferencia del evento onchange, que se activa cuando el elemento pierde el foco, oninput se desencadena cuando cambia el valor del cuadro de texto.

Page/BindEvent.razor:

@page "/bind-event"

<p>
    <input @bind="InputValue" @bind:event="oninput" />
</p>

<p>
    <code>InputValue</code>: @InputValue
</p>

@code {
    private string? InputValue { get; set; }
}

El enlace de atributos de Razor distingue mayúsculas de minúsculas:

  • @bind y @bind:event son válidos.
  • @Bind/@Bind:Event (letras mayúsculas B y E) o @BIND/@BIND:EVENT (todas las letras mayúsculas) no son válidos.

Selección de varias opciones con elementos <select>

El enlace admite la selección de la opción multiple con los elementos <select>. El evento @onchange proporciona una matriz de los elementos seleccionados mediante argumentos de evento (ChangeEventArgs). El valor se debe enlazar a un tipo de matriz.

Pages/BindMultipleInput.razor:

@page "/bind-multiple-input"

<h1>Bind Multiple <code>input</code>Example</h1>

<p>
    <label>
        Select one or more cars: 
        <select @onchange="SelectedCarsChanged" multiple>
            <option value="audi">Audi</option>
            <option value="jeep">Jeep</option>
            <option value="opel">Opel</option>
            <option value="saab">Saab</option>
            <option value="volvo">Volvo</option>
        </select>
    </label>
</p>

<p>
    Selected Cars: @string.Join(", ", SelectedCars)
</p>

<p>
    <label>
        Select one or more cities: 
        <select @bind="SelectedCities" multiple>
            <option value="bal">Baltimore</option>
            <option value="la">Los Angeles</option>
            <option value="pdx">Portland</option>
            <option value="sf">San Francisco</option>
            <option value="sea">Seattle</option>
        </select>
    </label>
</p>

<span>
    Selected Cities: @string.Join(", ", SelectedCities)
</span>

@code {
    public string[] SelectedCars { get; set; } = new string[] { };
    public string[] SelectedCities { get; set; } = new[] { "bal", "sea" };

    void SelectedCarsChanged(ChangeEventArgs e)
    {
        if (e.Value is not null)
        {
            SelectedCars = (string[])e.Value;
        }
    }
}

Para obtener información sobre cómo se controlan las cadenas vacías y los valores null en el enlace de datos, vea la sección Enlace de opciones del elemento <select> a valores null de objetos de C#.

Enlace de opciones del elemento <select> a valores null de un objeto C#

No hay ninguna manera razonable de representar un valor de opción de elemento <select> como valor null de objeto C#, por los siguientes motivos:

  • Los atributos HTML no pueden tener valores null. El equivalente más cercano a null en HTML es la ausencia del atributo value de HTML del elemento <option>.
  • Al seleccionar un <option> sin ningún atributo value, el explorador trata el valor como contenido de texto de ese elemento de <option>.

El marco de Blazor no intenta suprimir el comportamiento predeterminado porque implicaría lo siguiente:

  • Crear una cadena de soluciones alternativas de casos especiales en el marco.
  • Hacer cambios importantes en el comportamiento actual del marco.

El null más plausible equivalente en HTML es una cadena vacíavalue. El marco Blazor controla null en las conversiones de cadenas vacías para el enlace bidireccional al valor de <select>.

Valores no analizables

Cuando un usuario proporciona un valor no analizable a un elemento enlazado a datos, el valor no analizable se revierte automáticamente a su valor anterior al desencadenar el evento de enlace.

Considere el siguiente componente, donde un elemento <input> se enlaza a un tipo int con un valor inicial de 123.

Pages/UnparsableValues.razor:

@page "/unparseable-values"

<p>
    <input @bind="inputValue" />
</p>

<p>
    <code>inputValue</code>: @inputValue
</p>

@code {
    private int inputValue = 123;
}

De forma predeterminada, el enlace se aplica al evento onchange del elemento. Si el usuario actualiza el valor de la entrada del cuadro de texto a 123.45 y cambia el foco, el valor del elemento se revierte a 123 cuando onchange se activa. Cuando el valor 123.45 se rechaza en favor del valor original de 123, el usuario entiende que no se ha aceptado su valor.

En el caso del evento oninput (@bind:event="oninput"), la reversión del valor se produce después de cualquier pulsación de tecla que especifique un valor no analizable. Cuando el destino es el evento oninput con un tipo enlazado a int, se impide que el usuario escriba el carácter de punto (.). El carácter de punto (.) se elimina de forma inmediata, por lo que el usuario recibe un comentario al instante informándole de que solo se permiten números enteros. Hay escenarios en los que la reversión del valor del evento oninput no es ideal, por ejemplo, cuando se debe permitir que el usuario borre un valor <input> que no se puede analizar. De forma alternativa, puede hacer lo siguiente:

  • No use el evento oninput. Use el evento onchange predeterminado para que no se revierta un valor no válido hasta que el elemento pierda el foco.
  • Establezca un enlace a un tipo que acepte valores NULL, como int? o string, y proporcione la lógica personalizada de los descriptores de acceso get y set para administrar las entradas no válidas.
  • Use un componente de validación de formularios, como InputNumber<TValue> o InputDate<TValue>. Los componentes de validación de formularios ofrecen compatibilidad integrada para administrar entradas no válidas. Componentes de validación de formularios:
    • Permiten que el usuario proporcione entradas no válidas y reciba errores de validación en la clase EditContext asociada.
    • Muestran errores de validación en la interfaz de usuario sin impedir que el usuario escriba datos adicionales en el formulario web.

Cadenas de formato

El enlace de datos funciona con una única cadena de formato DateTime mediante @bind:format="{FORMAT STRING}", donde el marcador de posición {FORMAT STRING} es la cadena de formato. Otras expresiones de formato, como los formatos de moneda o número, no están disponibles en este momento, pero podrían agregarse en una versión futura.

Pages/DateBinding.razor:

@page "/date-binding"

<p>
    <label>
        <code>yyyy-MM-dd</code> format:
        <input @bind="startDate" @bind:format="yyyy-MM-dd" />
    </label>
</p>

<p>
    <code>startDate</code>: @startDate
</p>

@code {
    private DateTime startDate = new(2020, 1, 1);
}

En el código anterior, el tipo de campo (atributo <input>) del elemento type tiene como valor predeterminado text.

Se aceptan los valores NULL System.DateTime y System.DateTimeOffset:

private DateTime? date;
private DateTimeOffset? dateOffset;

No se recomienda especificar un formato para el tipo de campo date porque Blazor tiene compatibilidad integrada para dar formato a las fechas. A pesar de la recomendación, use solo el formato de fecha yyyy-MM-dd para que el enlace funcione correctamente si se proporciona un formato con el tipo de campo date:

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

Formatos de enlace personalizados

Los descriptores de acceso get y set de C# se pueden usar para crear un comportamiento de formato de enlace personalizado, como muestra el siguiente componente DecimalBinding. El componente enlaza un decimal positivo o negativo con hasta tres posiciones decimales a un elemento <input> por medio de una propiedad string (DecimalValue).

Pages/DecimalBinding.razor:

@page "/decimal-binding"
@using System.Globalization

<p>
    <label>
        Decimal value (±0.000 format):
        <input @bind="DecimalValue" />
    </label>
</p>

<p>
    <code>decimalValue</code>: @decimalValue
</p>

@code {
    private decimal decimalValue = 1.1M;
    private NumberStyles style = 
        NumberStyles.AllowDecimalPoint | NumberStyles.AllowLeadingSign;
    private CultureInfo culture = CultureInfo.CreateSpecificCulture("en-US");

    private string DecimalValue
    {
        get => decimalValue.ToString("0.000", culture);
        set
        {
            if (Decimal.TryParse(value, style, culture, out var number))
            {
                decimalValue = Math.Round(number, 3);
            }
        }
    }
}

Enlace con parámetros de componente

Un escenario habitual es enlazar una propiedad de un componente secundario a una propiedad de su elemento primario. Este escenario se denomina enlace encadenado porque se producen varios niveles de enlace simultáneamente.

Los parámetros de componente permiten el enlace de propiedades de un componente primario con la sintaxis @bind-{PROPERTY}, donde el marcador de posición {PROPERTY} es la propiedad que se va a enlazar.

No se pueden implementar enlaces encadenados con la sintaxis @bind en el componente secundario. Es necesario especificar un controlador de eventos y un valor por separado para permitir la actualización de la propiedad en el elemento primario desde el componente secundario.

El componente primario sigue aprovechando la sintaxis @bind para configurar el enlace de datos con el componente secundario.

El siguiente componente ChildBind tiene un parámetro de componente Year y un elemento EventCallback<TValue>. Por convención, el elemento EventCallback<TValue> del parámetro se debe denominar como el nombre de parámetro del componente con el sufijo "Changed". La sintaxis de nomenclatura es {PARAMETER NAME}Changed, donde el marcador de posición {PARAMETER NAME} es el nombre del parámetro. En el ejemplo siguiente, el elemento EventCallback<TValue> se denomina YearChanged.

EventCallback.InvokeAsync invoca al delegado asociado al enlace con el argumento proporcionado y envía una notificación de evento de la propiedad modificada.

Shared/ChildBind.razor:

<div class="card bg-light mt-3" style="width:18rem ">
    <div class="card-body">
        <h3 class="card-title">ChildBind Component</h3>
        <p class="card-text">
            Child <code>Year</code>: @Year
        </p>
        <button @onclick="UpdateYearFromChild">Update Year from Child</button>
    </div>
</div>

@code {
    private Random r = new();

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

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

    private async Task UpdateYearFromChild()
    {
        await YearChanged.InvokeAsync(r.Next(1950, 2021));
    }
}

Para más información sobre los eventos y EventCallback<TValue>, vea la sección EventCallback del artículo Control de eventos de Blazor en ASP.NET Core.

En el siguiente componente Parent, el campo year se enlaza al parámetro Year del componente secundario. El parámetro Year se puede enlazar porque tiene un evento YearChanged complementario que coincide con el tipo del parámetro Year.

Pages/Parent.razor:

@page "/Parent"

<h1>Parent Component</h1>

<p>Parent <code>year</code>: @year</p>

<button @onclick="UpdateYear">Update Parent <code>year</code></button>

<ChildBind @bind-Year="year" />

@code {
    private Random r = new();
    private int year = 1979;

    private void UpdateYear()
    {
        year = r.Next(1950, 2021);
    }
}

Por convención, una propiedad se puede enlazar a un controlador de eventos correspondiente si se incluye un atributo @bind-{PROPERTY}:event asignado al controlador, donde el marcador de posición {PROPERTY} es la propiedad. <ChildBind @bind-Year="year" /> equivale a escribir lo siguiente:

<ChildBind @bind-Year="year" @bind-Year:event="YearChanged" />

En un ejemplo real más sofisticado, el siguiente componente PasswordEntry:

  • Establece el valor del elemento <input> en un campo password.
  • Expone los cambios de una propiedad Password a un componente primario con un objeto EventCallback que pasa el valor actual del campo password del elemento secundario como su argumento.
  • Usa el evento onclick para desencadenar el método ToggleShowPassword. Para más información, vea Control de eventos de Blazor en ASP.NET Core.

Shared/PasswordEntry.razor:

<div class="card bg-light mt-3" style="width:22rem ">
    <div class="card-body">
        <h3 class="card-title">Password Component</h3>
        <p class="card-text">
            <label>
                Password:
                <input @oninput="OnPasswordChanged"
                       required
                       type="@(showPassword ? "text" : "password")"
                       value="@password" />
            </label>
        </p>
        <button class="btn btn-primary" @onclick="ToggleShowPassword">
            Show password
        </button>
    </div>
</div>

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

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

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

    private async Task OnPasswordChanged(ChangeEventArgs e)
    {
        password = e?.Value?.ToString();

        await PasswordChanged.InvokeAsync(password);
    }

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

El componente PasswordEntry se usa en otro componente, como el siguiente ejemplo de componente PasswordBinding.

Pages/PasswordBinding.razor:

@page "/password-binding"

<h1>Password Binding</h1>

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

<p>
    <code>password</code>: @password
</p>

@code {
    private string password = "Not set";
}

Cuando el componente PasswordBinding se representa inicialmente, el valor password de Not set se muestra en la interfaz de usuario. Después de la representación inicial, el valor de password refleja los cambios realizados en el valor del parámetro de componente Password del componente PasswordEntry.

Nota

En el ejemplo anterior se enlaza la contraseña de forma unidireccional desde el componente PasswordEntry secundario al componente PasswordBinding primario. El enlace bidireccional no es un requisito en este escenario si el objetivo es que la aplicación tenga un componente de entrada de contraseña compartida para reutilizarlo en la aplicación que simplemente pasa la contraseña al elemento primario. Para obtener un enfoque que permita el enlace bidireccional sin escribir directamente en el parámetro del componente secundario, vea el ejemplo del componente NestedChild en la sección Enlace entre más de dos componentes de este artículo.

Realice las comprobaciones o detecte los errores en el controlador. El siguiente componente PasswordEntry revisado informa de inmediato al usuario si se emplea un espacio en el valor de la contraseña.

Shared/PasswordEntry.razor:

<div class="card bg-light mt-3" style="width:22rem ">
    <div class="card-body">
        <h3 class="card-title">Password Component</h3>
        <p class="card-text">
            <label>
                Password:
                <input @oninput="OnPasswordChanged"
                       required
                       type="@(showPassword ? "text" : "password")"
                       value="@password" />
            </label>
            <span class="text-danger">@validationMessage</span>
        </p>
        <button class="btn btn-primary" @onclick="ToggleShowPassword">
            Show password
        </button>
    </div>
</div>

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

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

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

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

        if (password != null && password.Contains(' '))
        {
            validationMessage = "Spaces not allowed!";

            return Task.CompletedTask;
        }
        else
        {
            validationMessage = string.Empty;

            return PasswordChanged.InvokeAsync(password);
        }
    }

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

Enlace entre más de dos componentes

Puede enlazar parámetros a través de cualquier número de componentes anidados, pero debe respetar el flujo unidireccional de los datos:

  • Las notificaciones de cambio suben en la jerarquía.
  • Los valores nuevos de parámetro bajan en la jerarquía.

Un enfoque común y recomendado es almacenar solo los datos subyacentes en el componente primario para evitar la confusión sobre el estado que se debe actualizar, como se muestra en el ejemplo siguiente.

Pages/Parent.razor:

@page "/parent"

<h1>Parent Component</h1>

<p>Parent Message: <b>@parentMessage</b></p>

<p>
    <button @onclick="ChangeValue">Change from Parent</button>
</p>

<NestedChild @bind-ChildMessage="parentMessage" />

@code {
    private string parentMessage = "Initial value set in Parent";

    private void ChangeValue()
    {
        parentMessage = $"Set in Parent {DateTime.Now}";
    }
}

Shared/NestedChild.razor:

<div class="border rounded m-1 p-1">
    <h2>Child Component</h2>

    <p>Child Message: <b>@ChildMessage</b></p>

    <p>
        <button @onclick="ChangeValue">Change from Child</button>
    </p>

    <NestedGrandchild @bind-GrandchildMessage="BoundValue" />
</div>

@code {
    [Parameter]
    public string? ChildMessage { get; set; }

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

    private string BoundValue
    {
        get => ChildMessage ?? string.Empty;
        set => ChildMessageChanged.InvokeAsync(value);
    }

    private async Task ChangeValue()
    {
        await ChildMessageChanged.InvokeAsync(
            $"Set in Child {DateTime.Now}");
    }
}

Advertencia

Por lo general, evite crear componentes que escriban directamente en sus propios parámetros de componente. El componente anterior NestedChild usa una propiedad BoundValue en lugar de escribir directamente en su parámetro ChildMessage. Para más información, vea Componentes Razor de ASP.NET Core.

Shared/NestedGrandchild.razor:

<div class="border rounded m-1 p-1">
    <h3>Grandchild Component</h3>

    <p>Grandchild Message: <b>@GrandchildMessage</b></p>

    <p>
        <button @onclick="ChangeValue">Change from Grandchild</button>
    </p>
</div>

@code {
    [Parameter]
    public string? GrandchildMessage { get; set; }

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

    private async Task ChangeValue()
    {
        await GrandchildMessageChanged.InvokeAsync(
            $"Set in Grandchild {DateTime.Now}");
    }
}

Para obtener un enfoque alternativo adecuado a fin de compartir datos en memoria y entre componentes que no están necesariamente anidados, vea Administración de estado de Blazor en ASP.NET Core.

Recursos adicionales

Los componentes de Razor proporcionan características de enlace de datos con el atributo de directiva @bindRazor con un valor de campo, propiedad o expresión de Razor.

En el ejemplo siguiente se enlaza:

  • Un valor del elemento <input> al campo inputValue de C#.
  • Un segundo valor del elemento <input> a la propiedad InputValue de C#.

Cuando un elemento <input> pierde el foco, se actualiza su campo o propiedad enlazada.

Pages/Bind.razor:

@page "/bind"

<p>
    <input @bind="inputValue" />
</p>

<p>
    <input @bind="InputValue" />
</p>

<ul>
    <li><code>inputValue</code>: @inputValue</li>
    <li><code>InputValue</code>: @InputValue</li>
</ul>

@code {
    private string inputValue;

    private string InputValue { get; set; }
}

El cuadro de texto se actualiza en la interfaz de usuario solo cuando se representa el componente, no en respuesta al cambio de valor del campo o la propiedad. Como los componentes se representan a sí mismos después de que se ejecute el código del controlador de eventos, las actualizaciones de campos y propiedades normalmente se reflejan en la interfaz de usuario justo después de que se desencadene un controlador de eventos.

Como demostración de cómo el enlace de datos se compone en HTML, en el ejemplo siguiente se enlaza la propiedad InputValue al segundo <input> del elemento value y a los atributos onchange. El segundo elemento <input> del ejemplo siguiente es una demostración de concepto y no está pensado para sugerir cómo debe enlazar los datos en los componentes de Razor .

Pages/BindTheory.razor:

@page "/bind-theory"

<p>
    <label>
        Normal Blazor binding: 
        <input @bind="InputValue" />
    </label>
</p>

<p>
    <label>
        Demonstration of equivalent HTML binding: 
        <input value="@InputValue"
            @onchange="@((ChangeEventArgs __e) => InputValue = __e.Value.ToString())" />
    </label>
</p>

<p>
    <code>InputValue</code>: @InputValue
</p>

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

Cuando se representa el componente BindTheory, el valor value del elemento <input> de demostración HTML procede de la propiedad InputValue. Cuando el usuario especifica un valor en el cuadro de texto y cambia el foco del elemento, se desencadena el evento onchange y la propiedad InputValue se establece en el valor modificado. En realidad, la generación de código es más compleja, porque @bind administra los casos en los que se realizan conversiones de tipos. En general, @bind asocia el valor actual de una expresión a un atributo value y controla los cambios mediante el controlador registrado.

Enlace una propiedad o un campo en otros eventos de Document Object Model (DOM) mediante la inclusión de un atributo @bind:event="{EVENT}" con un evento DOM para el marcador de posición {EVENT}. En el siguiente ejemplo se enlaza la propiedad InputValue al valor del elemento <input> cuando se desencadena el evento oninput del elemento. A diferencia del evento onchange, que se activa cuando el elemento pierde el foco, oninput se desencadena cuando cambia el valor del cuadro de texto.

Page/BindEvent.razor:

@page "/bind-event"

<p>
    <input @bind="InputValue" @bind:event="oninput" />
</p>

<p>
    <code>InputValue</code>: @InputValue
</p>

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

El enlace de atributos de Razor distingue mayúsculas de minúsculas:

  • @bind y @bind:event son válidos.
  • @Bind/@Bind:Event (letras mayúsculas B y E) o @BIND/@BIND:EVENT (todas las letras mayúsculas) no son válidos.

Enlace de opciones del elemento <select> a valores null de un objeto C#

No hay ninguna manera razonable de representar un valor de opción de elemento <select> como valor null de objeto C#, por los siguientes motivos:

  • Los atributos HTML no pueden tener valores null. El equivalente más cercano a null en HTML es la ausencia del atributo value de HTML del elemento <option>.
  • Al seleccionar un <option> sin ningún atributo value, el explorador trata el valor como contenido de texto de ese elemento de <option>.

El marco de Blazor no intenta suprimir el comportamiento predeterminado porque implicaría lo siguiente:

  • Crear una cadena de soluciones alternativas de casos especiales en el marco.
  • Hacer cambios importantes en el comportamiento actual del marco.

El null más plausible equivalente en HTML es una cadena vacíavalue. El marco Blazor controla null en las conversiones de cadenas vacías para el enlace bidireccional al valor de <select>.

Valores no analizables

Cuando un usuario proporciona un valor no analizable a un elemento enlazado a datos, el valor no analizable se revierte automáticamente a su valor anterior al desencadenar el evento de enlace.

Considere el siguiente componente, donde un elemento <input> se enlaza a un tipo int con un valor inicial de 123.

Pages/UnparsableValues.razor:

@page "/unparseable-values"

<p>
    <input @bind="inputValue" />
</p>

<p>
    <code>inputValue</code>: @inputValue
</p>

@code {
    private int inputValue = 123;
}

De forma predeterminada, el enlace se aplica al evento onchange del elemento. Si el usuario actualiza el valor de la entrada del cuadro de texto a 123.45 y cambia el foco, el valor del elemento se revierte a 123 cuando onchange se activa. Cuando el valor 123.45 se rechaza en favor del valor original de 123, el usuario entiende que no se ha aceptado su valor.

En el caso del evento oninput (@bind:event="oninput"), la reversión del valor se produce después de cualquier pulsación de tecla que especifique un valor no analizable. Cuando el destino es el evento oninput con un tipo enlazado a int, se impide que el usuario escriba el carácter de punto (.). El carácter de punto (.) se elimina de forma inmediata, por lo que el usuario recibe un comentario al instante informándole de que solo se permiten números enteros. Hay escenarios en los que la reversión del valor del evento oninput no es ideal, por ejemplo, cuando se debe permitir que el usuario borre un valor <input> que no se puede analizar. De forma alternativa, puede hacer lo siguiente:

  • No use el evento oninput. Use el evento onchange predeterminado para que no se revierta un valor no válido hasta que el elemento pierda el foco.
  • Establezca un enlace a un tipo que acepte valores NULL, como int? o string, y proporcione la lógica personalizada de los descriptores de acceso get y set para administrar las entradas no válidas.
  • Use un componente de validación de formularios, como InputNumber<TValue> o InputDate<TValue>. Los componentes de validación de formularios ofrecen compatibilidad integrada para administrar entradas no válidas. Componentes de validación de formularios:
    • Permiten que el usuario proporcione entradas no válidas y reciba errores de validación en la clase EditContext asociada.
    • Muestran errores de validación en la interfaz de usuario sin impedir que el usuario escriba datos adicionales en el formulario web.

Cadenas de formato

El enlace de datos funciona con una única cadena de formato DateTime mediante @bind:format="{FORMAT STRING}", donde el marcador de posición {FORMAT STRING} es la cadena de formato. Otras expresiones de formato, como los formatos de moneda o número, no están disponibles en este momento, pero podrían agregarse en una versión futura.

Pages/DateBinding.razor:

@page "/date-binding"

<p>
    <label>
        <code>yyyy-MM-dd</code> format:
        <input @bind="startDate" @bind:format="yyyy-MM-dd" />
    </label>
</p>

<p>
    <code>startDate</code>: @startDate
</p>

@code {
    private DateTime startDate = new(2020, 1, 1);
}

En el código anterior, el tipo de campo (atributo <input>) del elemento type tiene como valor predeterminado text.

Se aceptan los valores NULL System.DateTime y System.DateTimeOffset:

private DateTime? date;
private DateTimeOffset? dateOffset;

No se recomienda especificar un formato para el tipo de campo date porque Blazor tiene compatibilidad integrada para dar formato a las fechas. A pesar de la recomendación, use solo el formato de fecha yyyy-MM-dd para que el enlace funcione correctamente si se proporciona un formato con el tipo de campo date:

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

Formatos de enlace personalizados

Los descriptores de acceso get y set de C# se pueden usar para crear un comportamiento de formato de enlace personalizado, como muestra el siguiente componente DecimalBinding. El componente enlaza un decimal positivo o negativo con hasta tres posiciones decimales a un elemento <input> por medio de una propiedad string (DecimalValue).

Pages/DecimalBinding.razor:

@page "/decimal-binding"
@using System.Globalization

<p>
    <label>
        Decimal value (±0.000 format):
        <input @bind="DecimalValue" />
    </label>
</p>

<p>
    <code>decimalValue</code>: @decimalValue
</p>

@code {
    private decimal decimalValue = 1.1M;
    private NumberStyles style = 
        NumberStyles.AllowDecimalPoint | NumberStyles.AllowLeadingSign;
    private CultureInfo culture = CultureInfo.CreateSpecificCulture("en-US");

    private string DecimalValue
    {
        get => decimalValue.ToString("0.000", culture);
        set
        {
            if (Decimal.TryParse(value, style, culture, out var number))
            {
                decimalValue = Math.Round(number, 3);
            }
        }
    }
}

Enlace con parámetros de componente

Un escenario habitual es enlazar una propiedad de un componente secundario a una propiedad de su elemento primario. Este escenario se denomina enlace encadenado porque se producen varios niveles de enlace simultáneamente.

Los parámetros de componente permiten el enlace de propiedades de un componente primario con la sintaxis @bind-{PROPERTY}, donde el marcador de posición {PROPERTY} es la propiedad que se va a enlazar.

No se pueden implementar enlaces encadenados con la sintaxis @bind en el componente secundario. Es necesario especificar un controlador de eventos y un valor por separado para permitir la actualización de la propiedad en el elemento primario desde el componente secundario.

El componente primario sigue aprovechando la sintaxis @bind para configurar el enlace de datos con el componente secundario.

El siguiente componente ChildBind tiene un parámetro de componente Year y un elemento EventCallback<TValue>. Por convención, el elemento EventCallback<TValue> del parámetro se debe denominar como el nombre de parámetro del componente con el sufijo "Changed". La sintaxis de nomenclatura es {PARAMETER NAME}Changed, donde el marcador de posición {PARAMETER NAME} es el nombre del parámetro. En el ejemplo siguiente, el elemento EventCallback<TValue> se denomina YearChanged.

EventCallback.InvokeAsync invoca al delegado asociado al enlace con el argumento proporcionado y envía una notificación de evento de la propiedad modificada.

Shared/ChildBind.razor:

<div class="card bg-light mt-3" style="width:18rem ">
    <div class="card-body">
        <h3 class="card-title">ChildBind Component</h3>
        <p class="card-text">
            Child <code>Year</code>: @Year
        </p>
        <button @onclick="UpdateYearFromChild">Update Year from Child</button>
    </div>
</div>

@code {
    private Random r = new();

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

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

    private async Task UpdateYearFromChild()
    {
        await YearChanged.InvokeAsync(r.Next(1950, 2021));
    }
}

Para más información sobre los eventos y EventCallback<TValue>, vea la sección EventCallback del artículo Control de eventos de Blazor en ASP.NET Core.

En el siguiente componente Parent, el campo year se enlaza al parámetro Year del componente secundario. El parámetro Year se puede enlazar porque tiene un evento YearChanged complementario que coincide con el tipo del parámetro Year.

Pages/Parent.razor:

@page "/Parent"

<h1>Parent Component</h1>

<p>Parent <code>year</code>: @year</p>

<button @onclick="UpdateYear">Update Parent <code>year</code></button>

<ChildBind @bind-Year="year" />

@code {
    private Random r = new();
    private int year = 1979;

    private void UpdateYear()
    {
        year = r.Next(1950, 2021);
    }
}

Por convención, una propiedad se puede enlazar a un controlador de eventos correspondiente si se incluye un atributo @bind-{PROPERTY}:event asignado al controlador, donde el marcador de posición {PROPERTY} es la propiedad. <ChildBind @bind-Year="year" /> equivale a escribir lo siguiente:

<ChildBind @bind-Year="year" @bind-Year:event="YearChanged" />

En un ejemplo real más sofisticado, el siguiente componente PasswordEntry:

  • Establece el valor del elemento <input> en un campo password.
  • Expone los cambios de una propiedad Password a un componente primario con un objeto EventCallback que pasa el valor actual del campo password del elemento secundario como su argumento.
  • Usa el evento onclick para desencadenar el método ToggleShowPassword. Para más información, vea Control de eventos de Blazor en ASP.NET Core.

Shared/PasswordEntry.razor:

<div class="card bg-light mt-3" style="width:22rem ">
    <div class="card-body">
        <h3 class="card-title">Password Component</h3>
        <p class="card-text">
            <label>
                Password:
                <input @oninput="OnPasswordChanged"
                       required
                       type="@(showPassword ? "text" : "password")"
                       value="@password" />
            </label>
        </p>
        <button class="btn btn-primary" @onclick="ToggleShowPassword">
            Show password
        </button>
    </div>
</div>

@code {
    private bool showPassword;
    private string password;

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

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

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

        await PasswordChanged.InvokeAsync(password);
    }

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

El componente PasswordEntry se usa en otro componente, como el siguiente ejemplo de componente PasswordBinding.

Pages/PasswordBinding.razor:

@page "/password-binding"

<h1>Password Binding</h1>

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

<p>
    <code>password</code>: @password
</p>

@code {
    private string password = "Not set";
}

Cuando el componente PasswordBinding se representa inicialmente, el valor password de Not set se muestra en la interfaz de usuario. Después de la representación inicial, el valor de password refleja los cambios realizados en el valor del parámetro de componente Password del componente PasswordEntry.

Nota

En el ejemplo anterior se enlaza la contraseña de forma unidireccional desde el componente PasswordEntry secundario al componente PasswordBinding primario. El enlace bidireccional no es un requisito en este escenario si el objetivo es que la aplicación tenga un componente de entrada de contraseña compartida para reutilizarlo en la aplicación que simplemente pasa la contraseña al elemento primario. Para obtener un enfoque que permita el enlace bidireccional sin escribir directamente en el parámetro del componente secundario, vea el ejemplo del componente NestedChild en la sección Enlace entre más de dos componentes de este artículo.

Realice las comprobaciones o detecte los errores en el controlador. El siguiente componente PasswordEntry revisado informa de inmediato al usuario si se emplea un espacio en el valor de la contraseña.

Shared/PasswordEntry.razor:

<div class="card bg-light mt-3" style="width:22rem ">
    <div class="card-body">
        <h3 class="card-title">Password Component</h3>
        <p class="card-text">
            <label>
                Password:
                <input @oninput="OnPasswordChanged"
                       required
                       type="@(showPassword ? "text" : "password")"
                       value="@password" />
            </label>
            <span class="text-danger">@validationMessage</span>
        </p>
        <button class="btn btn-primary" @onclick="ToggleShowPassword">
            Show password
        </button>
    </div>
</div>

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

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

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

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

        if (password.Contains(' '))
        {
            validationMessage = "Spaces not allowed!";

            return Task.CompletedTask;
        }
        else
        {
            validationMessage = string.Empty;

            return PasswordChanged.InvokeAsync(password);
        }
    }

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

Enlace entre más de dos componentes

Puede enlazar parámetros a través de cualquier número de componentes anidados, pero debe respetar el flujo unidireccional de los datos:

  • Las notificaciones de cambio suben en la jerarquía.
  • Los valores nuevos de parámetro bajan en la jerarquía.

Un enfoque común y recomendado es almacenar solo los datos subyacentes en el componente primario para evitar la confusión sobre el estado que se debe actualizar, como se muestra en el ejemplo siguiente.

Pages/Parent.razor:

@page "/parent"

<h1>Parent Component</h1>

<p>Parent Message: <b>@parentMessage</b></p>

<p>
    <button @onclick="ChangeValue">Change from Parent</button>
</p>

<NestedChild @bind-ChildMessage="parentMessage" />

@code {
    private string parentMessage = "Initial value set in Parent";

    private void ChangeValue()
    {
        parentMessage = $"Set in Parent {DateTime.Now}";
    }
}

Shared/NestedChild.razor:

<div class="border rounded m-1 p-1">
    <h2>Child Component</h2>

    <p>Child Message: <b>@ChildMessage</b></p>

    <p>
        <button @onclick="ChangeValue">Change from Child</button>
    </p>

    <NestedGrandchild @bind-GrandchildMessage="BoundValue" />
</div>

@code {
    [Parameter]
    public string ChildMessage { get; set; }

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

    private string BoundValue
    {
        get => ChildMessage;
        set => ChildMessageChanged.InvokeAsync(value);
    }

    private async Task ChangeValue()
    {
        await ChildMessageChanged.InvokeAsync(
            $"Set in Child {DateTime.Now}");
    }
}

Advertencia

Por lo general, evite crear componentes que escriban directamente en sus propios parámetros de componente. El componente anterior NestedChild usa una propiedad BoundValue en lugar de escribir directamente en su parámetro ChildMessage. Para más información, vea Componentes Razor de ASP.NET Core.

Shared/NestedGrandchild.razor:

<div class="border rounded m-1 p-1">
    <h3>Grandchild Component</h3>

    <p>Grandchild Message: <b>@GrandchildMessage</b></p>

    <p>
        <button @onclick="ChangeValue">Change from Grandchild</button>
    </p>
</div>

@code {
    [Parameter]
    public string GrandchildMessage { get; set; }

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

    private async Task ChangeValue()
    {
        await GrandchildMessageChanged.InvokeAsync(
            $"Set in Grandchild {DateTime.Now}");
    }
}

Para obtener un enfoque alternativo adecuado a fin de compartir datos en memoria y entre componentes que no están necesariamente anidados, vea Administración de estado de Blazor en ASP.NET Core.

Recursos adicionales

Los componentes de Razor proporcionan características de enlace de datos con el atributo de directiva @bindRazor con un valor de campo, propiedad o expresión de Razor.

En el ejemplo siguiente se enlaza:

  • Un valor del elemento <input> al campo inputValue de C#.
  • Un segundo valor del elemento <input> a la propiedad InputValue de C#.

Cuando un elemento <input> pierde el foco, se actualiza su campo o propiedad enlazada.

Pages/Bind.razor:

@page "/bind"

<p>
    <input @bind="inputValue" />
</p>

<p>
    <input @bind="InputValue" />
</p>

<ul>
    <li><code>inputValue</code>: @inputValue</li>
    <li><code>InputValue</code>: @InputValue</li>
</ul>

@code {
    private string inputValue;

    private string InputValue { get; set; }
}

El cuadro de texto se actualiza en la interfaz de usuario solo cuando se representa el componente, no en respuesta al cambio de valor del campo o la propiedad. Como los componentes se representan a sí mismos después de que se ejecute el código del controlador de eventos, las actualizaciones de campos y propiedades normalmente se reflejan en la interfaz de usuario justo después de que se desencadene un controlador de eventos.

Como demostración de cómo el enlace de datos se compone en HTML, en el ejemplo siguiente se enlaza la propiedad InputValue al segundo <input> del elemento value y a los atributos onchange. El segundo elemento <input> del ejemplo siguiente es una demostración de concepto y no está pensado para sugerir cómo debe enlazar los datos en los componentes de Razor .

Pages/BindTheory.razor:

@page "/bind-theory"

<p>
    <label>
        Normal Blazor binding: 
        <input @bind="InputValue" />
    </label>
</p>

<p>
    <label>
        Demonstration of equivalent HTML binding: 
        <input value="@InputValue"
            @onchange="@((ChangeEventArgs __e) => InputValue = __e.Value.ToString())" />
    </label>
</p>

<p>
    <code>InputValue</code>: @InputValue
</p>

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

Cuando se representa el componente BindTheory, el valor value del elemento <input> de demostración HTML procede de la propiedad InputValue. Cuando el usuario especifica un valor en el cuadro de texto y cambia el foco del elemento, se desencadena el evento onchange y la propiedad InputValue se establece en el valor modificado. En realidad, la generación de código es más compleja, porque @bind administra los casos en los que se realizan conversiones de tipos. En general, @bind asocia el valor actual de una expresión a un atributo value y controla los cambios mediante el controlador registrado.

Enlace una propiedad o un campo en otros eventos de Document Object Model (DOM) mediante la inclusión de un atributo @bind:event="{EVENT}" con un evento DOM para el marcador de posición {EVENT}. En el siguiente ejemplo se enlaza la propiedad InputValue al valor del elemento <input> cuando se desencadena el evento oninput del elemento. A diferencia del evento onchange, que se activa cuando el elemento pierde el foco, oninput se desencadena cuando cambia el valor del cuadro de texto.

Page/BindEvent.razor:

@page "/bind-event"

<p>
    <input @bind="InputValue" @bind:event="oninput" />
</p>

<p>
    <code>InputValue</code>: @InputValue
</p>

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

El enlace de atributos de Razor distingue mayúsculas de minúsculas:

  • @bind y @bind:event son válidos.
  • @Bind/@Bind:Event (letras mayúsculas B y E) o @BIND/@BIND:EVENT (todas las letras mayúsculas) no son válidos.

Enlace de opciones del elemento <select> a valores null de un objeto C#

No hay ninguna manera razonable de representar un valor de opción de elemento <select> como valor null de objeto C#, por los siguientes motivos:

  • Los atributos HTML no pueden tener valores null. El equivalente más cercano a null en HTML es la ausencia del atributo value de HTML del elemento <option>.
  • Al seleccionar un <option> sin ningún atributo value, el explorador trata el valor como contenido de texto de ese elemento de <option>.

El marco de Blazor no intenta suprimir el comportamiento predeterminado porque implicaría lo siguiente:

  • Crear una cadena de soluciones alternativas de casos especiales en el marco.
  • Hacer cambios importantes en el comportamiento actual del marco.

El marco Blazor no controla automáticamente null en las conversiones de cadenas vacías al intentar el enlace bidireccional al valor de <select>. Para obtener más información, vea Corrección de enlaces <select> a un valor null (dotnet/aspnetcore #23221).

Valores no analizables

Cuando un usuario proporciona un valor no analizable a un elemento enlazado a datos, el valor no analizable se revierte automáticamente a su valor anterior al desencadenar el evento de enlace.

Considere el siguiente componente, donde un elemento <input> se enlaza a un tipo int con un valor inicial de 123.

Pages/UnparsableValues.razor:

@page "/unparseable-values"

<p>
    <input @bind="inputValue" />
</p>

<p>
    <code>inputValue</code>: @inputValue
</p>

@code {
    private int inputValue = 123;
}

De forma predeterminada, el enlace se aplica al evento onchange del elemento. Si el usuario actualiza el valor de la entrada del cuadro de texto a 123.45 y cambia el foco, el valor del elemento se revierte a 123 cuando onchange se activa. Cuando el valor 123.45 se rechaza en favor del valor original de 123, el usuario entiende que no se ha aceptado su valor.

En el caso del evento oninput (@bind:event="oninput"), la reversión del valor se produce después de cualquier pulsación de tecla que especifique un valor no analizable. Cuando el destino es el evento oninput con un tipo enlazado a int, se impide que el usuario escriba el carácter de punto (.). El carácter de punto (.) se elimina de forma inmediata, por lo que el usuario recibe un comentario al instante informándole de que solo se permiten números enteros. Hay escenarios en los que la reversión del valor del evento oninput no es ideal, por ejemplo, cuando se debe permitir que el usuario borre un valor <input> que no se puede analizar. De forma alternativa, puede hacer lo siguiente:

  • No use el evento oninput. Use el evento onchange predeterminado para que no se revierta un valor no válido hasta que el elemento pierda el foco.
  • Establezca un enlace a un tipo que acepte valores NULL, como int? o string, y proporcione la lógica personalizada de los descriptores de acceso get y set para administrar las entradas no válidas.
  • Use un componente de validación de formularios, como InputNumber<TValue> o InputDate<TValue>. Los componentes de validación de formularios ofrecen compatibilidad integrada para administrar entradas no válidas. Componentes de validación de formularios:
    • Permiten que el usuario proporcione entradas no válidas y reciba errores de validación en la clase EditContext asociada.
    • Muestran errores de validación en la interfaz de usuario sin impedir que el usuario escriba datos adicionales en el formulario web.

Cadenas de formato

El enlace de datos funciona con una única cadena de formato DateTime mediante @bind:format="{FORMAT STRING}", donde el marcador de posición {FORMAT STRING} es la cadena de formato. Otras expresiones de formato, como los formatos de moneda o número, no están disponibles en este momento, pero podrían agregarse en una versión futura.

Pages/DateBinding.razor:

@page "/date-binding"

<p>
    <label>
        <code>yyyy-MM-dd</code> format:
        <input @bind="startDate" @bind:format="yyyy-MM-dd" />
    </label>
</p>

<p>
    <code>startDate</code>: @startDate
</p>

@code {
    private DateTime startDate = new DateTime(2020, 1, 1);
}

En el código anterior, el tipo de campo (atributo <input>) del elemento type tiene como valor predeterminado text.

Se aceptan los valores NULL System.DateTime y System.DateTimeOffset:

private DateTime? date;
private DateTimeOffset? dateOffset;

No se recomienda especificar un formato para el tipo de campo date porque Blazor tiene compatibilidad integrada para dar formato a las fechas. A pesar de la recomendación, use solo el formato de fecha yyyy-MM-dd para que el enlace funcione correctamente si se proporciona un formato con el tipo de campo date:

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

Formatos de enlace personalizados

Los descriptores de acceso get y set de C# se pueden usar para crear un comportamiento de formato de enlace personalizado, como muestra el siguiente componente DecimalBinding. El componente enlaza un decimal positivo o negativo con hasta tres posiciones decimales a un elemento <input> por medio de una propiedad string (DecimalValue).

Pages/DecimalBinding.razor:

@page "/decimal-binding"
@using System.Globalization

<p>
    <label>
        Decimal value (±0.000 format):
        <input @bind="DecimalValue" />
    </label>
</p>

<p>
    <code>decimalValue</code>: @decimalValue
</p>

@code {
    private decimal decimalValue = 1.1M;
    private NumberStyles style = 
        NumberStyles.AllowDecimalPoint | NumberStyles.AllowLeadingSign;
    private CultureInfo culture = CultureInfo.CreateSpecificCulture("en-US");

    private string DecimalValue
    {
        get => decimalValue.ToString("0.000", culture);
        set
        {
            if (Decimal.TryParse(value, style, culture, out var number))
            {
                decimalValue = Math.Round(number, 3);
            }
        }
    }
}

Enlace con parámetros de componente

Un escenario habitual es enlazar una propiedad de un componente secundario a una propiedad de su elemento primario. Este escenario se denomina enlace encadenado porque se producen varios niveles de enlace simultáneamente.

Los parámetros de componente permiten el enlace de propiedades de un componente primario con la sintaxis @bind-{PROPERTY}, donde el marcador de posición {PROPERTY} es la propiedad que se va a enlazar.

No se pueden implementar enlaces encadenados con la sintaxis @bind en el componente secundario. Es necesario especificar un controlador de eventos y un valor por separado para permitir la actualización de la propiedad en el elemento primario desde el componente secundario.

El componente primario sigue aprovechando la sintaxis @bind para configurar el enlace de datos con el componente secundario.

El siguiente componente ChildBind tiene un parámetro de componente Year y un elemento EventCallback<TValue>. Por convención, el elemento EventCallback<TValue> del parámetro se debe denominar como el nombre de parámetro del componente con el sufijo "Changed". La sintaxis de nomenclatura es {PARAMETER NAME}Changed, donde el marcador de posición {PARAMETER NAME} es el nombre del parámetro. En el ejemplo siguiente, el elemento EventCallback<TValue> se denomina YearChanged.

EventCallback.InvokeAsync invoca al delegado asociado al enlace con el argumento proporcionado y envía una notificación de evento de la propiedad modificada.

Shared/ChildBind.razor:

<div class="card bg-light mt-3" style="width:18rem ">
    <div class="card-body">
        <h3 class="card-title">ChildBind Component</h3>
        <p class="card-text">
            Child <code>Year</code>: @Year
        </p>
        <button @onclick="UpdateYearFromChild">Update Year from Child</button>
    </div>
</div>

@code {
    private Random r = new Random();

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

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

    private async Task UpdateYearFromChild()
    {
        await YearChanged.InvokeAsync(r.Next(1950, 2021));
    }
}

Para más información sobre los eventos y EventCallback<TValue>, vea la sección EventCallback del artículo Control de eventos de Blazor en ASP.NET Core.

En el siguiente componente Parent, el campo year se enlaza al parámetro Year del componente secundario. El parámetro Year se puede enlazar porque tiene un evento YearChanged complementario que coincide con el tipo del parámetro Year.

Pages/Parent.razor:

@page "/Parent"

<h1>Parent Component</h1>

<p>Parent <code>year</code>: @year</p>

<button @onclick="UpdateYear">Update Parent <code>year</code></button>

<ChildBind @bind-Year="year" />

@code {
    private Random r = new Random();
    private int year = 1979;

    private void UpdateYear()
    {
        year = r.Next(1950, 2021);
    }
}

Por convención, una propiedad se puede enlazar a un controlador de eventos correspondiente si se incluye un atributo @bind-{PROPERTY}:event asignado al controlador, donde el marcador de posición {PROPERTY} es la propiedad. <ChildBind @bind-Year="year" /> equivale a escribir lo siguiente:

<ChildBind @bind-Year="year" @bind-Year:event="YearChanged" />

En un ejemplo real más sofisticado, el siguiente componente PasswordEntry:

  • Establece el valor del elemento <input> en un campo password.
  • Expone los cambios de una propiedad Password a un componente primario con un objeto EventCallback que pasa el valor actual del campo password del elemento secundario como su argumento.
  • Usa el evento onclick para desencadenar el método ToggleShowPassword. Para más información, vea Control de eventos de Blazor en ASP.NET Core.

Shared/PasswordEntry.razor:

<div class="card bg-light mt-3" style="width:22rem ">
    <div class="card-body">
        <h3 class="card-title">Password Component</h3>
        <p class="card-text">
            <label>
                Password:
                <input @oninput="OnPasswordChanged"
                       required
                       type="@(showPassword ? "text" : "password")"
                       value="@password" />
            </label>
        </p>
        <button class="btn btn-primary" @onclick="ToggleShowPassword">
            Show password
        </button>
    </div>
</div>

@code {
    private bool showPassword;
    private string password;

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

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

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

        await PasswordChanged.InvokeAsync(password);
    }

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

El componente PasswordEntry se usa en otro componente, como el siguiente ejemplo de componente PasswordBinding.

Pages/PasswordBinding.razor:

@page "/password-binding"

<h1>Password Binding</h1>

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

<p>
    <code>password</code>: @password
</p>

@code {
    private string password = "Not set";
}

Cuando el componente PasswordBinding se representa inicialmente, el valor password de Not set se muestra en la interfaz de usuario. Después de la representación inicial, el valor de password refleja los cambios realizados en el valor del parámetro de componente Password del componente PasswordEntry.

Nota

En el ejemplo anterior se enlaza la contraseña de forma unidireccional desde el componente PasswordEntry secundario al componente PasswordBinding primario. El enlace bidireccional no es un requisito en este escenario si el objetivo es que la aplicación tenga un componente de entrada de contraseña compartida para reutilizarlo en la aplicación que simplemente pasa la contraseña al elemento primario. Para obtener un enfoque que permita el enlace bidireccional sin escribir directamente en el parámetro del componente secundario, vea el ejemplo del componente NestedChild en la sección Enlace entre más de dos componentes de este artículo.

Realice las comprobaciones o detecte los errores en el controlador. El siguiente componente PasswordEntry revisado informa de inmediato al usuario si se emplea un espacio en el valor de la contraseña.

Shared/PasswordEntry.razor:

<div class="card bg-light mt-3" style="width:22rem ">
    <div class="card-body">
        <h3 class="card-title">Password Component</h3>
        <p class="card-text">
            <label>
                Password:
                <input @oninput="OnPasswordChanged"
                       required
                       type="@(showPassword ? "text" : "password")"
                       value="@password" />
            </label>
            <span class="text-danger">@validationMessage</span>
        </p>
        <button class="btn btn-primary" @onclick="ToggleShowPassword">
            Show password
        </button>
    </div>
</div>

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

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

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

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

        if (password.Contains(' '))
        {
            validationMessage = "Spaces not allowed!";

            return Task.CompletedTask;
        }
        else
        {
            validationMessage = string.Empty;

            return PasswordChanged.InvokeAsync(password);
        }
    }

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

Enlace entre más de dos componentes

Puede enlazar parámetros a través de cualquier número de componentes anidados, pero debe respetar el flujo unidireccional de los datos:

  • Las notificaciones de cambio suben en la jerarquía.
  • Los valores nuevos de parámetro bajan en la jerarquía.

Un enfoque común y recomendado es almacenar solo los datos subyacentes en el componente primario para evitar la confusión sobre el estado que se debe actualizar, como se muestra en el ejemplo siguiente.

Pages/Parent.razor:

@page "/parent"

<h1>Parent Component</h1>

<p>Parent Message: <b>@parentMessage</b></p>

<p>
    <button @onclick="ChangeValue">Change from Parent</button>
</p>

<NestedChild @bind-ChildMessage="parentMessage" />

@code {
    private string parentMessage = "Initial value set in Parent";

    private void ChangeValue()
    {
        parentMessage = $"Set in Parent {DateTime.Now}";
    }
}

Shared/NestedChild.razor:

<div class="border rounded m-1 p-1">
    <h2>Child Component</h2>

    <p>Child Message: <b>@ChildMessage</b></p>

    <p>
        <button @onclick="ChangeValue">Change from Child</button>
    </p>

    <NestedGrandchild @bind-GrandchildMessage="BoundValue" />
</div>

@code {
    [Parameter]
    public string ChildMessage { get; set; }

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

    private string BoundValue
    {
        get => ChildMessage;
        set => ChildMessageChanged.InvokeAsync(value);
    }

    private async Task ChangeValue()
    {
        await ChildMessageChanged.InvokeAsync(
            $"Set in Child {DateTime.Now}");
    }
}

Advertencia

Por lo general, evite crear componentes que escriban directamente en sus propios parámetros de componente. El componente anterior NestedChild usa una propiedad BoundValue en lugar de escribir directamente en su parámetro ChildMessage. Para más información, vea Componentes Razor de ASP.NET Core.

Shared/NestedGrandchild.razor:

<div class="border rounded m-1 p-1">
    <h3>Grandchild Component</h3>

    <p>Grandchild Message: <b>@GrandchildMessage</b></p>

    <p>
        <button @onclick="ChangeValue">Change from Grandchild</button>
    </p>
</div>

@code {
    [Parameter]
    public string GrandchildMessage { get; set; }

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

    private async Task ChangeValue()
    {
        await GrandchildMessageChanged.InvokeAsync(
            $"Set in Grandchild {DateTime.Now}");
    }
}

Para obtener un enfoque alternativo adecuado a fin de compartir datos en memoria y entre componentes que no están necesariamente anidados, vea Administración de estado de Blazor en ASP.NET Core.

Recursos adicionales

Los componentes de Razor proporcionan características de enlace de datos con el atributo de directiva @bindRazor con un valor de campo, propiedad o expresión de Razor.

En el ejemplo siguiente se enlaza:

  • Un valor del elemento <input> al campo inputValue de C#.
  • Un segundo valor del elemento <input> a la propiedad InputValue de C#.

Cuando un elemento <input> pierde el foco, se actualiza su campo o propiedad enlazada.

Pages/Bind.razor:

@page "/bind"

<p>
    <input @bind="inputValue" />
</p>

<p>
    <input @bind="InputValue" />
</p>

<ul>
    <li><code>inputValue</code>: @inputValue</li>
    <li><code>InputValue</code>: @InputValue</li>
</ul>

@code {
    private string? inputValue;

    private string? InputValue { get; set; }
}

El cuadro de texto se actualiza en la interfaz de usuario solo cuando se representa el componente, no en respuesta al cambio de valor del campo o la propiedad. Como los componentes se representan a sí mismos después de que se ejecute el código del controlador de eventos, las actualizaciones de campos y propiedades normalmente se reflejan en la interfaz de usuario justo después de que se desencadene un controlador de eventos.

Como demostración de cómo el enlace de datos se compone en HTML, en el ejemplo siguiente se enlaza la propiedad InputValue al segundo <input> del elemento value y a los atributos onchange. El segundo elemento <input> del ejemplo siguiente es una demostración de concepto y no está pensado para sugerir cómo debe enlazar los datos en los componentes de Razor .

Pages/BindTheory.razor:

@page "/bind-theory"

<p>
    <label>
        Normal Blazor binding: 
        <input @bind="InputValue" />
    </label>
</p>

<p>
    <label>
        Demonstration of equivalent HTML binding: 
        <input value="@InputValue"
            @onchange="@((ChangeEventArgs __e) => InputValue = __e?.Value?.ToString())" />
    </label>
</p>

<p>
    <code>InputValue</code>: @InputValue
</p>

@code {
    private string? InputValue { get; set; }
}

Cuando se representa el componente BindTheory, el valor value del elemento <input> de demostración HTML procede de la propiedad InputValue. Cuando el usuario especifica un valor en el cuadro de texto y cambia el foco del elemento, se desencadena el evento onchange y la propiedad InputValue se establece en el valor modificado. En realidad, la generación de código es más compleja, porque @bind administra los casos en los que se realizan conversiones de tipos. En general, @bind asocia el valor actual de una expresión a un atributo value y controla los cambios mediante el controlador registrado.

Enlace una propiedad o un campo en otros eventos de Document Object Model (DOM) mediante la inclusión de un atributo @bind:event="{EVENT}" con un evento DOM para el marcador de posición {EVENT}. En el siguiente ejemplo se enlaza la propiedad InputValue al valor del elemento <input> cuando se desencadena el evento oninput del elemento. A diferencia del evento onchange, que se activa cuando el elemento pierde el foco, oninput se desencadena cuando cambia el valor del cuadro de texto.

Page/BindEvent.razor:

@page "/bind-event"

<p>
    <input @bind="InputValue" @bind:event="oninput" />
</p>

<p>
    <code>InputValue</code>: @InputValue
</p>

@code {
    private string? InputValue { get; set; }
}

El enlace de atributos de Razor distingue mayúsculas de minúsculas:

  • @bind y @bind:event son válidos.
  • @Bind/@Bind:Event (letras mayúsculas B y E) o @BIND/@BIND:EVENT (todas las letras mayúsculas) no son válidos.

Selección de varias opciones con elementos <select>

El enlace admite la selección de la opción multiple con los elementos <select>. El evento @onchange proporciona una matriz de los elementos seleccionados mediante argumentos de evento (ChangeEventArgs). El valor se debe enlazar a un tipo de matriz.

Pages/BindMultipleInput.razor:

@page "/bind-multiple-input"

<h1>Bind Multiple <code>input</code>Example</h1>

<p>
    <label>
        Select one or more cars: 
        <select @onchange="SelectedCarsChanged" multiple>
            <option value="audi">Audi</option>
            <option value="jeep">Jeep</option>
            <option value="opel">Opel</option>
            <option value="saab">Saab</option>
            <option value="volvo">Volvo</option>
        </select>
    </label>
</p>

<p>
    Selected Cars: @string.Join(", ", SelectedCars)
</p>

<p>
    <label>
        Select one or more cities: 
        <select @bind="SelectedCities" multiple>
            <option value="bal">Baltimore</option>
            <option value="la">Los Angeles</option>
            <option value="pdx">Portland</option>
            <option value="sf">San Francisco</option>
            <option value="sea">Seattle</option>
        </select>
    </label>
</p>

<span>
    Selected Cities: @string.Join(", ", SelectedCities)
</span>

@code {
    public string[] SelectedCars { get; set; } = new string[] { };
    public string[] SelectedCities { get; set; } = new[] { "bal", "sea" };

    void SelectedCarsChanged(ChangeEventArgs e)
    {
        if (e.Value is not null)
        {
            SelectedCars = (string[])e.Value;
        }
    }
}

Para obtener información sobre cómo se controlan las cadenas vacías y los valores null en el enlace de datos, vea la sección Enlace de opciones del elemento <select> a valores null de objetos de C#.

Enlace de opciones del elemento <select> a valores null de un objeto C#

No hay ninguna manera razonable de representar un valor de opción de elemento <select> como valor null de objeto C#, por los siguientes motivos:

  • Los atributos HTML no pueden tener valores null. El equivalente más cercano a null en HTML es la ausencia del atributo value de HTML del elemento <option>.
  • Al seleccionar un <option> sin ningún atributo value, el explorador trata el valor como contenido de texto de ese elemento de <option>.

El marco de Blazor no intenta suprimir el comportamiento predeterminado porque implicaría lo siguiente:

  • Crear una cadena de soluciones alternativas de casos especiales en el marco.
  • Hacer cambios importantes en el comportamiento actual del marco.

El null más plausible equivalente en HTML es una cadena vacíavalue. El marco Blazor controla null en las conversiones de cadenas vacías para el enlace bidireccional al valor de <select>.

Valores no analizables

Cuando un usuario proporciona un valor no analizable a un elemento enlazado a datos, el valor no analizable se revierte automáticamente a su valor anterior al desencadenar el evento de enlace.

Considere el siguiente componente, donde un elemento <input> se enlaza a un tipo int con un valor inicial de 123.

Pages/UnparsableValues.razor:

@page "/unparseable-values"

<p>
    <input @bind="inputValue" />
</p>

<p>
    <code>inputValue</code>: @inputValue
</p>

@code {
    private int inputValue = 123;
}

De forma predeterminada, el enlace se aplica al evento onchange del elemento. Si el usuario actualiza el valor de la entrada del cuadro de texto a 123.45 y cambia el foco, el valor del elemento se revierte a 123 cuando onchange se activa. Cuando el valor 123.45 se rechaza en favor del valor original de 123, el usuario entiende que no se ha aceptado su valor.

En el caso del evento oninput (@bind:event="oninput"), la reversión del valor se produce después de cualquier pulsación de tecla que especifique un valor no analizable. Cuando el destino es el evento oninput con un tipo enlazado a int, se impide que el usuario escriba el carácter de punto (.). El carácter de punto (.) se elimina de forma inmediata, por lo que el usuario recibe un comentario al instante informándole de que solo se permiten números enteros. Hay escenarios en los que la reversión del valor del evento oninput no es ideal, por ejemplo, cuando se debe permitir que el usuario borre un valor <input> que no se puede analizar. De forma alternativa, puede hacer lo siguiente:

  • No use el evento oninput. Use el evento onchange predeterminado para que no se revierta un valor no válido hasta que el elemento pierda el foco.
  • Establezca un enlace a un tipo que acepte valores NULL, como int? o string, y proporcione la lógica personalizada de los descriptores de acceso get y set para administrar las entradas no válidas.
  • Use un componente de validación de formularios, como InputNumber<TValue> o InputDate<TValue>. Los componentes de validación de formularios ofrecen compatibilidad integrada para administrar entradas no válidas. Componentes de validación de formularios:
    • Permiten que el usuario proporcione entradas no válidas y reciba errores de validación en la clase EditContext asociada.
    • Muestran errores de validación en la interfaz de usuario sin impedir que el usuario escriba datos adicionales en el formulario web.

Cadenas de formato

El enlace de datos funciona con una única cadena de formato DateTime mediante @bind:format="{FORMAT STRING}", donde el marcador de posición {FORMAT STRING} es la cadena de formato. Otras expresiones de formato, como los formatos de moneda o número, no están disponibles en este momento, pero podrían agregarse en una versión futura.

Pages/DateBinding.razor:

@page "/date-binding"

<p>
    <label>
        <code>yyyy-MM-dd</code> format:
        <input @bind="startDate" @bind:format="yyyy-MM-dd" />
    </label>
</p>

<p>
    <code>startDate</code>: @startDate
</p>

@code {
    private DateTime startDate = new(2020, 1, 1);
}

En el código anterior, el tipo de campo (atributo <input>) del elemento type tiene como valor predeterminado text.

Se aceptan los valores NULL System.DateTime y System.DateTimeOffset:

private DateTime? date;
private DateTimeOffset? dateOffset;

No se recomienda especificar un formato para el tipo de campo date porque Blazor tiene compatibilidad integrada para dar formato a las fechas. A pesar de la recomendación, use solo el formato de fecha yyyy-MM-dd para que el enlace funcione correctamente si se proporciona un formato con el tipo de campo date:

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

Formatos de enlace personalizados

Los descriptores de acceso get y set de C# se pueden usar para crear un comportamiento de formato de enlace personalizado, como muestra el siguiente componente DecimalBinding. El componente enlaza un decimal positivo o negativo con hasta tres posiciones decimales a un elemento <input> por medio de una propiedad string (DecimalValue).

Pages/DecimalBinding.razor:

@page "/decimal-binding"
@using System.Globalization

<p>
    <label>
        Decimal value (±0.000 format):
        <input @bind="DecimalValue" />
    </label>
</p>

<p>
    <code>decimalValue</code>: @decimalValue
</p>

@code {
    private decimal decimalValue = 1.1M;
    private NumberStyles style = 
        NumberStyles.AllowDecimalPoint | NumberStyles.AllowLeadingSign;
    private CultureInfo culture = CultureInfo.CreateSpecificCulture("en-US");

    private string DecimalValue
    {
        get => decimalValue.ToString("0.000", culture);
        set
        {
            if (Decimal.TryParse(value, style, culture, out var number))
            {
                decimalValue = Math.Round(number, 3);
            }
        }
    }
}

Enlace con parámetros de componente

Un escenario habitual es enlazar una propiedad de un componente secundario a una propiedad de su elemento primario. Este escenario se denomina enlace encadenado porque se producen varios niveles de enlace simultáneamente.

Los parámetros de componente permiten el enlace de propiedades de un componente primario con la sintaxis @bind-{PROPERTY}, donde el marcador de posición {PROPERTY} es la propiedad que se va a enlazar.

No se pueden implementar enlaces encadenados con la sintaxis @bind en el componente secundario. Es necesario especificar un controlador de eventos y un valor por separado para permitir la actualización de la propiedad en el elemento primario desde el componente secundario.

El componente primario sigue aprovechando la sintaxis @bind para configurar el enlace de datos con el componente secundario.

El siguiente componente ChildBind tiene un parámetro de componente Year y un elemento EventCallback<TValue>. Por convención, el elemento EventCallback<TValue> del parámetro se debe denominar como el nombre de parámetro del componente con el sufijo "Changed". La sintaxis de nomenclatura es {PARAMETER NAME}Changed, donde el marcador de posición {PARAMETER NAME} es el nombre del parámetro. En el ejemplo siguiente, el elemento EventCallback<TValue> se denomina YearChanged.

EventCallback.InvokeAsync invoca al delegado asociado al enlace con el argumento proporcionado y envía una notificación de evento de la propiedad modificada.

Shared/ChildBind.razor:

<div class="card bg-light mt-3" style="width:18rem ">
    <div class="card-body">
        <h3 class="card-title">ChildBind Component</h3>
        <p class="card-text">
            Child <code>Year</code>: @Year
        </p>
        <button @onclick="UpdateYearFromChild">Update Year from Child</button>
    </div>
</div>

@code {
    private Random r = new();

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

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

    private async Task UpdateYearFromChild()
    {
        await YearChanged.InvokeAsync(r.Next(1950, 2021));
    }
}

Para más información sobre los eventos y EventCallback<TValue>, vea la sección EventCallback del artículo Control de eventos de Blazor en ASP.NET Core.

En el siguiente componente Parent, el campo year se enlaza al parámetro Year del componente secundario. El parámetro Year se puede enlazar porque tiene un evento YearChanged complementario que coincide con el tipo del parámetro Year.

Pages/Parent.razor:

@page "/Parent"

<h1>Parent Component</h1>

<p>Parent <code>year</code>: @year</p>

<button @onclick="UpdateYear">Update Parent <code>year</code></button>

<ChildBind @bind-Year="year" />

@code {
    private Random r = new();
    private int year = 1979;

    private void UpdateYear()
    {
        year = r.Next(1950, 2021);
    }
}

Por convención, una propiedad se puede enlazar a un controlador de eventos correspondiente si se incluye un atributo @bind-{PROPERTY}:event asignado al controlador, donde el marcador de posición {PROPERTY} es la propiedad. <ChildBind @bind-Year="year" /> equivale a escribir lo siguiente:

<ChildBind @bind-Year="year" @bind-Year:event="YearChanged" />

En un ejemplo real más sofisticado, el siguiente componente PasswordEntry:

  • Establece el valor del elemento <input> en un campo password.
  • Expone los cambios de una propiedad Password a un componente primario con un objeto EventCallback que pasa el valor actual del campo password del elemento secundario como su argumento.
  • Usa el evento onclick para desencadenar el método ToggleShowPassword. Para más información, vea Control de eventos de Blazor en ASP.NET Core.

Shared/PasswordEntry.razor:

<div class="card bg-light mt-3" style="width:22rem ">
    <div class="card-body">
        <h3 class="card-title">Password Component</h3>
        <p class="card-text">
            <label>
                Password:
                <input @oninput="OnPasswordChanged"
                       required
                       type="@(showPassword ? "text" : "password")"
                       value="@password" />
            </label>
        </p>
        <button class="btn btn-primary" @onclick="ToggleShowPassword">
            Show password
        </button>
    </div>
</div>

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

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

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

    private async Task OnPasswordChanged(ChangeEventArgs e)
    {
        password = e?.Value?.ToString();

        await PasswordChanged.InvokeAsync(password);
    }

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

El componente PasswordEntry se usa en otro componente, como el siguiente ejemplo de componente PasswordBinding.

Pages/PasswordBinding.razor:

@page "/password-binding"

<h1>Password Binding</h1>

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

<p>
    <code>password</code>: @password
</p>

@code {
    private string password = "Not set";
}

Cuando el componente PasswordBinding se representa inicialmente, el valor password de Not set se muestra en la interfaz de usuario. Después de la representación inicial, el valor de password refleja los cambios realizados en el valor del parámetro de componente Password del componente PasswordEntry.

Nota

En el ejemplo anterior se enlaza la contraseña de forma unidireccional desde el componente PasswordEntry secundario al componente PasswordBinding primario. El enlace bidireccional no es un requisito en este escenario si el objetivo es que la aplicación tenga un componente de entrada de contraseña compartida para reutilizarlo en la aplicación que simplemente pasa la contraseña al elemento primario. Para obtener un enfoque que permita el enlace bidireccional sin escribir directamente en el parámetro del componente secundario, vea el ejemplo del componente NestedChild en la sección Enlace entre más de dos componentes de este artículo.

Realice las comprobaciones o detecte los errores en el controlador. El siguiente componente PasswordEntry revisado informa de inmediato al usuario si se emplea un espacio en el valor de la contraseña.

Shared/PasswordEntry.razor:

<div class="card bg-light mt-3" style="width:22rem ">
    <div class="card-body">
        <h3 class="card-title">Password Component</h3>
        <p class="card-text">
            <label>
                Password:
                <input @oninput="OnPasswordChanged"
                       required
                       type="@(showPassword ? "text" : "password")"
                       value="@password" />
            </label>
            <span class="text-danger">@validationMessage</span>
        </p>
        <button class="btn btn-primary" @onclick="ToggleShowPassword">
            Show password
        </button>
    </div>
</div>

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

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

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

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

        if (password != null && password.Contains(' '))
        {
            validationMessage = "Spaces not allowed!";

            return Task.CompletedTask;
        }
        else
        {
            validationMessage = string.Empty;

            return PasswordChanged.InvokeAsync(password);
        }
    }

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

Enlace entre más de dos componentes

Puede enlazar parámetros a través de cualquier número de componentes anidados, pero debe respetar el flujo unidireccional de los datos:

  • Las notificaciones de cambio suben en la jerarquía.
  • Los valores nuevos de parámetro bajan en la jerarquía.

Un enfoque común y recomendado es almacenar solo los datos subyacentes en el componente primario para evitar la confusión sobre el estado que se debe actualizar, como se muestra en el ejemplo siguiente.

Pages/Parent.razor:

@page "/parent"

<h1>Parent Component</h1>

<p>Parent Message: <b>@parentMessage</b></p>

<p>
    <button @onclick="ChangeValue">Change from Parent</button>
</p>

<NestedChild @bind-ChildMessage="parentMessage" />

@code {
    private string parentMessage = "Initial value set in Parent";

    private void ChangeValue()
    {
        parentMessage = $"Set in Parent {DateTime.Now}";
    }
}

Shared/NestedChild.razor:

<div class="border rounded m-1 p-1">
    <h2>Child Component</h2>

    <p>Child Message: <b>@ChildMessage</b></p>

    <p>
        <button @onclick="ChangeValue">Change from Child</button>
    </p>

    <NestedGrandchild @bind-GrandchildMessage="BoundValue" />
</div>

@code {
    [Parameter]
    public string? ChildMessage { get; set; }

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

    private string BoundValue
    {
        get => ChildMessage ?? string.Empty;
        set => ChildMessageChanged.InvokeAsync(value);
    }

    private async Task ChangeValue()
    {
        await ChildMessageChanged.InvokeAsync(
            $"Set in Child {DateTime.Now}");
    }
}

Advertencia

Por lo general, evite crear componentes que escriban directamente en sus propios parámetros de componente. El componente anterior NestedChild usa una propiedad BoundValue en lugar de escribir directamente en su parámetro ChildMessage. Para más información, vea Componentes Razor de ASP.NET Core.

Shared/NestedGrandchild.razor:

<div class="border rounded m-1 p-1">
    <h3>Grandchild Component</h3>

    <p>Grandchild Message: <b>@GrandchildMessage</b></p>

    <p>
        <button @onclick="ChangeValue">Change from Grandchild</button>
    </p>
</div>

@code {
    [Parameter]
    public string? GrandchildMessage { get; set; }

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

    private async Task ChangeValue()
    {
        await GrandchildMessageChanged.InvokeAsync(
            $"Set in Grandchild {DateTime.Now}");
    }
}

Para obtener un enfoque alternativo adecuado a fin de compartir datos en memoria y entre componentes que no están necesariamente anidados, vea Administración de estado de Blazor en ASP.NET Core.

Recursos adicionales