ASP.NET Core Blazor-Formularbindung

Hinweis

Dies ist nicht die neueste Version dieses Artikels. Informationen zum aktuellen Release finden Sie in der .NET 8-Version dieses Artikels.

Wichtig

Diese Informationen beziehen sich auf ein Vorabversionsprodukt, das vor der kommerziellen Freigabe möglicherweise noch wesentlichen Änderungen unterliegt. Microsoft gibt keine Garantie, weder ausdrücklich noch impliziert, hinsichtlich der hier bereitgestellten Informationen.

Informationen zum aktuellen Release finden Sie in der .NET 8-Version dieses Artikels.

In diesem Artikel wird erläutert, wie die Bindung in Blazor-Formularen verwendet wird.

EditForm/EditContext Modell

EditForm erstellt einen EditContext auf Grundlage des zugewiesenen Objekts als kaskadierenden Wert für andere Komponenten im Formular. Der EditContext verfolgt Metadaten über den Bearbeitungsprozess nach, einschließlich der Formularfelder, die geändert wurden, und der aktuellen Validierungsnachrichten. Durch das Zuweisen zu einem EditForm.Model oder einem EditForm.EditContext lässt sich ein Formular an Daten binden.

Modellbindung

Zuweisung zu EditForm.Model:

<EditForm ... Model="Model" ...>
    ...
</EditForm>

@code {
    [SupplyParameterFromForm]
    public Starship? Model { get; set; }

    protected override void OnInitialized() => Model ??= new();
}
<EditForm ... Model="Model" ...>
    ...
</EditForm>

@code {
    public Starship? Model { get; set; }

    protected override void OnInitialized() => Model ??= new();
}

Hinweis

Die meisten Formularmodellbeispielen dieses Artikels binden Formulare an C#-Eigenschaften, die C#-Feldbindung wird jedoch ebenfalls unterstützt.

Kontextbindung

Zuweisung zu EditForm.EditContext:

<EditForm ... EditContext="editContext" ...>
    ...
</EditForm>

@code {
    private EditContext? editContext;

    [SupplyParameterFromForm]
    public Starship? Model { get; set; }

    protected override void OnInitialized()
    {
        Model ??= new();
        editContext = new(Model);
    }
}
<EditForm ... EditContext="editContext" ...>
    ...
</EditForm>

@code {
    private EditContext? editContext;

    public Starship? Model { get; set; }

    protected override void OnInitialized()
    {
        Model ??= new();
        editContext = new(Model);
    }
}

Weisen Sie einer EditForm-Klasse entweder eine EditContext-Eigenschaft oder ein Model zu. Wenn beide zugewiesen sind, wird ein Laufzeitfehler ausgelöst.

Unterstützte Typen

Die Bindung unterstützt Folgendes:

  • Primitive Typen
  • Auflistungen
  • Komplexe Typen
  • Rekursive Typen
  • Typen mit Konstruktoren
  • Enumerationen

Sie können auch die Attribute [DataMember] und [IgnoreDataMember] verwenden, um die Modellbindung anzupassen. Verwenden Sie diese Attribute, um Eigenschaften nach Bedarf umzubenennen, zu ignorieren und zu markieren.

Zusätzliche Bindungsoptionen

Zusätzliche Modellbindungsoptionen stehen in RazorComponentsServiceOptions zur Verfügung, wenn AddRazorComponents aufgerufen wird:

Im Folgenden werden die vom Framework zugewiesenen Standardwerte veranschaulicht:

builder.Services.AddRazorComponents(options =>
{
    options.FormMappingUseCurrentCulture = true;
    options.MaxFormMappingCollectionSize = 1024;
    options.MaxFormMappingErrorCount = 200;
    options.MaxFormMappingKeySize = 1024 * 2;
    options.MaxFormMappingRecursionDepth = 64;
}).AddInteractiveServerComponents();

Formularnamen

