ASP.NET Core Blazor-Datenbindung

Razor Komponenten bieten Datenbindungsfunktionen mit dem @bind Razor-Direktivenattribut mit einem Feld, einer Eigenschaft oder einem Razor-Ausdruckswert.

Im folgenden Beispiel werden Elemente gebunden:

  • Ein <input>-Elementwert an das C#-Feld inputValue.
  • Ein zweiter <input>-Elementwert an die C#-Eigenschaft InputValue.

Wenn ein <input>-Element den Fokus verliert, wird das gebundene Feld oder die Eigenschaft aktualisiert.

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

Das Textfeld wird in der Benutzeroberfläche nur dann aktualisiert, wenn die Komponente gerendert wird, nicht als Reaktion auf die Änderung des Werts des Felds oder der Eigenschaft. Da sich Komponenten nach der Ausführung von Ereignishandlercode selbst rendern, werden Feld- und Eigenschaftsaktualisierungen in der Regel unmittelbar nach dem Auslösen eines Ereignishandlers in der Benutzeroberfläche widergespiegelt.

Als Demo, wie Datenbindung in HTML erfolgt, bindet das folgende Beispiel die Eigenschaft InputValue an die Attribute value und onchange des zweiten <input>-Elements. Das zweite <input>-Element im folgenden Beispiel ist eine Konzeptdemo und soll nicht zeigen, wie Daten in Razor-Komponenten gebunden werden sollen.

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

Wenn die BindTheory-Komponente gerendert wird, stammt der value des <input>-Elements der HTML-Demo aus der InputValue-Eigenschaft. Wenn der Benutzer einen Wert in das Textfeld eingibt und den Elementfokus ändert, wird das onchange-Ereignis ausgelöst und die InputValue-Eigenschaft auf den geänderten Wert festgelegt. In Wirklichkeit ist die Codeausführung komplexer, weil @bind Fälle verarbeitet, in denen Typkonvertierungen durchgeführt werden. Im Allgemeinen ordnet @bind den aktuellen Wert eines Ausdrucks einem value-Attribut zu und behandelt Änderungen mit dem registrierten Handler.

Binden Sie eine Eigenschaft oder ein Feld an andere DOM-Ereignisse (Dokumentobjektmodell), indem Sie ein @bind:event="{EVENT}"-Attribut mit einem DOM-Ereignis für den Platzhalter {EVENT} einbinden. Im folgenden Beispiel wird die InputValue-Eigenschaft an den Wert des <input>-Elements gebunden, wenn das oninput-Ereignis des Elements ausgelöst wird. Im Gegensatz zum onchange-Ereignis, das ausgelöst wird, wenn das Element den Fokus verliert, wird oninput ausgelöst, wenn sich der Wert des Textfelds ändert.

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

Bei der Razor-Attributbindung wird Groß-/Kleinschreibung berücksichtigt:

  • @bind und @bind:event sind gültig.
  • @Bind/@Bind:Event (Großbuchstaben B und E ) oder @BIND/@BIND:EVENT (ausschließlich Großbuchstaben) sind ungültig.

Auswahl der Option „multiple“ für <input>-Elemente

Die Bindung unterstützt die Auswahl der Option multiple für <input>-Elemente. Das @onchange-Ereignis stellt ein Array der ausgewählten Elemente über Ereignisargumente (ChangeEventArgs) zur Verfügung. Der Wert muss an einen Arraytyp gebunden werden.

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)
    {
        SelectedCars = (string[])e.Value;
    }
}

Informationen dazu, wie leere Zeichenfolgen und null-Werte in der Datenbindung behandelt werden, finden Sie im Abschnitt Binden von Optionen des <select>-Elements an null-Werte von C#-Objekten.

Binden von <select>-Elementoptionen an null-Werte von C#-Objekten

Es gibt aus folgenden Gründen keine vernünftige Möglichkeit, den Optionswert eines <select>-Elements als null-Wert eines C#-Objekts darzustellen:

  • HTML-Attribute können keine null-Werte aufweisen. null entspricht in HTML am ehesten dem Nichtvorhandensein des value-HTML-Attributs im <option>-Element.
  • Wenn Sie ein <option>-Element ohne value-Attribut auswählen, behandelt der Browser den Wert als Textinhalt dieses <option>-Elements.

Das Blazor-Framework versucht nicht, das Standardverhalten zu unterdrücken, weil dies Folgendes beinhalten würde:

  • Erstellen einer Kette von Problemumgehungen für Sonderfälle im Framework.
  • Breaking Changes am aktuellen Frameworkverhalten.

Das plausibelste Äquivalent zu null in HTML ist eine value mit einer leeren Zeichenfolge. Das Blazor-Framework verarbeitet null als Konvertierung einer leeren Zeichenfolge für eine bidirektionale Bindung an einen <select>-Wert.

Nicht analysierbare Werte

Wenn ein Benutzer einem Element mit Datenbindung einen nicht analysierbaren Wert zur Verfügung stellt, wird der nicht analysierbare Wert automatisch auf seinen vorherigen Wert zurückgesetzt, wenn das Bindungsereignis ausgelöst wird.

Beachten Sie die folgende Komponente, in der ein <input>-Element an einen int-Typ mit einem Anfangswert von 123 gebunden ist.

Pages/UnparsableValues.razor:

@page "/unparseable-values"

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

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

@code {
    private int inputValue = 123;
}

Standardmäßig gilt die Bindung für das onchange-Ereignis des Elements. Wenn der Benutzer den Wert des Eintrags des Textfelds in 123.45 aktualisiert und den Fokus ändert, wird der Wert des Elements beim Auslösen von onchange auf 123 zurückgesetzt. Wenn der Wert 123.45 zugunsten des ursprünglichen Werts von 123 abgelehnt wird, versteht der Benutzer, dass sein Wert nicht akzeptiert wurde.

Für das oninput-Ereignis (@bind:event="oninput") erfolgt die Wertumkehr nach jedem Tastendruck, der einen nicht analysierbaren Wert einführt. Wenn das oninput-Ereignis mit einem int-gebundenen Typ adressiert wird, wird ein Benutzer daran gehindert, ein Punktzeichen (.) einzugeben. Ein Punktzeichen (.) wird sofort entfernt, sodass der Benutzer sofort die Rückmeldung erhält, dass nur ganze Zahlen zulässig sind. Es gibt Szenarien, in denen die Umkehrung des Werts auf das oninput Ereignis nicht ideal ist, z. B. wenn dem Benutzer erlaubt werden soll, einen nicht analysierbaren <input>-Wert zu löschen. Zu den Alternativen gehören:

  • Verwenden Sie nicht das Ereignis oninput. Verwenden Sie das Standardereignis onchange, bei dem ein ungültiger Wert erst dann zurückgesetzt wird, wenn das Element den Fokus verliert.
  • Binden Sie an einen Nullable-Typ (z. B. int? oder string), und stellen Sie benutzerdefinierteget Logik und setZugriffsmethodenlogik zur Behandlung ungültiger Einträge bereit.
  • Verwenden Sie eine Formularüberprüfungskomponente wie InputNumber<TValue> oder InputDate<TValue>. Formularüberprüfungskomponenten stellen integrierte Unterstützung zur Verwaltung ungültiger Eingaben bereit. Formularüberprüfungskomponenten:
    • Erlauben Sie dem Benutzer, ungültige Eingaben zu machen und Überprüfungsfehler für das zugehörige EditContext zu erhalten.
    • Zeigen Sie Überprüfungsfehler auf der Benutzeroberfläche an, ohne den Benutzer bei der Eingabe zusätzlicher WebForm-Daten zu beeinträchtigen.

Formatzeichenfolgen

Datenbindung funktioniert mit einer einzelnen DateTime-Formatzeichenfolge mit@bind:format="{FORMAT STRING}", wobei der Platzhalter {FORMAT STRING} die Formatzeichenfolge ist. Andere Formatausdrücke, z. B. Währungs- oder Zahlenformate, sind zurzeit nicht verfügbar, werden jedoch möglicherweise in einem zukünftigen Release hinzugefügt.

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

Im Code oben ist der Feldtyp des <input>-Elements (type-Attribut) standardmäßig auf text festgelegt.

Die Nullable-Typen System.DateTime und System.DateTimeOffset werden unterstützt:

private DateTime? date;
private DateTimeOffset? dateOffset;

Die Angabe eines Formats für den Feldtyp date wird nicht empfohlen, da Blazor eine integrierte Unterstützung für die Formatierung von Daten bietet. Verwenden Sie trotz der Empfehlung nur dann das Datumsformat yyyy-MM-dd für die Bindung, um ordnungsgemäß zu funktionieren, wenn ein Format mit dem Feldtyp date bereitgestellt wird:

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

Benutzerdefinierte Bindungsformate

C# get und setZugriffsmethoden können verwendet werden, um ein benutzerdefiniertes Bindungsformatverhalten zu erstellen, wie die folgende DecimalBinding-Komponente veranschaulicht. Die Komponente bindet eine positive oder negative Dezimalzahl mit bis zu drei Dezimalstellen über eine string-Eigenschaft (DecimalValue) an ein <input>-Element.

Pages/DecimalBinding.razor:

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

<p>
    <label>
        Decimal value (&plusmn;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);
            }
        }
    }
}

Binden mit Komponentenparametern

Ein häufiges Szenario besteht darin, eine Eigenschaft einer untergeordneten Komponente an eine Eigenschaft in ihrer übergeordneten Komponente zu binden. Dieses Szenario wird als verkettete Bindung bezeichnet, da mehrere Ebenen der Bindung gleichzeitig auftreten.

Komponentenparameter erlauben Bindungseigenschaften einer übergeordneten Komponente mit @bind-{PROPERTY}-Syntax, wobei der Platzhalter {PROPERTY} die Eigenschaft ist, die gebunden werden soll.

Verkettete Bindungen können nicht mit der @bind-Syntax in der untergeordneten Komponente implementiert werden. Separat müssen ein Ereignishandler und ein Wert angegeben werden, um die Aktualisierung der Eigenschaft in der übergeordneten Komponente zu unterstützen.

Die übergeordnete Komponente nutzt weiterhin die @bind-Syntax, um die Datenbindung mit der untergeordneten Komponente einzurichten.

Die folgende ChildBind-Komponente (Year) verfügt über einen Komponentenparameter und einen EventCallback<TValue>. Gemäß der Konvention muss EventCallback<TValue> für den Parameter wie der Name des Komponentenparameters mit einem Suffix „Changed“ benannt werden. Die Benennungssyntax ist {PARAMETER NAME}Changed, wobei der Platzhalter {PARAMETER NAME} der Parametername ist. Im folgenden Beispiel trägt EventCallback<TValue> den Namen YearChanged.

EventCallback.InvokeAsync ruft den Delegaten, der der Bindung zugeordnet ist, mit dem bereitgestellten Argument auf und sendet eine Ereignisbenachrichtigung über die geänderte Eigenschaft.

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

Weitere Informationen zu Ereignissen und EventCallback<TValue> finden Sie im Abschnitt EventCallback des Artikels „ASP.NET Core Blazor-Ereignisbehandlung“.