Verwenden Sie den Parameter FormName, um einen Formularnamen zuzuweisen. Formularnamen müssen eindeutig sein, um Modelldaten zu binden. Das folgende Formular heißt RomulanAle:

<EditForm ... FormName="RomulanAle" ...>
    ...
</EditForm>

Für die Bereitstellung eines Formularnamens gilt:

  • Ist für alle Formulare erforderlich, die von statisch gerenderten serverseitigen Komponenten übermittelt werden.
  • Ist nicht erforderlich für Formulare, die von interaktiv gerenderten Komponenten übermittelt werden, die Formulare in Blazor WebAssembly-Apps und Komponenten mit einem interaktiven Rendermodus enthalten. Es wird jedoch empfohlen, für jedes Formular einen eindeutigen Formularnamen anzugeben, um Formularveröffentlichungsfehler zur Laufzeit zu verhindern, wenn die Interaktivität für ein Formular verworfen wird.

Der Formularname wird nur überprüft, wenn das Formular als herkömmliche HTTP POST-Anforderung von einer statisch gerenderten serverseitigen Komponente an einen Endpunkt übermittelt wird. Das Framework löst eine Ausnahme nicht zum Zeitpunkt des Renderns eines Formulars, sondern erst zu dem Zeitpunkt aus, an dem eine HTTP POST-Anforderung eintrifft. Es gibt keinen Formularnamen an.

Standardmäßig gibt es einen unbenannten Formularbereich (leere Zeichenfolge) oberhalb der Stammkomponente der App, der ausreicht, wenn in der App keine Konflikte bei Formularnamen vorhanden sind. Wenn Konflikte bei Formularnamen möglich sind, z. B. wenn Sie ein Formular aus einer Bibliothek einschließen und keine Kontrolle über den von den Entwickler*innen der Bibliothek verwendeten Formularnamen haben, geben Sie mit der FormMappingScope-Komponente im Hauptprojekt der Blazor-Web-App einen Formularnamenbereich an.

Im folgenden Beispiel hat die HelloFormFromLibrary-Komponente ein Formular namens Hello und befindet sich in einer Bibliothek.

HelloFormFromLibrary.razor:

<EditForm FormName="Hello" Model="this" OnSubmit="Submit">
    <InputText @bind-Value="Name" />
    <button type="submit">Submit</button>
</EditForm>

@if (submitted)
{
    <p>Hello @Name from the library's form!</p>
}

@code {
    bool submitted = false;

    [SupplyParameterFromForm]
    public string? Name { get; set; }

    private void Submit() => submitted = true;
}

Die folgende NamedFormsWithScope-Komponente verwendet die HelloFormFromLibrary-Komponente der Bibliothek, und sie verfügt über ein Formular mit dem Namen Hello. Der Bereichsname der FormMappingScope-Komponente lautet ParentContext für alle Formulare, die von der HelloFormFromLibrary-Komponente bereitgestellt werden. Obwohl beide Formulare in diesem Beispiel denselben Formularnamen aufweisen (Hello), tritt kein Konflikt bei den Formularnamen auf, und die Ereignisse werden für POST-Ereignisse an das richtige Formular weitergeleitet.

NamedFormsWithScope.razor:

@page "/named-forms-with-scope"

<div>Hello form from a library</div>

<FormMappingScope Name="ParentContext">
    <HelloFormFromLibrary />
</FormMappingScope>

<div>Hello form using the same form name</div>

<EditForm FormName="Hello" Model="this" OnSubmit="Submit">
    <InputText @bind-Value="Name" />
    <button type="submit">Submit</button>
</EditForm>

@if (submitted)
{
    <p>Hello @Name from the app form!</p>
}

@code {
    bool submitted = false;

    [SupplyParameterFromForm]
    public string? Name { get; set; }

    private void Submit() => submitted = true;
}

Angeben eines Parameters aus dem Formular ([SupplyParameterFromForm])