In der folgenden Parent-Komponente ist das Feld year an den Year-Parameter der untergeordneten Komponente gebunden. Der Parameter Year ist bindbar, da er ein Begleitereignis YearChanged aufweist, das dem Typ des Parameters Year entspricht.

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

Gemäß der Konvention kann eine Eigenschaft an einen entsprechenden Ereignishandler gebunden werden, indem ein @bind-{PROPERTY}:event-Attribut einbezogen wird, das dem Handler zugewiesen ist, wobei der Platzhalter {PROPERTY} die Eigenschaft darstellt. <ChildBind @bind-Year="year" /> ist identisch mit dem folgenden Code:

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

In einem komplexeren Beispiel aus der Praxis führt die PasswordEntry-Komponente Folgendes aus:

  • Legt den Wert eines <input>-Elements auf ein password-Feld fest.
  • Macht Änderungen einer Password-Eigenschaft einer übergeordneten Komponente mit einem EventCallback verfügbar, der den aktuellen Wert des password-Felds des untergeordneten Elements als Argument übergibt.
  • Verwendet das onclick-Ereignis zum Auslösen der ToggleShowPassword-Methode. Weitere Informationen finden Sie unter ASP.NET Core Blazor-Ereignisbehandlung.

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

Die PasswordEntry-Komponente wird in einer anderen Komponente verwendet, wie im folgenden PasswordBinding-Komponentenbeispiel.

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

Wenn die PasswordBinding Komponente zum ersten Mal gerendert wird, wird der password-Wert von Not set auf der Benutzeroberfläche angezeigt. Nach dem ersten Rendering berücksichtigt der Wert von password Änderungen, die am Password-Komponentenparameterwert in der PasswordEntry-Komponente vorgenommen wurden.

Hinweis

Im obigen Beispiel wird das Kennwort von der untergeordneten PasswordEntry-Komponente einseitig an die übergeordnete PasswordBinding-Komponente gebunden. Eine zweiseitige Bindung ist in diesem Szenario nicht erforderlich, wenn das Ziel darin besteht, dass die App über eine freigegebene Kennworteingabekomponente für die Wiederverwendung in der App verfügt, die das Kennwort lediglich an das übergeordnete Element übergibt. Eine Vorgehensweise, die eine zweiseitige Bindung ohne direktes Schreiben in den Parameter der untergeordneten Komponente zulässt, finden Sie im NestedChild-Komponentenbeispiel im Abschnitt Binden über mehr als zwei Komponenten dieses Artikels.

Führen Sie die Prüfungen durch, oder fangen Sie Fehler im Handler ab. Die folgende überarbeitete PasswordEntry-Komponente gibt dem Benutzer sofortiges Feedback, wenn ein Leerzeichen im Wert des Kennworts verwendet wird.

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

Binden über mehr als zwei Komponenten

Sie können Parameter über eine beliebige Anzahl geschachtelter Komponenten binden, Sie müssen aber den unidirektionalen Datenfluss berücksichtigen:

  • Änderungsbenachrichtigungen durchlaufen die Hierarchie von unten nach oben.
  • Neue Parameterwerte durchlaufen die Hierarchie von oben nach unten.

Ein gängiges und empfohlenes Verfahren besteht darin, nur die zugrunde liegenden Daten in der übergeordneten Komponente zu speichern, damit Sie immer genau wissen, welcher Zustand aktualisiert werden muss, wie im folgenden Beispiel gezeigt.

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

Warnung

Sie sollten im Allgemeinen das Erstellen von Komponenten vermeiden, die direkt in eigene Komponentenparameter schreiben. Die obige NestedChild-Komponente verwendet eine BoundValue -Eigenschaft, anstatt direkt in den ChildMessage-Parameter zu schreiben. Weitere Informationen finden Sie unter ASP.NET Core-Razor-Komponenten.

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

Einen alternativen Ansatz, der sich für die gemeinsame Nutzung von Daten im Arbeitsspeicher über Komponenten hinweg eignet, die nicht unbedingt geschachtelt sind, finden Sie unter Blazor-Zustandsverwaltung in ASP.NET Core.

Zusätzliche Ressourcen

Razor Komponenten bieten Datenbindungsfunktionen mit dem @bind Razor-Direktivenattribut mit einem Feld, einer Eigenschaft oder einem Razor-Ausdruckswert.

Im folgenden Beispiel werden Elemente gebunden:

  • Ein <input>-Elementwert an das C#-Feld inputValue.
  • Ein zweiter <input>-Elementwert an die C#-Eigenschaft InputValue.

Wenn ein <input>-Element den Fokus verliert, wird das gebundene Feld oder die Eigenschaft aktualisiert.

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

Das Textfeld wird in der Benutzeroberfläche nur dann aktualisiert, wenn die Komponente gerendert wird, nicht als Reaktion auf die Änderung des Werts des Felds oder der Eigenschaft. Da sich Komponenten nach der Ausführung von Ereignishandlercode selbst rendern, werden Feld- und Eigenschaftsaktualisierungen in der Regel unmittelbar nach dem Auslösen eines Ereignishandlers in der Benutzeroberfläche widergespiegelt.

Als Demo, wie Datenbindung in HTML erfolgt, bindet das folgende Beispiel die Eigenschaft InputValue an die Attribute value und onchange des zweiten <input>-Elements. Das zweite <input>-Element im folgenden Beispiel ist eine Konzeptdemo und soll nicht zeigen, wie Daten in Razor-Komponenten gebunden werden sollen.

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