Das Attribut [SupplyParameterFromForm] gibt an, dass der Wert der zugeordneten Eigenschaft aus den Formulardaten für das Formular bereitgestellt werden soll. Daten in der Anforderung, die dem Namen der Eigenschaft entsprechen, werden an die Eigenschaft gebunden. Auf InputBase<TValue> basierende Eingaben generieren Formularwertnamen, die den Namen entsprechen, die Blazor für die Modellbindung verwendet.

Sie können die folgenden Formularbindungsparameter für das [SupplyParameterFromForm]-Attribut angeben:

  • Name: Ruft den Namen für den Parameter ab oder legt ihn fest. Der Name wird verwendet, um das Präfix zu bestimmen, das für den Abgleich mit den Formulardaten verwendet werden soll, und um zu entscheiden, ob der Wert gebunden werden muss.
  • FormName: Ruft den Namen für den Handler ab oder legt ihn fest. Der Name wird für den Abgleich des Parameters mit dem Formular anhand des Formularnamens verwendet, um zu entscheiden, ob der Wert gebunden werden muss.

Das folgende Beispiel bindet zwei Formulare unabhängig voneinander anhand des Formularnamens an ihre Modelle.

Starship6.razor:

@page "/starship-6"
@inject ILogger<Starship6> Logger

<EditForm Model="Model1" OnSubmit="Submit1" FormName="Holodeck1">
    <div>
        <label>
            Holodeck 1 Identifier: 
            <InputText @bind-Value="Model1!.Id" />
        </label>
    </div>
    <div>
        <button type="submit">Submit</button>
    </div>
</EditForm>

<EditForm Model="Model2" OnSubmit="Submit2" FormName="Holodeck2">
    <div>
        <label>
            Holodeck 2 Identifier: 
            <InputText @bind-Value="Model2!.Id" />
        </label>
    </div>
    <div>
        <button type="submit">Submit</button>
    </div>
</EditForm>

@code {
    [SupplyParameterFromForm(FormName = "Holodeck1")]
    public Holodeck? Model1 { get; set; }

    [SupplyParameterFromForm(FormName = "Holodeck2")]
    public Holodeck? Model2 { get; set; }

    protected override void OnInitialized()
    {
        Model1 ??= new();
        Model2 ??= new();
    }

    private void Submit1()
    {
        Logger.LogInformation("Submit1: Id = {Id}", Model1?.Id);
    }

    private void Submit2()
    {
        Logger.LogInformation("Submit2: Id = {Id}", Model2?.Id);
    }

    public class Holodeck
    {
        public string? Id { get; set; }
    }
}

Schachteln und Binden von Formularen

Die folgende Anleitung veranschaulicht, wie untergeordnete Formulare geschachtelt und gebunden werden.

Die folgende Seite mit Schiffsdetails (ShipDetails) enthält eine Beschreibung und eine Länge für ein Unterformular.

ShipDetails.cs:

namespace BlazorSample;

public class ShipDetails
{
    public string? Description { get; set; }
    public int? Length { get; set; }
}

Die folgende Ship-Klasse benennt einen Bezeichner (Id) und enthält die Schiffsdetails.

Ship.cs:

namespace BlazorSample
{
    public class Ship
    {
        public string? Id { get; set; }
        public ShipDetails Details { get; set; } = new();
    }
}

Das folgende Unterformular wird zum Bearbeiten von Werten vom Typ ShipDetails verwendet. Dies wird durch Vererben von Editor<T> oben in der Komponente implementiert. Editor<T> stellt sicher, dass die untergeordnete Komponente die richtigen Formularfeldnamen basierend auf dem Modell (T) generiert, wobei T im folgenden Beispiel ShipDetails lautet.

StarshipSubform.razor:

@inherits Editor<ShipDetails>

<div>
    <label>
        Description: 
        <InputText @bind-Value="Value!.Description" />
    </label>