Wenn die BindTheory-Komponente gerendert wird, stammt der value des <input>-Elements der HTML-Demo aus der InputValue-Eigenschaft. Wenn der Benutzer einen Wert in das Textfeld eingibt und den Elementfokus ändert, wird das onchange-Ereignis ausgelöst und die InputValue-Eigenschaft auf den geänderten Wert festgelegt. In Wirklichkeit ist die Codeausführung komplexer, weil @bind Fälle verarbeitet, in denen Typkonvertierungen durchgeführt werden. Im Allgemeinen ordnet @bind den aktuellen Wert eines Ausdrucks einem value-Attribut zu und behandelt Änderungen mit dem registrierten Handler.

Binden Sie eine Eigenschaft oder ein Feld an andere DOM-Ereignisse (Dokumentobjektmodell), indem Sie ein @bind:event="{EVENT}"-Attribut mit einem DOM-Ereignis für den Platzhalter {EVENT} einbinden. Im folgenden Beispiel wird die InputValue-Eigenschaft an den Wert des <input>-Elements gebunden, wenn das oninput-Ereignis des Elements ausgelöst wird. Im Gegensatz zum onchange-Ereignis, das ausgelöst wird, wenn das Element den Fokus verliert, wird oninput ausgelöst, wenn sich der Wert des Textfelds ändert.

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

Bei der Razor-Attributbindung wird Groß-/Kleinschreibung berücksichtigt:

  • @bind und @bind:event sind gültig.
  • @Bind/@Bind:Event (Großbuchstaben B und E ) oder @BIND/@BIND:EVENT (ausschließlich Großbuchstaben) sind ungültig.

Binden von <select>-Elementoptionen an null-Werte von C#-Objekten

Es gibt aus folgenden Gründen keine vernünftige Möglichkeit, den Optionswert eines <select>-Elements als null-Wert eines C#-Objekts darzustellen:

  • HTML-Attribute können keine null-Werte aufweisen. null entspricht in HTML am ehesten dem Nichtvorhandensein des value-HTML-Attributs im <option>-Element.
  • Wenn Sie ein <option>-Element ohne value-Attribut auswählen, behandelt der Browser den Wert als Textinhalt dieses <option>-Elements.

Das Blazor-Framework versucht nicht, das Standardverhalten zu unterdrücken, weil dies Folgendes beinhalten würde:

  • Erstellen einer Kette von Problemumgehungen für Sonderfälle im Framework.
  • Breaking Changes am aktuellen Frameworkverhalten.

Das plausibelste Äquivalent zu null in HTML ist eine value mit einer leeren Zeichenfolge. Das Blazor-Framework verarbeitet null als Konvertierung einer leeren Zeichenfolge für eine bidirektionale Bindung an einen <select>-Wert.

Nicht analysierbare Werte

Wenn ein Benutzer einem Element mit Datenbindung einen nicht analysierbaren Wert zur Verfügung stellt, wird der nicht analysierbare Wert automatisch auf seinen vorherigen Wert zurückgesetzt, wenn das Bindungsereignis ausgelöst wird.

Beachten Sie die folgende Komponente, in der ein <input>-Element an einen int-Typ mit einem Anfangswert von 123 gebunden ist.

Pages/UnparsableValues.razor:

@page "/unparseable-values"

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

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

@code {
    private int inputValue = 123;
}

Standardmäßig gilt die Bindung für das onchange-Ereignis des Elements. Wenn der Benutzer den Wert des Eintrags des Textfelds in 123.45 aktualisiert und den Fokus ändert, wird der Wert des Elements beim Auslösen von onchange auf 123 zurückgesetzt. Wenn der Wert 123.45 zugunsten des ursprünglichen Werts von 123 abgelehnt wird, versteht der Benutzer, dass sein Wert nicht akzeptiert wurde.

Für das oninput-Ereignis (@bind:event="oninput") erfolgt die Wertumkehr nach jedem Tastendruck, der einen nicht analysierbaren Wert einführt. Wenn das oninput-Ereignis mit einem int-gebundenen Typ adressiert wird, wird ein Benutzer daran gehindert, ein Punktzeichen (.) einzugeben. Ein Punktzeichen (.) wird sofort entfernt, sodass der Benutzer sofort die Rückmeldung erhält, dass nur ganze Zahlen zulässig sind. Es gibt Szenarien, in denen die Umkehrung des Werts auf das oninput Ereignis nicht ideal ist, z. B. wenn dem Benutzer erlaubt werden soll, einen nicht analysierbaren <input>-Wert zu löschen. Zu den Alternativen gehören:

  • Verwenden Sie nicht das Ereignis oninput. Verwenden Sie das Standardereignis onchange, bei dem ein ungültiger Wert erst dann zurückgesetzt wird, wenn das Element den Fokus verliert.
  • Binden Sie an einen Nullable-Typ (z. B. int? oder string), und stellen Sie benutzerdefinierteget Logik und setZugriffsmethodenlogik zur Behandlung ungültiger Einträge bereit.
  • Verwenden Sie eine Formularüberprüfungskomponente wie InputNumber<TValue> oder InputDate<TValue>. Formularüberprüfungskomponenten stellen integrierte Unterstützung zur Verwaltung ungültiger Eingaben bereit. Formularüberprüfungskomponenten:
    • Erlauben Sie dem Benutzer, ungültige Eingaben zu machen und Überprüfungsfehler für das zugehörige EditContext zu erhalten.
    • Zeigen Sie Überprüfungsfehler auf der Benutzeroberfläche an, ohne den Benutzer bei der Eingabe zusätzlicher WebForm-Daten zu beeinträchtigen.

Formatzeichenfolgen

Datenbindung funktioniert mit einer einzelnen DateTime-Formatzeichenfolge mit@bind:format="{FORMAT STRING}", wobei der Platzhalter {FORMAT STRING} die Formatzeichenfolge ist. Andere Formatausdrücke, z. B. Währungs- oder Zahlenformate, sind zurzeit nicht verfügbar, werden jedoch möglicherweise in einem zukünftigen Release hinzugefügt.

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

Im Code oben ist der Feldtyp des <input>-Elements (type-Attribut) standardmäßig auf text festgelegt.

Die Nullable-Typen System.DateTime und System.DateTimeOffset werden unterstützt:

private DateTime? date;
private DateTimeOffset? dateOffset;

Die Angabe eines Formats für den Feldtyp date wird nicht empfohlen, da Blazor eine integrierte Unterstützung für die Formatierung von Daten bietet. Verwenden Sie trotz der Empfehlung nur dann das Datumsformat yyyy-MM-dd für die Bindung, um ordnungsgemäß zu funktionieren, wenn ein Format mit dem Feldtyp date bereitgestellt wird:

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

Benutzerdefinierte Bindungsformate

C# get und setZugriffsmethoden können verwendet werden, um ein benutzerdefiniertes Bindungsformatverhalten zu erstellen, wie die folgende DecimalBinding-Komponente veranschaulicht. Die Komponente bindet eine positive oder negative Dezimalzahl mit bis zu drei Dezimalstellen über eine string-Eigenschaft (DecimalValue) an ein <input>-Element.

Pages/DecimalBinding.razor:

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

<p>
    <label>
        Decimal value (&plusmn;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);
            }
        }
    }
}

Binden mit Komponentenparametern

Ein häufiges Szenario besteht darin, eine Eigenschaft einer untergeordneten Komponente an eine Eigenschaft in ihrer übergeordneten Komponente zu binden. Dieses Szenario wird als verkettete Bindung bezeichnet, da mehrere Ebenen der Bindung gleichzeitig auftreten.

Komponentenparameter erlauben Bindungseigenschaften einer übergeordneten Komponente mit @bind-{PROPERTY}-Syntax, wobei der Platzhalter {PROPERTY} die Eigenschaft ist, die gebunden werden soll.

Verkettete Bindungen können nicht mit der @bind-Syntax in der untergeordneten Komponente implementiert werden. Separat müssen ein Ereignishandler und ein Wert angegeben werden, um die Aktualisierung der Eigenschaft in der übergeordneten Komponente zu unterstützen.

Die übergeordnete Komponente nutzt weiterhin die @bind-Syntax, um die Datenbindung mit der untergeordneten Komponente einzurichten.

Die folgende ChildBind-Komponente (Year) verfügt über einen Komponentenparameter und einen EventCallback<TValue>. Gemäß der Konvention muss EventCallback<TValue> für den Parameter wie der Name des Komponentenparameters mit einem Suffix „Changed“ benannt werden. Die Benennungssyntax ist {PARAMETER NAME}Changed, wobei der Platzhalter {PARAMETER NAME} der Parametername ist. Im folgenden Beispiel trägt EventCallback<TValue> den Namen YearChanged.

EventCallback.InvokeAsync ruft den Delegaten, der der Bindung zugeordnet ist, mit dem bereitgestellten Argument auf und sendet eine Ereignisbenachrichtigung über die geänderte Eigenschaft.

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

Weitere Informationen zu Ereignissen und EventCallback<TValue> finden Sie im Abschnitt EventCallback des Artikels „ASP.NET Core Blazor-Ereignisbehandlung“.

In der folgenden Parent-Komponente ist das Feld year an den Year-Parameter der untergeordneten Komponente gebunden. Der Parameter Year ist bindbar, da er ein Begleitereignis YearChanged aufweist, das dem Typ des Parameters Year entspricht.

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

Gemäß der Konvention kann eine Eigenschaft an einen entsprechenden Ereignishandler gebunden werden, indem ein @bind-{PROPERTY}:event-Attribut einbezogen wird, das dem Handler zugewiesen ist, wobei der Platzhalter {PROPERTY} die Eigenschaft darstellt. <ChildBind @bind-Year="year" /> ist identisch mit dem folgenden Code:

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

In einem komplexeren Beispiel aus der Praxis führt die PasswordEntry-Komponente Folgendes aus:

  • Legt den Wert eines <input>-Elements auf ein password-Feld fest.
  • Macht Änderungen einer Password-Eigenschaft einer übergeordneten Komponente mit einem EventCallback verfügbar, der den aktuellen Wert des password-Felds des untergeordneten Elements als Argument übergibt.
  • Verwendet das onclick-Ereignis zum Auslösen der ToggleShowPassword-Methode. Weitere Informationen finden Sie unter ASP.NET Core Blazor-Ereignisbehandlung.

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

Die PasswordEntry-Komponente wird in einer anderen Komponente verwendet, wie im folgenden PasswordBinding-Komponentenbeispiel.

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

Wenn die PasswordBinding Komponente zum ersten Mal gerendert wird, wird der password-Wert von Not set auf der Benutzeroberfläche angezeigt. Nach dem ersten Rendering berücksichtigt der Wert von password Änderungen, die am Password-Komponentenparameterwert in der PasswordEntry-Komponente vorgenommen wurden.

Hinweis