</div>
<div>
    <label>
        Length: 
        <InputNumber @bind-Value="Value!.Length" />
    </label>
</div>

Das Hauptformular ist an die Ship-Klasse gebunden. Die Komponente StarshipSubform wird verwendet, um Lieferdetails zu bearbeiten, die als Model!.Details gebunden sind.

Starship7.razor:

@page "/starship-7"
@inject ILogger<Starship7> Logger

<EditForm Model="Model" OnSubmit="Submit" FormName="Starship7">
    <div>
        <label>
            Identifier: 
            <InputText @bind-Value="Model!.Id" />
        </label>
    </div>
    <StarshipSubform @bind-Value="Model!.Details" />
    <div>
        <button type="submit">Submit</button>
    </div>
</EditForm>

@code {
    [SupplyParameterFromForm]
    public Ship? Model { get; set; }

    protected override void OnInitialized() => Model ??= new();

    private void Submit()
    {
        Logger.LogInformation("Id = {Id} Desc = {Description} Length = {Length}",
            Model?.Id, Model?.Details?.Description, Model?.Details?.Length);
    }
}

Erweiterte Fehlerszenarien bei der Formularzuordnung

Das Framework instanziiert den FormMappingContext für ein Formular und füllt ihn auf. Dies ist der Kontext, der mit dem Zuordnungsvorgang eines bestimmten Formulars verknüpft ist. Jeder Zuordnungsbereich (definiert durch eine FormMappingScope-Komponente) instanziiert FormMappingContext. Jedes Mal, wenn ein [SupplyParameterFromForm] den Kontext nach einem Wert abfragt, füllt das Framework den FormMappingContext mit dem versuchten Wert und allen Zuordnungsfehlern auf.

Entwickler*innen interagieren wahrscheinlich nicht direkt mit FormMappingContext, da es sich hauptsächlich um eine Datenquelle für InputBase<TValue>, EditContext und andere interne Implementierungen handelt, um Zuordnungsfehler als Validierungsfehler anzuzeigen. In erweiterten benutzerdefinierten Szenarien können Entwickler*innen direkt als [CascadingParameter] auf FormMappingContext zugreifen, um benutzerdefinierten Code zu schreiben, der die versuchten Werte und Fehlerzuordnungen verwendet.

Optionsfelder

Das Beispiel in diesem Abschnitt basiert auf dem Starfleet Starship Database-Formular (Starship3-Komponente) des Abschnitts Beispielformular in diesem Artikel.

Fügen Sie der App die folgenden enum-Typen hinzu. Erstellen Sie eine neue Datei, um diese aufzunehmen, oder fügen Sie sie der Datei Starship.cs hinzu.

public class ComponentEnums
{
    public enum Manufacturer { SpaceX, NASA, ULA, VirginGalactic, Unknown }
    public enum Color { ImperialRed, SpacecruiserGreen, StarshipBlue, VoyagerOrange }
    public enum Engine { Ion, Plasma, Fusion, Warp }
}

Machen Sie die enums-Klasse für Folgendes zugänglich:

  • Starship-Modell in Starship.cs (z. B. using static ComponentEnums;).
  • Starfleet Starship Database-Formular (Starship3.razor) (z. B. @using static ComponentEnums).

Verwenden Sie InputRadio<TValue>-Komponenten mit der InputRadioGroup<TValue>-Komponente, um eine Optionsfeldgruppe zu erstellen. Im folgenden Beispiel werden dem im Abschnitt Beispielformular des Artikels Eingabekomponenten beschriebenen Starship-Modell Eigenschaften hinzugefügt:

[Required]
[Range(typeof(Manufacturer), nameof(Manufacturer.SpaceX), 
    nameof(Manufacturer.VirginGalactic), ErrorMessage = "Pick a manufacturer.")]
public Manufacturer Manufacturer { get; set; } = Manufacturer.Unknown;

[Required, EnumDataType(typeof(Color))]
public Color? Color { get; set; } = null;