Im obigen Beispiel wird das Kennwort von der untergeordneten PasswordEntry-Komponente einseitig an die übergeordnete PasswordBinding-Komponente gebunden. Eine zweiseitige Bindung ist in diesem Szenario nicht erforderlich, wenn das Ziel darin besteht, dass die App über eine freigegebene Kennworteingabekomponente für die Wiederverwendung in der App verfügt, die das Kennwort lediglich an das übergeordnete Element übergibt. Eine Vorgehensweise, die eine zweiseitige Bindung ohne direktes Schreiben in den Parameter der untergeordneten Komponente zulässt, finden Sie im NestedChild-Komponentenbeispiel im Abschnitt Binden über mehr als zwei Komponenten dieses Artikels.

Führen Sie die Prüfungen durch, oder fangen Sie Fehler im Handler ab. Die folgende überarbeitete PasswordEntry-Komponente gibt dem Benutzer sofortiges Feedback, wenn ein Leerzeichen im Wert des Kennworts verwendet wird.

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

Binden über mehr als zwei Komponenten

Sie können Parameter über eine beliebige Anzahl geschachtelter Komponenten binden, Sie müssen aber den unidirektionalen Datenfluss berücksichtigen:

  • Änderungsbenachrichtigungen durchlaufen die Hierarchie von unten nach oben.
  • Neue Parameterwerte durchlaufen die Hierarchie von oben nach unten.

Ein gängiges und empfohlenes Verfahren besteht darin, nur die zugrunde liegenden Daten in der übergeordneten Komponente zu speichern, damit Sie immer genau wissen, welcher Zustand aktualisiert werden muss, wie im folgenden Beispiel gezeigt.

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

Warnung

Sie sollten im Allgemeinen das Erstellen von Komponenten vermeiden, die direkt in eigene Komponentenparameter schreiben. Die obige NestedChild-Komponente verwendet eine BoundValue -Eigenschaft, anstatt direkt in den ChildMessage-Parameter zu schreiben. Weitere Informationen finden Sie unter ASP.NET Core-Razor-Komponenten.

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

Einen alternativen Ansatz, der sich für die gemeinsame Nutzung von Daten im Arbeitsspeicher über Komponenten hinweg eignet, die nicht unbedingt geschachtelt sind, finden Sie unter Blazor-Zustandsverwaltung in ASP.NET Core.

Zusätzliche Ressourcen

Razor Komponenten bieten Datenbindungsfunktionen mit dem @bind Razor-Direktivenattribut mit einem Feld, einer Eigenschaft oder einem Razor-Ausdruckswert.

Im folgenden Beispiel werden Elemente gebunden:

  • Ein <input>-Elementwert an das C#-Feld inputValue.
  • Ein zweiter <input>-Elementwert an die C#-Eigenschaft InputValue.

Wenn ein <input>-Element den Fokus verliert, wird das gebundene Feld oder die Eigenschaft aktualisiert.

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

Das Textfeld wird in der Benutzeroberfläche nur dann aktualisiert, wenn die Komponente gerendert wird, nicht als Reaktion auf die Änderung des Werts des Felds oder der Eigenschaft. Da sich Komponenten nach der Ausführung von Ereignishandlercode selbst rendern, werden Feld- und Eigenschaftsaktualisierungen in der Regel unmittelbar nach dem Auslösen eines Ereignishandlers in der Benutzeroberfläche widergespiegelt.

Als Demo, wie Datenbindung in HTML erfolgt, bindet das folgende Beispiel die Eigenschaft InputValue an die Attribute value und onchange des zweiten <input>-Elements. Das zweite <input>-Element im folgenden Beispiel ist eine Konzeptdemo und soll nicht zeigen, wie Daten in Razor-Komponenten gebunden werden sollen.

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

Wenn die BindTheory-Komponente gerendert wird, stammt der value des <input>-Elements der HTML-Demo aus der InputValue-Eigenschaft. Wenn der Benutzer einen Wert in das Textfeld eingibt und den Elementfokus ändert, wird das onchange-Ereignis ausgelöst und die InputValue-Eigenschaft auf den geänderten Wert festgelegt. In Wirklichkeit ist die Codeausführung komplexer, weil @bind Fälle verarbeitet, in denen Typkonvertierungen durchgeführt werden. Im Allgemeinen ordnet @bind den aktuellen Wert eines Ausdrucks einem value-Attribut zu und behandelt Änderungen mit dem registrierten Handler.

Binden Sie eine Eigenschaft oder ein Feld an andere DOM-Ereignisse (Dokumentobjektmodell), indem Sie ein @bind:event="{EVENT}"-Attribut mit einem DOM-Ereignis für den Platzhalter {EVENT} einbinden. Im folgenden Beispiel wird die InputValue-Eigenschaft an den Wert des <input>-Elements gebunden, wenn das oninput-Ereignis des Elements ausgelöst wird. Im Gegensatz zum onchange-Ereignis, das ausgelöst wird, wenn das Element den Fokus verliert, wird oninput ausgelöst, wenn sich der Wert des Textfelds ändert.

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

Bei der Razor-Attributbindung wird Groß-/Kleinschreibung berücksichtigt:

  • @bind und @bind:event sind gültig.
  • @Bind/@Bind:Event (Großbuchstaben B und E ) oder @BIND/@BIND:EVENT (ausschließlich Großbuchstaben) sind ungültig.

Binden von <select>-Elementoptionen an null-Werte von C#-Objekten

Es gibt aus folgenden Gründen keine vernünftige Möglichkeit, den Optionswert eines <select>-Elements als null-Wert eines C#-Objekts darzustellen:

  • HTML-Attribute können keine null-Werte aufweisen. null entspricht in HTML am ehesten dem Nichtvorhandensein des value-HTML-Attributs im <option>-Element.
  • Wenn Sie ein <option>-Element ohne value-Attribut auswählen, behandelt der Browser den Wert als Textinhalt dieses <option>-Elements.

Das Blazor-Framework versucht nicht, das Standardverhalten zu unterdrücken, weil dies Folgendes beinhalten würde:

  • Erstellen einer Kette von Problemumgehungen für Sonderfälle im Framework.
  • Breaking Changes am aktuellen Frameworkverhalten.

Beim Versuch einer bidirektionalen Bindung an einen <select>-Wert verarbeitet das Blazor-Framework null nicht automatisch als Konvertierung einer leeren Zeichenfolge. Weitere Informationen finden Sie unter Fix binding <select> to a null value (dotnet/aspnetcore #23221) (Korrigieren der Bindung von an einen NULL-Wert [dotnet/aspnetcore #23221]).

Nicht analysierbare Werte

Wenn ein Benutzer einem Element mit Datenbindung einen nicht analysierbaren Wert zur Verfügung stellt, wird der nicht analysierbare Wert automatisch auf seinen vorherigen Wert zurückgesetzt, wenn das Bindungsereignis ausgelöst wird.

Beachten Sie die folgende Komponente, in der ein <input>-Element an einen int-Typ mit einem Anfangswert von 123 gebunden ist.

Pages/UnparsableValues.razor:

@page "/unparseable-values"

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

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

@code {
    private int inputValue = 123;
}

Standardmäßig gilt die Bindung für das onchange-Ereignis des Elements. Wenn der Benutzer den Wert des Eintrags des Textfelds in 123.45 aktualisiert und den Fokus ändert, wird der Wert des Elements beim Auslösen von onchange auf 123 zurückgesetzt. Wenn der Wert 123.45 zugunsten des ursprünglichen Werts von 123 abgelehnt wird, versteht der Benutzer, dass sein Wert nicht akzeptiert wurde.

Für das oninput-Ereignis (@bind:event="oninput") erfolgt die Wertumkehr nach jedem Tastendruck, der einen nicht analysierbaren Wert einführt. Wenn das oninput-Ereignis mit einem int-gebundenen Typ adressiert wird, wird ein Benutzer daran gehindert, ein Punktzeichen (.) einzugeben. Ein Punktzeichen (.) wird sofort entfernt, sodass der Benutzer sofort die Rückmeldung erhält, dass nur ganze Zahlen zulässig sind. Es gibt Szenarien, in denen die Umkehrung des Werts auf das oninput Ereignis nicht ideal ist, z. B. wenn dem Benutzer erlaubt werden soll, einen nicht analysierbaren <input>-Wert zu löschen. Zu den Alternativen gehören:

  • Verwenden Sie nicht das Ereignis oninput. Verwenden Sie das Standardereignis onchange, bei dem ein ungültiger Wert erst dann zurückgesetzt wird, wenn das Element den Fokus verliert.
  • Binden Sie an einen Nullable-Typ (z. B. int? oder string), und stellen Sie benutzerdefinierteget Logik und setZugriffsmethodenlogik zur Behandlung ungültiger Einträge bereit.
  • Verwenden Sie eine Formularüberprüfungskomponente wie InputNumber<TValue> oder InputDate<TValue>. Formularüberprüfungskomponenten stellen integrierte Unterstützung zur Verwaltung ungültiger Eingaben bereit. Formularüberprüfungskomponenten:
    • Erlauben Sie dem Benutzer, ungültige Eingaben zu machen und Überprüfungsfehler für das zugehörige EditContext zu erhalten.
    • Zeigen Sie Überprüfungsfehler auf der Benutzeroberfläche an, ohne den Benutzer bei der Eingabe zusätzlicher WebForm-Daten zu beeinträchtigen.

Formatzeichenfolgen

Datenbindung funktioniert mit einer einzelnen DateTime-Formatzeichenfolge mit@bind:format="{FORMAT STRING}", wobei der Platzhalter {FORMAT STRING} die Formatzeichenfolge ist. Andere Formatausdrücke, z. B. Währungs- oder Zahlenformate, sind zurzeit nicht verfügbar, werden jedoch möglicherweise in einem zukünftigen Release hinzugefügt.

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

Im Code oben ist der Feldtyp des <input>-Elements (type-Attribut) standardmäßig auf text festgelegt.

Die Nullable-Typen System.DateTime und System.DateTimeOffset werden unterstützt:

private DateTime? date;
private DateTimeOffset? dateOffset;

Die Angabe eines Formats für den Feldtyp date wird nicht empfohlen, da Blazor eine integrierte Unterstützung für die Formatierung von Daten bietet. Verwenden Sie trotz der Empfehlung nur dann das Datumsformat yyyy-MM-dd für die Bindung, um ordnungsgemäß zu funktionieren, wenn ein Format mit dem Feldtyp date bereitgestellt wird:

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

Benutzerdefinierte Bindungsformate

C# get und setZugriffsmethoden können verwendet werden, um ein benutzerdefiniertes Bindungsformatverhalten zu erstellen, wie die folgende DecimalBinding-Komponente veranschaulicht. Die Komponente bindet eine positive oder negative Dezimalzahl mit bis zu drei Dezimalstellen über eine string-Eigenschaft (DecimalValue) an ein <input>-Element.

Pages/DecimalBinding.razor:

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

<p>
    <label>
        Decimal value (&plusmn;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);
            }
        }
    }
}

Binden mit Komponentenparametern

Ein häufiges Szenario besteht darin, eine Eigenschaft einer untergeordneten Komponente an eine Eigenschaft in ihrer übergeordneten Komponente zu binden. Dieses Szenario wird als verkettete Bindung bezeichnet, da mehrere Ebenen der Bindung gleichzeitig auftreten.