[Required, EnumDataType(typeof(Engine))]
public Engine? Engine { get; set; } = null;

Aktualisieren Sie das Starfleet Starship Database-Formular (Starship3-Komponente) im Abschnitt Beispielformular des Artikels Eingabekomponenten. Fügen Sie die Komponenten hinzu, damit Folgendes erstellt wird:

  • Eine Optionsfeldgruppe für den Schiffshersteller.
  • Eine geschachtelte Optionsfeldgruppe für den Motor und die Farbe des Schiffs.

Hinweis

Geschachtelte Optionsfeldgruppen werden nicht häufig in Formularen verwendet, weil sie zu einem unorganisierten Layout von Formularsteuerelementen führen können, das Benutzer verwirren kann. Es gibt jedoch Fälle, in denen sie im Benutzeroberflächenentwurf sinnvoll sind, z. B. im folgenden Beispiel, bei dem Empfehlungen für zwei Benutzereingaben (Schiffsmotor (ship enginge) und Schiffsfarbe (ship color)) gepaart werden. Für die Validierung des Formulars ist ein Motor und eine Farbe erforderlich. Das Layout des Formulars verwendet geschachtelte InputRadioGroup<TValue>s, um Empfehlungen für Motor und Farbe zu paaren. Der Benutzer kann allerdings jeden Motor mit jeder Farbe kombinieren, um das Formular zu übermitteln.

Hinweis

Stellen Sie sicher, dass die ComponentEnums-Klasse für die Komponente für das folgende Beispiel verfügbar ist:

@using static ComponentEnums
<fieldset>
    <legend>Manufacturer</legend>
    <InputRadioGroup @bind-Value="Model!.Manufacturer">
        @foreach (var manufacturer in Enum.GetValues<Manufacturer>())
        {
            <div>
                <label>
                    <InputRadio Value="manufacturer" />
                    @manufacturer
                </label>
            </div>
        }
    </InputRadioGroup>
</fieldset>

<fieldset>
    <legend>Engine and Color</legend>
    <p>
        Engine and color pairs are recommended, but any
        combination of engine and color is allowed.
    </p>
    <InputRadioGroup Name="engine" @bind-Value="Model!.Engine">
        <InputRadioGroup Name="color" @bind-Value="Model!.Color">
            <div style="margin-bottom:5px">
                <div>
                    <label>
                        <InputRadio Name="engine" Value="Engine.Ion" />
                        Ion
                    </label>
                </div>
                <div>
                    <label>
                        <InputRadio Name="color" Value="Color.ImperialRed" />
                        Imperial Red
                    </label>
                </div>
            </div>
            <div style="margin-bottom:5px">
                <div>
                    <label>
                        <InputRadio Name="engine" Value="Engine.Plasma" />
                        Plasma
                    </label>
                </div>
                <div>
                    <label>
                        <InputRadio Name="color" Value="Color.SpacecruiserGreen" />
                        Spacecruiser Green
                    </label>
                </div>
            </div>
            <div style="margin-bottom:5px">
                <div>
                    <label>
                        <InputRadio Name="engine" Value="Engine.Fusion" />
                        Fusion
                    </label>
                </div>
                <div>
                    <label>
                        <InputRadio Name="color" Value="Color.StarshipBlue" />
                        Starship Blue
                    </label>
                </div>
            </div>
            <div style="margin-bottom:5px">
                <div>
                    <label>
                        <InputRadio Name="engine" Value="Engine.Warp" />
                        Warp
                    </label>
                </div>
                <div>
                    <label>
                        <InputRadio Name="color" Value="Color.VoyagerOrange" />
                        Voyager Orange
                    </label>
                </div>
            </div>
        </InputRadioGroup>
    </InputRadioGroup>
</fieldset>

Hinweis

Wenn Name ausgelassen wird, werden die InputRadio<TValue>-Komponenten nach dem direkten Vorgänger gruppiert.

Wenn Sie das vorangehende Razor-Markup in der Starship3-Komponente des Abschnitts Beispielformular des Artikels Eingabekomponenten implementiert haben, aktualisieren Sie die Protokollierung für die Submit-Methode:

Logger.LogInformation("Id = {Id} Description = {Description} " +
    "Classification = {Classification} MaximumAccommodation = " +
    "{MaximumAccommodation} IsValidatedDesign = " +
    "{IsValidatedDesign} ProductionDate = {ProductionDate} " +
    "Manufacturer = {Manufacturer}, Engine = {Engine}, " +
    "Color = {Color}",
    Model?.Id, Model?.Description, Model?.Classification,
    Model?.MaximumAccommodation, Model?.IsValidatedDesign,
    Model?.ProductionDate, Model?.Manufacturer, Model?.Engine, 
    Model?.Color);

Beim Verwenden von Optionsfeldern in einem Formular werden Datenbindungen nicht wie andere Elemente verarbeitet, da Optionsfelder als Gruppe ausgewertet werden. Der Wert der einzelnen Optionsfelder ist fest. Der Wert der Optionsfeldgruppe ist jedoch der Wert des ausgewählten Optionsfelds. Das folgende Beispiel veranschaulicht die Vorgehensweise:

  • Verarbeiten von Datenbindungen für eine Optionsfeldgruppe
  • Unterstützen der Validierung mithilfe einer benutzerdefinierten InputRadio<TValue>-Komponente

InputRadio.razor:

@using System.Globalization
@inherits InputBase<TValue>
@typeparam TValue

<input @attributes="AdditionalAttributes" type="radio" value="@SelectedValue" 
       checked="@(SelectedValue.Equals(Value))" @onchange="OnChange" />

@code {
    [Parameter]
    public TValue SelectedValue { get; set; }

    private void OnChange(ChangeEventArgs args)
    {
        CurrentValueAsString = args.Value.ToString();
    }

    protected override bool TryParseValueFromString(string value, 
        out TValue result, out string errorMessage)
    {
        var success = BindConverter.TryConvertTo<TValue>(
            value, CultureInfo.CurrentCulture, out var parsedValue);
        if (success)
        {
            result = parsedValue;
            errorMessage = null;

            return true;
        }
        else
        {
            result = default;
            errorMessage = "The field isn't valid.";

            return false;
        }
    }
}

Weitere Informationen zu den generischen Typparametern (@typeparam) finden Sie in den folgenden Artikeln:

Verwenden Sie das folgende Beispielmodell.

StarshipModel.cs:

using System.ComponentModel.DataAnnotations;

namespace BlazorServer80
{
    public class Model
    {
        [Range(1, 5)]
        public int Rating { get; set; }
    }
}

Die folgende RadioButtonExample-Komponente verwendet die vorangehende InputRadio-Komponente, um eine Bewertung vom Benutzer zu erhalten und sie zu überprüfen:

RadioButtonExample.razor:

@page "/radio-button-example"
@using System.ComponentModel.DataAnnotations
@using Microsoft.Extensions.Logging
@inject ILogger<RadioButtonExample> Logger

<h1>Radio Button Example</h1>

<EditForm Model="Model" OnValidSubmit="HandleValidSubmit">
    <DataAnnotationsValidator />
    <ValidationSummary />

    @for (int i = 1; i <= 5; i++)
    {
        <div>
            <label>
                <InputRadio name="rate" SelectedValue="i" 
                    @bind-Value="Model.Rating" />
                @i
            </label>
        </div>
    }

    <div>
        <button type="submit">Submit</button>
    </div>
</EditForm>

<div>@Model.Rating</div>

@code {
    public StarshipModel Model { get; set; }

    protected override void OnInitialized() => Model ??= new();

    private void HandleValidSubmit()
    {
        Logger.LogInformation("HandleValidSubmit called");
    }
}