Komponentenparameter erlauben Bindungseigenschaften einer übergeordneten Komponente mit @bind-{PROPERTY}-Syntax, wobei der Platzhalter {PROPERTY} die Eigenschaft ist, die gebunden werden soll.

Verkettete Bindungen können nicht mit der @bind-Syntax in der untergeordneten Komponente implementiert werden. Separat müssen ein Ereignishandler und ein Wert angegeben werden, um die Aktualisierung der Eigenschaft in der übergeordneten Komponente zu unterstützen.

Die übergeordnete Komponente nutzt weiterhin die @bind-Syntax, um die Datenbindung mit der untergeordneten Komponente einzurichten.

Die folgende ChildBind-Komponente (Year) verfügt über einen Komponentenparameter und einen EventCallback<TValue>. Gemäß der Konvention muss EventCallback<TValue> für den Parameter wie der Name des Komponentenparameters mit einem Suffix „Changed“ benannt werden. Die Benennungssyntax ist {PARAMETER NAME}Changed, wobei der Platzhalter {PARAMETER NAME} der Parametername ist. Im folgenden Beispiel trägt EventCallback<TValue> den Namen YearChanged.

EventCallback.InvokeAsync ruft den Delegaten, der der Bindung zugeordnet ist, mit dem bereitgestellten Argument auf und sendet eine Ereignisbenachrichtigung über die geänderte Eigenschaft.

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

Weitere Informationen zu Ereignissen und EventCallback<TValue> finden Sie im Abschnitt EventCallback des Artikels „ASP.NET Core Blazor-Ereignisbehandlung“.

In der folgenden Parent-Komponente ist das Feld year an den Year-Parameter der untergeordneten Komponente gebunden. Der Parameter Year ist bindbar, da er ein Begleitereignis YearChanged aufweist, das dem Typ des Parameters Year entspricht.

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

Gemäß der Konvention kann eine Eigenschaft an einen entsprechenden Ereignishandler gebunden werden, indem ein @bind-{PROPERTY}:event-Attribut einbezogen wird, das dem Handler zugewiesen ist, wobei der Platzhalter {PROPERTY} die Eigenschaft darstellt. <ChildBind @bind-Year="year" /> ist identisch mit dem folgenden Code:

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

In einem komplexeren Beispiel aus der Praxis führt die PasswordEntry-Komponente Folgendes aus:

  • Legt den Wert eines <input>-Elements auf ein password-Feld fest.
  • Macht Änderungen einer Password-Eigenschaft einer übergeordneten Komponente mit einem EventCallback verfügbar, der den aktuellen Wert des password-Felds des untergeordneten Elements als Argument übergibt.
  • Verwendet das onclick-Ereignis zum Auslösen der ToggleShowPassword-Methode. Weitere Informationen finden Sie unter ASP.NET Core Blazor-Ereignisbehandlung.

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

Die PasswordEntry-Komponente wird in einer anderen Komponente verwendet, wie im folgenden PasswordBinding-Komponentenbeispiel.

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

Wenn die PasswordBinding Komponente zum ersten Mal gerendert wird, wird der password-Wert von Not set auf der Benutzeroberfläche angezeigt. Nach dem ersten Rendering berücksichtigt der Wert von password Änderungen, die am Password-Komponentenparameterwert in der PasswordEntry-Komponente vorgenommen wurden.

Hinweis

Im obigen Beispiel wird das Kennwort von der untergeordneten PasswordEntry-Komponente einseitig an die übergeordnete PasswordBinding-Komponente gebunden. Eine zweiseitige Bindung ist in diesem Szenario nicht erforderlich, wenn das Ziel darin besteht, dass die App über eine freigegebene Kennworteingabekomponente für die Wiederverwendung in der App verfügt, die das Kennwort lediglich an das übergeordnete Element übergibt. Eine Vorgehensweise, die eine zweiseitige Bindung ohne direktes Schreiben in den Parameter der untergeordneten Komponente zulässt, finden Sie im NestedChild-Komponentenbeispiel im Abschnitt Binden über mehr als zwei Komponenten dieses Artikels.

Führen Sie die Prüfungen durch, oder fangen Sie Fehler im Handler ab. Die folgende überarbeitete PasswordEntry-Komponente gibt dem Benutzer sofortiges Feedback, wenn ein Leerzeichen im Wert des Kennworts verwendet wird.

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

Binden über mehr als zwei Komponenten

Sie können Parameter über eine beliebige Anzahl geschachtelter Komponenten binden, Sie müssen aber den unidirektionalen Datenfluss berücksichtigen:

  • Änderungsbenachrichtigungen durchlaufen die Hierarchie von unten nach oben.
  • Neue Parameterwerte durchlaufen die Hierarchie von oben nach unten.

Ein gängiges und empfohlenes Verfahren besteht darin, nur die zugrunde liegenden Daten in der übergeordneten Komponente zu speichern, damit Sie immer genau wissen, welcher Zustand aktualisiert werden muss, wie im folgenden Beispiel gezeigt.

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

Warnung

Sie sollten im Allgemeinen das Erstellen von Komponenten vermeiden, die direkt in eigene Komponentenparameter schreiben. Die obige NestedChild-Komponente verwendet eine BoundValue -Eigenschaft, anstatt direkt in den ChildMessage-Parameter zu schreiben. Weitere Informationen finden Sie unter ASP.NET Core-Razor-Komponenten.

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

Einen alternativen Ansatz, der sich für die gemeinsame Nutzung von Daten im Arbeitsspeicher über Komponenten hinweg eignet, die nicht unbedingt geschachtelt sind, finden Sie unter Blazor-Zustandsverwaltung in ASP.NET Core.

Zusätzliche Ressourcen