From 8495494e6ff8d3d0f97fdcd1bf9da670f049d7a7 Mon Sep 17 00:00:00 2001 From: guardrex <1622880+guardrex@users.noreply.github.com> Date: Wed, 25 Oct 2023 10:23:21 -0400 Subject: [PATCH 01/10] Blazor forms node --- .openpublishing.redirection.json | 9 +- .../blazor/components/built-in-components.md | 82 +- aspnetcore/blazor/components/data-binding.md | 12 +- aspnetcore/blazor/components/lifecycle.md | 4 +- aspnetcore/blazor/file-downloads.md | 2 +- aspnetcore/blazor/file-uploads.md | 4 +- aspnetcore/blazor/forms/binding.md | 662 ++++++ .../index.md} | 675 ++---- aspnetcore/blazor/forms/input-components.md | 729 +++++++ aspnetcore/blazor/forms/troubleshoot.md | 218 ++ aspnetcore/blazor/forms/validation.md | 1873 +++++++++++++++++ aspnetcore/blazor/fundamentals/signalr.md | 6 +- .../call-dotnet-from-javascript.md | 2 +- aspnetcore/blazor/security/index.md | 2 +- .../security/server/additional-scenarios.md | 2 +- .../security/server/threat-mitigation.md | 2 +- .../webassembly-lazy-load-assemblies.md | 2 +- aspnetcore/release-notes/aspnetcore-5.0.md | 6 +- aspnetcore/release-notes/aspnetcore-6.0.md | 2 +- aspnetcore/release-notes/aspnetcore-7.0.md | 6 +- aspnetcore/release-notes/aspnetcore-8.0.md | 2 +- aspnetcore/toc.yml | 14 +- .../whats-new/dotnet-AspNetCore.Docs-mod0.md | 2 +- .../whats-new/dotnet-AspNetCore.Docs-mod2.md | 2 +- .../whats-new/dotnet-AspNetCore.Docs-mod3.md | 2 +- 25 files changed, 3746 insertions(+), 576 deletions(-) create mode 100644 aspnetcore/blazor/forms/binding.md rename aspnetcore/blazor/{forms-and-input-components.md => forms/index.md} (89%) create mode 100644 aspnetcore/blazor/forms/input-components.md create mode 100644 aspnetcore/blazor/forms/troubleshoot.md create mode 100644 aspnetcore/blazor/forms/validation.md diff --git a/.openpublishing.redirection.json b/.openpublishing.redirection.json index 9206b88cee83..cbe6cbeb301d 100644 --- a/.openpublishing.redirection.json +++ b/.openpublishing.redirection.json @@ -847,7 +847,7 @@ }, { "source_path": "aspnetcore/razor-components/forms-validation.md", - "redirect_url": "/aspnet/core/blazor/forms-and-input-components", + "redirect_url": "/aspnet/core/blazor/forms/", "redirect_document_id": false }, { @@ -1201,7 +1201,7 @@ }, { "source_path": "aspnetcore/blazor/forms-validation.md", - "redirect_url": "/aspnet/core/blazor/forms-and-input-components", + "redirect_url": "/aspnet/core/blazor/forms/", "redirect_document_id": false }, { @@ -1257,6 +1257,11 @@ "source_path": "aspnetcore/blazor/blazor-server-ef-core.md", "redirect_url": "/aspnet/core/blazor/blazor-ef-core", "redirect_document_id": false + }, + { + "source_path": "aspnetcore/blazor/forms-and-input-components.md", + "redirect_url": "/aspnet/core/blazor/forms/", + "redirect_document_id": false } ] } diff --git a/aspnetcore/blazor/components/built-in-components.md b/aspnetcore/blazor/components/built-in-components.md index a87dabc3a62b..a475e032e480 100644 --- a/aspnetcore/blazor/components/built-in-components.md +++ b/aspnetcore/blazor/components/built-in-components.md @@ -19,7 +19,7 @@ The following built-in Razor components are provided by the Blazor framework: * [`App`](xref:blazor/project-structure) -* [`AntiforgeryToken`](xref:blazor/forms-and-input-components#antiforgery-support) +* [`AntiforgeryToken`](xref:blazor/forms/index#antiforgery-support) * [`Authentication`](xref:blazor/security/webassembly/index#authentication-component) * [`AuthorizeView`](xref:blazor/security/index#authorizeview-component) * [`CascadingValue`](xref:blazor/components/cascading-values-and-parameters#cascadingvalue-component) @@ -28,15 +28,15 @@ The following built-in Razor components are provided by the Blazor framework: * [`FocusOnNavigate`](xref:blazor/fundamentals/routing#focus-an-element-on-navigation) * [`HeadContent`](xref:blazor/components/control-head-content) * [`HeadOutlet`](xref:blazor/components/control-head-content) -* [`InputCheckbox`](xref:blazor/forms-and-input-components#built-in-input-components) -* [`InputDate`](xref:blazor/forms-and-input-components#built-in-input-components) +* [`InputCheckbox`](xref:blazor/forms/input-components) +* [`InputDate`](xref:blazor/forms/input-components) * [`InputFile`](xref:blazor/file-uploads) -* [`InputNumber`](xref:blazor/forms-and-input-components#built-in-input-components) -* [`InputRadio`](xref:blazor/forms-and-input-components#built-in-input-components) -* [`InputRadioGroup`](xref:blazor/forms-and-input-components#built-in-input-components) -* [`InputSelect`](xref:blazor/forms-and-input-components#built-in-input-components) -* [`InputText`](xref:blazor/forms-and-input-components#built-in-input-components) -* [`InputTextArea`](xref:blazor/forms-and-input-components#built-in-input-components) +* [`InputNumber`](xref:blazor/forms/input-components) +* [`InputRadio`](xref:blazor/forms/input-components) +* [`InputRadioGroup`](xref:blazor/forms/input-components) +* [`InputSelect`](xref:blazor/forms/input-components) +* [`InputText`](xref:blazor/forms/input-components) +* [`InputTextArea`](xref:blazor/forms/input-components) * [`LayoutView`](xref:blazor/components/layouts#apply-a-layout-to-arbitrary-content-layoutview-component) * [`MainLayout`](xref:blazor/components/layouts#mainlayout-component) * [`NavLink`](xref:blazor/fundamentals/routing#navlink-and-navmenu-components) @@ -62,15 +62,15 @@ The following built-in Razor components are provided by the Blazor framework: * [`FocusOnNavigate`](xref:blazor/fundamentals/routing#focus-an-element-on-navigation) * [`HeadContent`](xref:blazor/components/control-head-content) * [`HeadOutlet`](xref:blazor/components/control-head-content) -* [`InputCheckbox`](xref:blazor/forms-and-input-components#built-in-input-components) -* [`InputDate`](xref:blazor/forms-and-input-components#built-in-input-components) +* [`InputCheckbox`](xref:blazor/forms/input-components) +* [`InputDate`](xref:blazor/forms/input-components) * [`InputFile`](xref:blazor/file-uploads) -* [`InputNumber`](xref:blazor/forms-and-input-components#built-in-input-components) -* [`InputRadio`](xref:blazor/forms-and-input-components#built-in-input-components) -* [`InputRadioGroup`](xref:blazor/forms-and-input-components#built-in-input-components) -* [`InputSelect`](xref:blazor/forms-and-input-components#built-in-input-components) -* [`InputText`](xref:blazor/forms-and-input-components#built-in-input-components) -* [`InputTextArea`](xref:blazor/forms-and-input-components#built-in-input-components) +* [`InputNumber`](xref:blazor/forms/input-components) +* [`InputRadio`](xref:blazor/forms/input-components) +* [`InputRadioGroup`](xref:blazor/forms/input-components) +* [`InputSelect`](xref:blazor/forms/input-components) +* [`InputText`](xref:blazor/forms/input-components) +* [`InputTextArea`](xref:blazor/forms/input-components) * [`LayoutView`](xref:blazor/components/layouts#apply-a-layout-to-arbitrary-content-layoutview-component) * [`MainLayout`](xref:blazor/components/layouts#mainlayout-component) * [`NavLink`](xref:blazor/fundamentals/routing#navlink-and-navmenu-components) @@ -94,15 +94,15 @@ The following built-in Razor components are provided by the Blazor framework: * [`FocusOnNavigate`](xref:blazor/fundamentals/routing#focus-an-element-on-navigation) * [`HeadContent`](xref:blazor/components/control-head-content) * [`HeadOutlet`](xref:blazor/components/control-head-content) -* [`InputCheckbox`](xref:blazor/forms-and-input-components#built-in-input-components) -* [`InputDate`](xref:blazor/forms-and-input-components#built-in-input-components) +* [`InputCheckbox`](xref:blazor/forms/input-components) +* [`InputDate`](xref:blazor/forms/input-components) * [`InputFile`](xref:blazor/file-uploads) -* [`InputNumber`](xref:blazor/forms-and-input-components#built-in-input-components) -* [`InputRadio`](xref:blazor/forms-and-input-components#built-in-input-components) -* [`InputRadioGroup`](xref:blazor/forms-and-input-components#built-in-input-components) -* [`InputSelect`](xref:blazor/forms-and-input-components#built-in-input-components) -* [`InputText`](xref:blazor/forms-and-input-components#built-in-input-components) -* [`InputTextArea`](xref:blazor/forms-and-input-components#built-in-input-components) +* [`InputNumber`](xref:blazor/forms/input-components) +* [`InputRadio`](xref:blazor/forms/input-components) +* [`InputRadioGroup`](xref:blazor/forms/input-components) +* [`InputSelect`](xref:blazor/forms/input-components) +* [`InputText`](xref:blazor/forms/input-components) +* [`InputTextArea`](xref:blazor/forms/input-components) * [`LayoutView`](xref:blazor/components/layouts#apply-a-layout-to-arbitrary-content-layoutview-component) * [`MainLayout`](xref:blazor/components/layouts#mainlayout-component) * [`NavLink`](xref:blazor/fundamentals/routing#navlink-and-navmenu-components) @@ -120,15 +120,15 @@ The following built-in Razor components are provided by the Blazor framework: * [`Authentication`](xref:blazor/security/webassembly/index#authentication-component) * [`AuthorizeView`](xref:blazor/security/index#authorizeview-component) * [`CascadingValue`](xref:blazor/components/cascading-values-and-parameters#cascadingvalue-component) -* [`InputCheckbox`](xref:blazor/forms-and-input-components#built-in-input-components) -* [`InputDate`](xref:blazor/forms-and-input-components#built-in-input-components) +* [`InputCheckbox`](xref:blazor/forms/input-components) +* [`InputDate`](xref:blazor/forms/input-components) * [`InputFile`](xref:blazor/file-uploads) -* [`InputNumber`](xref:blazor/forms-and-input-components#built-in-input-components) -* [`InputRadio`](xref:blazor/forms-and-input-components#built-in-input-components) -* [`InputRadioGroup`](xref:blazor/forms-and-input-components#built-in-input-components) -* [`InputSelect`](xref:blazor/forms-and-input-components#built-in-input-components) -* [`InputText`](xref:blazor/forms-and-input-components#built-in-input-components) -* [`InputTextArea`](xref:blazor/forms-and-input-components#built-in-input-components) +* [`InputNumber`](xref:blazor/forms/input-components) +* [`InputRadio`](xref:blazor/forms/input-components) +* [`InputRadioGroup`](xref:blazor/forms/input-components) +* [`InputSelect`](xref:blazor/forms/input-components) +* [`InputText`](xref:blazor/forms/input-components) +* [`InputTextArea`](xref:blazor/forms/input-components) * [`LayoutView`](xref:blazor/components/layouts#apply-a-layout-to-arbitrary-content-layoutview-component) * [`MainLayout`](xref:blazor/components/layouts#mainlayout-component) * [`NavLink`](xref:blazor/fundamentals/routing#navlink-and-navmenu-components) @@ -145,14 +145,14 @@ The following built-in Razor components are provided by the Blazor framework: * [`Authentication`](xref:blazor/security/webassembly/index#authentication-component) * [`AuthorizeView`](xref:blazor/security/index#authorizeview-component) * [`CascadingValue`](xref:blazor/components/cascading-values-and-parameters#cascadingvalue-component) -* [`InputCheckbox`](xref:blazor/forms-and-input-components#built-in-input-components) -* [`InputDate`](xref:blazor/forms-and-input-components#built-in-input-components) -* [`InputNumber`](xref:blazor/forms-and-input-components#built-in-input-components) -* [`InputRadio`](xref:blazor/forms-and-input-components#built-in-input-components) -* [`InputRadioGroup`](xref:blazor/forms-and-input-components#built-in-input-components) -* [`InputSelect`](xref:blazor/forms-and-input-components#built-in-input-components) -* [`InputText`](xref:blazor/forms-and-input-components#built-in-input-components) -* [`InputTextArea`](xref:blazor/forms-and-input-components#built-in-input-components) +* [`InputCheckbox`](xref:blazor/forms/input-components) +* [`InputDate`](xref:blazor/forms/input-components) +* [`InputNumber`](xref:blazor/forms/input-components) +* [`InputRadio`](xref:blazor/forms/input-components) +* [`InputRadioGroup`](xref:blazor/forms/input-components) +* [`InputSelect`](xref:blazor/forms/input-components) +* [`InputText`](xref:blazor/forms/input-components) +* [`InputTextArea`](xref:blazor/forms/input-components) * [`LayoutView`](xref:blazor/components/layouts#apply-a-layout-to-arbitrary-content-layoutview-component) * [`MainLayout`](xref:blazor/components/layouts#mainlayout-component) * [`NavLink`](xref:blazor/fundamentals/routing#navlink-and-navmenu-components) diff --git a/aspnetcore/blazor/components/data-binding.md b/aspnetcore/blazor/components/data-binding.md index 7473c49fc8fe..8ffa0ae9d31a 100644 --- a/aspnetcore/blazor/components/data-binding.md +++ b/aspnetcore/blazor/components/data-binding.md @@ -180,7 +180,7 @@ Additional examples } ``` -For more information on the `InputText` component, see . +For more information on the `InputText` component, see . Components support two-way data binding by defining a pair of parameters: @@ -229,7 +229,7 @@ Examples } ``` -For more information on the `InputText` component, see . +For more information on the `InputText` component, see . For another example use of `@bind:get` and `@bind:set`, see the [Bind across more than two components](#bind-across-more-than-two-components) section later in this article. @@ -457,7 +457,7 @@ For the `oninput` event (`@bind:event="oninput"`), a value reversion occurs afte * Don't use the `oninput` event. Use the default `onchange` event, where an invalid value isn't reverted until the element loses focus. * Bind to a nullable type, such as `int?` or `string` and either use `@bind:get`/`@bind:set` modifiers (described earlier in this article) or [bind to a property with custom `get` and `set` accessor logic](#binding-to-a-property-with-c-get-and-set-accessors) to handle invalid entries. -* Use a [form validation component](xref:blazor/forms-and-input-components), such as or . Form validation components provide built-in support to manage invalid inputs. Form validation components: +* Use a [form validation component](xref:blazor/forms/validation), such as or . Form validation components provide built-in support to manage invalid inputs. Form validation components: * Permit the user to provide invalid input and receive validation errors on the associated . * Display validation errors in the UI without interfering with the user entering additional webform data. @@ -812,8 +812,8 @@ For an alternative approach suited to sharing data in memory and across componen ## Additional resources * [Parameter change detection and additional guidance on Razor component rendering](xref:blazor/components/rendering) -* -* [Binding to radio buttons in a form](xref:blazor/forms-and-input-components#radio-buttons) -* [Binding `InputSelect` options to C# object `null` values](xref:blazor/forms-and-input-components#binding-inputselect-options-to-c-object-null-values) +* +* [Binding to radio buttons in a form](xref:blazor/forms/binding#radio-buttons) +* [Binding `InputSelect` options to C# object `null` values](xref:blazor/forms/binding#binding-inputselect-options-to-c-object-null-values) * [ASP.NET Core Blazor event handling: `EventCallback` section](xref:blazor/components/event-handling#eventcallback) * [Blazor samples GitHub repository (`dotnet/blazor-samples`)](https://github.com/dotnet/blazor-samples) diff --git a/aspnetcore/blazor/components/lifecycle.md b/aspnetcore/blazor/components/lifecycle.md index 39a14a763e62..05403dda5e25 100644 --- a/aspnetcore/blazor/components/lifecycle.md +++ b/aspnetcore/blazor/components/lifecycle.md @@ -579,7 +579,7 @@ These are unusual scenarios. For objects that are implemented correctly and beha ### Event handlers -Always unsubscribe event handlers from .NET events. The following [Blazor form](xref:blazor/forms-and-input-components) examples show how to unsubscribe an event handler in the `Dispose` method: +Always unsubscribe event handlers from .NET events. The following [Blazor form](xref:blazor/forms/index) examples show how to unsubscribe an event handler in the `Dispose` method: @@ -690,7 +690,7 @@ When [anonymous functions](/dotnet/csharp/programming-guide/statements-expressio } ``` - The full example of the preceding code with anonymous lambda expressions appears in the article. + The full example of the preceding code with anonymous lambda expressions appears in the article. For more information, see [Cleaning up unmanaged resources](/dotnet/standard/garbage-collection/unmanaged) and the topics that follow it on implementing the `Dispose` and `DisposeAsync` methods. diff --git a/aspnetcore/blazor/file-downloads.md b/aspnetcore/blazor/file-downloads.md index 6d0a7e25694e..f97930a8870b 100644 --- a/aspnetcore/blazor/file-downloads.md +++ b/aspnetcore/blazor/file-downloads.md @@ -254,5 +254,5 @@ For more information on CORS with ASP.NET Core apps and other Microsoft products * * [``: The Anchor element: Security and privacy (MDN documentation)](https://developer.mozilla.org/docs/Web/HTML/Element/a#security_and_privacy) * -* +* * [Blazor samples GitHub repository (`dotnet/blazor-samples`)](https://github.com/dotnet/blazor-samples) diff --git a/aspnetcore/blazor/file-uploads.md b/aspnetcore/blazor/file-uploads.md index 993120f4bd7c..aadd8cfab656 100644 --- a/aspnetcore/blazor/file-uploads.md +++ b/aspnetcore/blazor/file-uploads.md @@ -1419,7 +1419,7 @@ For more information on SignalR configuration and how to set * -* +* * [Blazor samples GitHub repository (`dotnet/blazor-samples`)](https://github.com/dotnet/blazor-samples) :::moniker-end @@ -1427,7 +1427,7 @@ For more information on SignalR configuration and how to set -* +* * [Blazor samples GitHub repository (`dotnet/blazor-samples`)](https://github.com/dotnet/blazor-samples) :::moniker-end diff --git a/aspnetcore/blazor/forms/binding.md b/aspnetcore/blazor/forms/binding.md new file mode 100644 index 000000000000..87659f4dd6c5 --- /dev/null +++ b/aspnetcore/blazor/forms/binding.md @@ -0,0 +1,662 @@ +--- +title: ASP.NET Core Blazor forms binding +author: guardrex +description: Learn how to use binding in Blazor forms. +monikerRange: '>= aspnetcore-3.1' +ms.author: riande +ms.custom: mvc +ms.date: 11/08/2022 +uid: blazor/forms/binding +--- +# ASP.NET Core Blazor forms binding + +[!INCLUDE[](~/includes/not-latest-version.md)] + +An creates an based on the assigned object as a [cascading value](xref:blazor/components/cascading-values-and-parameters) for other components in the form. The tracks metadata about the edit process, including which form fields have been modified and the current validation messages. Assigning to either an or an can bind a form to data. + +## Model binding + +Assignment to : + +:::moniker range=">= aspnetcore-8.0" + +```razor + + ... + + +@code { + [SupplyParameterFromForm] + public Starship? Model { get; set; } + + protected override void OnInitialized() => Model ??= new(); +} +``` + +:::moniker-end + +:::moniker range="< aspnetcore-8.0" + +```razor + + ... + + +@code { + public Starship? Model { get; set; } + + protected override void OnInitialized() => Model ??= new(); +} +``` + +> [!NOTE] +> Most of this article's form model examples bind forms to C# *properties*, but C# field binding is also supported. + +:::moniker-end + +## Context binding + +Assignment to : + +:::moniker range=">= aspnetcore-8.0" + +```razor + + ... + + +@code { + private EditContext? editContext; + + [SupplyParameterFromForm] + public Starship? Model { get; set; } + + protected override void OnInitialized() + { + Model ??= new(); + editContext = new(Model); + } +} +``` + +:::moniker-end + +:::moniker range="< aspnetcore-8.0" + +```razor + + ... + + +@code { + private EditContext? editContext; + + public Starship? Model { get; set; } + + protected override void OnInitialized() + { + Model ??= new(); + editContext = new(Model); + } +} +``` + +:::moniker-end + +Assign **either** an **or** a to an . If both are assigned, a runtime error is thrown. + +:::moniker range=">= aspnetcore-8.0" + +## Supported types + +Binding supports: + +* Primitive types +* Collections +* Complex types +* Recursive types +* Types with constructors +* Enums + +You can also use the [`[DataMember]`](xref:System.Runtime.Serialization.DataMemberAttribute) and [`[IgnoreDataMember]`](xref:System.Runtime.Serialization.IgnoreDataMemberAttribute) attributes to customize model binding. Use these attributes to rename properties, ignore properties, and mark properties as required. + +## Additional binding options + + + +Additional model binding options are available from `RazorComponentOptions` when calling : + +* `MaxFormMappingCollectionSize`: Maximum number of elements allowed in a form collection. +* `MaxFormMappingRecursionDepth`: Maximum depth allowed when recursively mapping form data. +* `MaxFormMappingErrorCount`: Maximum number of errors allowed when mapping form data. +* `MaxFormMappingKeySize`: Maximum size of the buffer used to read form data keys. + +The following demonstrates the default values assigned by the framework: + +```csharp +builder.Services.AddRazorComponents(options => +{ + options.FormMappingUseCurrentCulture = true; + options.MaxFormMappingCollectionSize = 1024; + options.MaxFormMappingErrorCount = 200; + options.MaxFormMappingKeySize = 1024 * 2; + options.MaxFormMappingRecursionDepth = 64; +}).AddInteractiveServerComponents(); +``` + +## Form names + +Use the `FormName` parameter to assign a form name. Form names must be unique to bind model data. The following form is named `RomulanAle`: + +```razor + + ... + +``` + +Supplying a form name: + + + +* Is required on all forms that are submitted via interactivity with server rendering. +* Not required for interactive rendering, which includes forms in Blazor WebAssembly apps and components marked with an interactive render mode. + +The form name is only checked when the form is posted to an endpoint as a traditional HTTP POST request (an SSR form post). The framework doesn't throw an exception at the point of rendering a form, but only at the point that an HTTP POST arrives and doesn't specify a form name. + +> [!WARNING] +> We recommend supplying a unique form name for every form to prevent runtime form posting errors. + +Define a scope for form names using the `FormMappingScope` component, which is useful for preventing form name collisions when a library supplies a form to a component and you have no way to control the form name used by the library's developer. By default, there's an empty-named scope above the app's root component, which suffices when there are no form name collisions. + +In the following example, the `FormMappingScope` scope name is `ParentContext` for the library-supplied form. POST events are routed to the correct form. + +`HelloFormFromLibrary.razor`: + +```razor + + + + + +@if (submitted) +{ +

Hello @Name from the library form!

+} + +@code { + bool submitted = false; + + [SupplyParameterFromForm] + public string? Name { get; set; } + + private void Submit() => submitted = true; +} +``` + +`NamedFormsWithScope.razor`: + +```razor +@page "/named-forms-with-scope" + +
Hello form from a library
+ + + + + +
Hello form using the same form name
+ + + + + + +@if (submitted) +{ +

Hello @Name from the app form!

+} + +@code { + bool submitted = false; + + [SupplyParameterFromForm] + public string? Name { get; set; } + + private void Submit() => submitted = true; +} +``` + +## Supply a parameter from the form (`[SupplyParameterFromForm]`) + +The `[SupplyParameterFromForm]` attribute indicates that the value of the associated property should be supplied from the form data for the form. Data in the request that matches the name of the property is bound to the property. Inputs based on `InputBase` generate form value names that match the names Blazor uses for model binding. + +You can specify the following form binding parameters to the `[SupplyParameterFromForm]` attribute: + +* `Name`: Gets or sets the name for the parameter. The name is used to determine the prefix to use to match the form data and decide whether or not the value needs to be bound. +* `FormName`: Gets or sets the name for the handler. The name is used to match the parameter to the form by form name to decide whether or not the value needs to be bound. + +The following example independently binds two forms to their models by form name. + +`Starship3.razor`: + +```razor +@page "/starship-3" +@rendermode RenderMode.InteractiveServer +@inject ILogger Logger + + + + + + + + + + + +@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; } + } +} +``` + +## Nest and bind forms + +The following guidance demonstrates how to nest and bind child forms. + + + +The following ship details class (`ShipDetails`) holds a description and length for a subform. + +`ShipDetails.cs`: + +```csharp +public class ShipDetails +{ + public string? Description { get; set; } + public int? Length { get; set; } +} +``` + +The following `Ship` class names an identifier (`Id`) and includes the ship details. + +`Ship.cs`: + +```csharp +public class Ship +{ + public string? Id { get; set; } + public ShipDetails Details { get; set; } = new(); +} +``` + +The following subform is used for editing values of the `ShipDetails` type. This is implemented by inheriting `Editor` at the top of the component. `Editor` ensures that the child component generates the correct form field names based on the model (`T`), where `T` in the following example is `ShipDetails`. + +`StarshipSubform.razor`: + +```razor +@inherits Editor + +
+ +
+
+ +
+``` + +The main form is bound to the `Ship` class. The `StarshipSubform` component is used to edit ship details, bound as `Model!.Details`. + +`Starship4.razor`: + +```razor +@page "/starship-4" +@rendermode RenderMode.InteractiveServer +@inject ILogger Logger + + +
+ +
+ +
+ +
+
+ +@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); + } +} +``` + +## Advanced form mapping error scenarios + +The framework instantiates and populates the `FormMappingContext` for a form, which is the context associated with a given form's mapping operation. Each mapping scope (defined by a `FormMappingScope` component) instantiates `FormMappingContext`. Each time a `[SupplyParameterFromForm]` asks the context for a value, the framework populates the `FormMappingContext` with the attempted value and any mapping errors. + +Developers aren't expected to interact with `FormMappingContext` directly, as it's mainly a source of data for `InputBase`, `EditContext`, and other internal implementations to show mapping errors as validation errors. In advanced custom scenarios, developers can access `FormMappingContext` directly as a `[CascadingParameter]` to write custom code that consumes the attempted values and mapping errors. + +:::moniker-end + +## Radio buttons + +:::moniker range=">= aspnetcore-5.0" + +The example in this section is based on the `Starfleet Starship Database` form (`Starship5` component) of the [Example form](#example-form) section of this article. + +Add the following [`enum` types](/dotnet/csharp/language-reference/language-specification/enums) to the app. Create a new file to hold them or add them to the `Starship.cs` file. + +```csharp +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 } +} +``` + +Make the `enums` class accessible to the: + +* `Starship` model in `Starship.cs` (for example, `using static ComponentEnums;`). +* `Starfleet Starship Database` form (`Starship5.razor`) (for example, `@using static ComponentEnums`). + +Use components with the component to create a radio button group. In the following example, properties are added to the `Starship` model described in the [Example form](#example-form) section: + +```csharp +[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; +``` + +Update the `Starfleet Starship Database` form (`Starship5` component) of the [Example form](#example-form) section. Add the components to produce: + +* A radio button group for the ship manufacturer. +* A nested radio button group for engine and ship color. + +> [!NOTE] +> Nested radio button groups aren't often used in forms because they can result in a disorganized layout of form controls that may confuse users. However, there are cases when they make sense in UI design, such as in the following example that pairs recommendations for two user inputs, ship engine and ship color. One engine and one color are required by the form's validation. The form's layout uses nested s to pair engine and color recommendations. However, the user can combine any engine with any color to submit the form. + +> [!NOTE] +> Be sure to make the `ComponentEnums` class available to the component for the following example: +> +> ```razor +> @using static ComponentEnums +> ``` + +```razor +
+ Manufacturer + + @foreach (var manufacturer in (Manufacturer[])Enum + .GetValues(typeof(Manufacturer))) + { +
+ +
+ } +
+
+ +
+ Engine and Color +

+ Engine and color pairs are recommended, but any + combination of engine and color is allowed. +

+ + +
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+
+
+
+``` + +> [!NOTE] +> If `Name` is omitted, components are grouped by their most recent ancestor. + +If you implemented the preceding Razor markup in the `Starship5` component of the [Example form](#example-form) section, update the logging for the `Submit` method: + +```csharp +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); +``` + +:::moniker-end + +:::moniker range="< aspnetcore-5.0" + +When working with radio buttons in a form, data binding is handled differently than other elements because radio buttons are evaluated as a group. The value of each radio button is fixed, but the value of the radio button group is the value of the selected radio button. The following example shows how to: + +* Handle data binding for a radio button group. +* Support validation using a custom component. + +`InputRadio.razor`: + +```razor +@using System.Globalization +@inherits InputBase +@typeparam TValue + + + +@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( + value, CultureInfo.CurrentCulture, out var parsedValue); + if (success) + { + result = parsedValue; + errorMessage = null; + + return true; + } + else + { + result = default; + errorMessage = $"{FieldId.FieldName} field isn't valid."; + + return false; + } + } +} +``` + +For more information on generic type parameters (`@typeparam`), see the following articles: + +* +* +* + +The following `RadioButtonExample` component uses the preceding `InputRadio` component to obtain and validate a rating from the user: + +`RadioButtonExample.razor`: + +```razor +@page "/radio-button-example" +@using System.ComponentModel.DataAnnotations +@using Microsoft.Extensions.Logging +@inject ILogger Logger + +

Radio Button Example

+ + + + + + @for (int i = 1; i <= 5; i++) + { +
+ +
+ } + +
+ +
+
+ +
@Model.Rating
+ +@code { + public Starship Model { get; set; } + + protected override void OnInitialized() => Model ??= new(); + + private void HandleValidSubmit() + { + Logger.LogInformation("HandleValidSubmit called"); + } + + public class Model + { + [Range(1, 5)] + public int Rating { get; set; } + } +} +``` + +:::moniker-end diff --git a/aspnetcore/blazor/forms-and-input-components.md b/aspnetcore/blazor/forms/index.md similarity index 89% rename from aspnetcore/blazor/forms-and-input-components.md rename to aspnetcore/blazor/forms/index.md index 917f17c00800..b5b80060ade3 100644 --- a/aspnetcore/blazor/forms-and-input-components.md +++ b/aspnetcore/blazor/forms/index.md @@ -1,14 +1,14 @@ --- -title: ASP.NET Core Blazor forms and input components +title: ASP.NET Core Blazor forms overview author: guardrex -description: Learn how to use forms with field validation and built-in input components in Blazor. +description: Learn how to use forms in Blazor. monikerRange: '>= aspnetcore-3.1' ms.author: riande ms.custom: mvc ms.date: 11/08/2022 -uid: blazor/forms-and-input-components +uid: blazor/forms/index --- -# ASP.NET Core Blazor forms and input components +# ASP.NET Core Blazor forms overview [!INCLUDE[](~/includes/not-latest-version.md)] @@ -17,16 +17,16 @@ The Blazor framework supports forms and provides built-in input components: :::moniker range=">= aspnetcore-8.0" * Bound to an object or model that can use [data annotations](xref:mvc/models/validation) - * An component * HTML forms with the `
` element -* [Built-in input components](#built-in-input-components) + * An component +* [Built-in input components](xref:blazor/forms/input-components) :::moniker-end :::moniker range="< aspnetcore-8.0" * An component bound to an object or model that can use [data annotations](xref:mvc/models/validation) -* [Built-in input components](#built-in-input-components) +* [Built-in input components](xref:blazor/forms/input-components) :::moniker-end @@ -37,99 +37,61 @@ The namesp A project created from the Blazor project template includes the namespace by default in the app's `_Imports.razor` file, which makes the namespace available to the app's Razor components. -## Examples in this article - :::moniker range=">= aspnetcore-8.0" -Components are configured for interactivity with server rendering and enhanced navigation. For a client-side experience in a Blazor Web App, change the render mode in the `@rendermode` directive at the top of the component to either: - - +## HTML forms -* `RenderMode.InteractiveWebAssembly` for interactive client rendering only. -* `RenderMode.InteractiveAuto` for interactive client rendering after interactive server rendering, which operates while the Blazor app bundle downloads in the background and the .NET WebAssembly runtime starts on the client. + -If working with a standalone Blazor WebAssembly app, render modes aren't used. Blazor WebAssembly apps always run interactively on WebAssembly. The example interactive forms in this article function in a standalone Blazor WebAssembly app as long as the code doesn't make assumptions about running on the server instead of the client. You can remove the `@rendermode` directive from the component when using the example forms in a Blazor WebAssembly app. +Standard interactive HTML forms with server rendering are supported without using an . -When using the WebAssembly render mode, keep in mind that all of the component code is compiled and sent to the client, where users can decompile and inspect it. Don't place private code, app secrets, or other sensitive information in client-rendered components. +Create a form using the normal HTML `` tag and specify an `@onsubmit` handler for handling the submitted form request. -Examples don't adopt enhanced form handling for form POST requests, but all of the examples can be updated to adopt the enhanced features by following the guidance in the [Enhanced form handling](#enhanced-form-handling) section. +`StarshipPlainForm.razor`: -:::moniker-end +```razor +@page "/starship-plain-form" +@rendermode RenderMode.InteractiveServer -:::moniker range="< aspnetcore-5.0" + + + + + -Examples use the [target-typed `new` operator](/dotnet/csharp/language-reference/operators/new-operator#target-typed-new), which was introduced with C# 9.0 and .NET 5. In the following example, the type isn't explicitly stated for the `new` operator: +@code { + [SupplyParameterFromForm] + public Starship? Model { get; set; } -```csharp -public ShipDescription ShipDescription { get; set; } = new(); -``` + protected override void OnInitialized() => Model ??= new(); -If using C# 8.0 or earlier (.NET 3.1), modify the example code to state the type to the `new` operator: + private void Submit() + { + Logger.LogInformation("Id = {Id}", Model?.Id); + } -```csharp -public ShipDescription ShipDescription { get; set; } = new ShipDescription(); + public class Starship + { + public string? Id { get; set; } + } +} ``` -:::moniker-end - -:::moniker range="< aspnetcore-6.0" +The preceding example includes antiforgery support by including an `AntiforgeryToken` component in the form. Antiforgery support is explained further in the [Antiforgery support](#antiforgery-support) section of this article. -Components use nullable reference types (NRTs), and the .NET compiler performs null-state static analysis, both of which are supported in .NET 6 or later. For more information, see . - -If using C# 9.0 or earlier (.NET 5 or earlier), remove the NRTs from the examples. Usually, this merely involves removing the question marks (`?`) and exclamation points (`!`) from the types in the example code. +To submit a form based on another element's DOM events, for example `oninput` or `onblur`, use JavaScript to submit the form ([`submit` (MDN documentation)](https://developer.mozilla.org/docs/Web/API/HTMLFormElement/submit)). -The .NET SDK applies implicit global `using` directives to projects when targeting .NET 6 or later. The examples use a logger to log information about form processing, but it isn't necessary to specify an `@using` directive for the namespace in the component examples. For more information, see [.NET project SDKs: Implicit using directives](/dotnet/core/project-sdk/overview#implicit-using-directives). +> [!IMPORTANT] +> For an HTML form, always use the `@formname` attribute directive to assign the form's name. -If using C# 9.0 or earlier (.NET 5 or earlier), add `@using` directives to the top of the component after the `@page` directive for any API required by the example. Find API namespaces through Visual Studio (right-click the object and select **Peek Definition**) or the [.NET API browser](/dotnet/api/). +Instead of using plain forms in Blazor apps, a form is typically defined with Blazor's built-in form support using the framework's component. The following Razor component demonstrates typical elements, components, and Razor code to render a webform using an component. :::moniker-end -To demonstrate how forms work with [data annotations](xref:mvc/models/validation) validation, example components rely on API. To avoid an extra line of code in each example to use the namespace, make the namespace available throughout the app's components with the imports file. Add the following line to the project's `_Imports.razor` file: - -```razor -@using System.ComponentModel.DataAnnotations -``` - -Form examples reference aspects of the [Star Trek](http://www.startrek.com/) universe. Star Trek is a copyright ©1966-2023 of [CBS Studios](https://www.paramount.com/brand/cbs-studios) and [Paramount](https://www.paramount.com). - - - - - -## Introduction +:::moniker range="< aspnetcore-8.0" A form is defined using the Blazor framework's component. The following Razor component demonstrates typical elements, components, and Razor code to render a webform using an component. -:::moniker range=">= aspnetcore-8.0" - -> [!NOTE] -> Most of this article's examples use Blazor's component to create a form. Alternatively, forms can be bound with an HTML `
` element. For more information, see the [HTML forms](#html-forms) section. - :::moniker-end `Starship1.razor`: @@ -227,7 +189,7 @@ In the next example, the preceding component is modified to create the form in t * If the `` form field contains more than ten characters when the **`Submit`** button is selected, an error appears in the validation summary ("`Id is too long.`"). `Submit` is **not** called. * If the `` form field contains a valid value when the **`Submit`** button is selected, `Submit` is called. -†The component is covered in the [Validator component](#validator-components) section. ‡The component is covered in the [Validation Summary and Validation Message components](#validation-summary-and-validation-message-components) section. +†The component is covered in the [Validator component](xref:blazor/forms/validation#validator-components) section. ‡The component is covered in the [Validation Summary and Validation Message components](xref:blazor/forms/validation#validation-summary-and-validation-message-components) section. `Starship2.razor`: @@ -301,397 +263,218 @@ In the next example, the preceding component is modified to create the form in t :::moniker-end -## Binding - -An creates an based on the assigned object as a [cascading value](xref:blazor/components/cascading-values-and-parameters) for other components in the form. The tracks metadata about the edit process, including which form fields have been modified and the current validation messages. Assigning to either an or an can bind a form to data. +## Handle form submission -### Model binding +The provides the following callbacks for handling form submission: -Assignment to : +* Use to assign an event handler to run when a form with valid fields is submitted. +* Use to assign an event handler to run when a form with invalid fields is submitted. +* Use to assign an event handler to run regardless of the form fields' validation status. The form is validated by calling in the event handler method. If returns `true`, the form is valid. :::moniker range=">= aspnetcore-8.0" -```razor - - ... - - -@code { - [SupplyParameterFromForm] - public Starship? Model { get; set; } +## Antiforgery support - protected override void OnInitialized() => Model ??= new(); -} -``` +The `AntiforgeryToken` component renders an antiforgery token as a hidden field, and the `[RequireAntiforgeryToken]` attribute enables antiforgery protection. If an antiforgery check fails, a [`400 - Bad Request`](https://developer.mozilla.org/docs/Web/HTTP/Status/400) response is thrown and the form isn't processed. -:::moniker-end +For forms based on , the `AntiforgeryToken` component and `[RequireAntiforgeryToken]` attribute are automatically added to provide antiforgery protection by default. -:::moniker range="< aspnetcore-8.0" +For forms based on the HTML `` element, manually add the `AntiforgeryToken` component to the form: ```razor - - ... - +@rendermode RenderMode.InteractiveServer -@code { - public Starship? Model { get; set; } + + + + - protected override void OnInitialized() => Model ??= new(); +@if (submitted) +{ +

Form submitted!

} -``` - -> [!NOTE] -> Most of this article's form model examples bind forms to C# *properties*, but C# field binding is also supported. -:::moniker-end - -### Context binding - -Assignment to : - -:::moniker range=">= aspnetcore-8.0" - -```razor - - ... - - -@code { - private EditContext? editContext; - - [SupplyParameterFromForm] - public Starship? Model { get; set; } +@code{ + private bool submitted = false; - protected override void OnInitialized() - { - Model ??= new(); - editContext = new(Model); - } + private void Submit() => submitted = true; } ``` -:::moniker-end - -:::moniker range="< aspnetcore-8.0" - -```razor - - ... - - -@code { - private EditContext? editContext; - - public Starship? Model { get; set; } +> [!WARNING] +> For forms based on either or the HTML `
` element, antiforgery protection can be disabled by passing `required: false` to the `[RequireAntiforgeryToken]` attribute. The following example disables antiforgery and is ***not recommended*** for public apps: +> +> ```razor +> @using Microsoft.AspNetCore.Antiforgery +> @attribute [RequireAntiforgeryToken(required: false)] +> ``` - protected override void OnInitialized() - { - Model ??= new(); - editContext = new(Model); - } -} -``` +For more information, see . :::moniker-end -Assign **either** an **or** a to an . If both are assigned, a runtime error is thrown. - :::moniker range=">= aspnetcore-8.0" -### Supported types - -Binding supports: - -* Primitive types -* Collections -* Complex types -* Recursive types -* Types with constructors -* Enums - -You can also use the [`[DataMember]`](xref:System.Runtime.Serialization.DataMemberAttribute) and [`[IgnoreDataMember]`](xref:System.Runtime.Serialization.IgnoreDataMemberAttribute) attributes to customize model binding. Use these attributes to rename properties, ignore properties, and mark properties as required. - -### Additional binding options - - - -Additional model binding options are available from `RazorComponentOptions` when calling : - -* `MaxFormMappingCollectionSize`: Maximum number of elements allowed in a form collection. -* `MaxFormMappingRecursionDepth`: Maximum depth allowed when recursively mapping form data. -* `MaxFormMappingErrorCount`: Maximum number of errors allowed when mapping form data. -* `MaxFormMappingKeySize`: Maximum size of the buffer used to read form data keys. - -The following demonstrates the default values assigned by the framework: - -```csharp -builder.Services.AddRazorComponents(options => -{ - options.FormMappingUseCurrentCulture = true; - options.MaxFormMappingCollectionSize = 1024; - options.MaxFormMappingErrorCount = 200; - options.MaxFormMappingKeySize = 1024 * 2; - options.MaxFormMappingRecursionDepth = 64; -}).AddInteractiveServerComponents(); -``` - -### Form names +## Enhanced form handling -Use the `FormName` parameter to assign a form name. Form names must be unique to bind model data. The following form is named `RomulanAle`: +Examples adopt [enhanced navigation](xref:blazor/fundamentals/routing#enhanced-navigation-and-form-handling) for form POST requests with the `Enhance` parameter for `EditForm` forms or the `data-enhance` attribute for HTML forms (``): ```razor - + ... ``` -Supplying a form name: - - - -* Is required on all forms that are submitted via interactivity with server rendering. -* Not required for interactive rendering, which includes forms in Blazor WebAssembly apps and components marked with an interactive render mode. - -The form name is only checked when the form is posted to an endpoint as a traditional HTTP POST request (an SSR form post). The framework doesn't throw an exception at the point of rendering a form, but only at the point that an HTTP POST arrives and doesn't specify a form name. - -> [!WARNING] -> We recommend supplying a unique form name for every form to prevent runtime form posting errors. +```html + + ... + +``` -Define a scope for form names using the `FormMappingScope` component, which is useful for preventing form name collisions when a library supplies a form to a component and you have no way to control the form name used by the library's developer. By default, there's an empty-named scope above the app's root component, which suffices when there are no form name collisions. +Unsupported: You can't set enhanced navigation on a form's ancestor element to enable enhanced form handling. -In the following example, the `FormMappingScope` scope name is `ParentContext` for the library-supplied form. POST events are routed to the correct form. +```html +
+
+ +
+
+``` -`HelloFormFromLibrary.razor`: +Enhanced form posts only work with Blazor endpoints. Posting an enhanced form to non-Blazor endpoint results in an error. -```razor - - - - +To disable enhanced form handling: -@if (submitted) -{ -

Hello @Name from the library form!

-} +* For an `EditForm`, remove the `Enhance` parameter from the form element (or set it to `false`: `Enhance="false"`). +* For an HTML `
`, remove the `data-enhance` attribute from form element (or set it to `false`: `data-enhance="false"`). -@code { - bool submitted = false; +Blazor's enhanced navigation and form handing may undo dynamic changes to the DOM if the updated content isn't part of the server rendering. To preserve the content of an element, use the `data-permanent` attribute. - [SupplyParameterFromForm] - public string? Name { get; set; } +In the following example, the content of the `
` element is updated dynamically by a script when the page loads: - private void Submit() => submitted = true; -} +```html +
+ ... +
``` -`NamedFormsWithScope.razor`: - -```razor -@page "/named-forms-with-scope" - -
Hello form from a library
+To disable enhanced navigation and form handling globally, see . - - - +For guidance on using the `enhancedload` event to listen for enhanced page updates, see . -
Hello form using the same form name
+:::moniker-end - - - - +## Examples -@if (submitted) -{ -

Hello @Name from the app form!

-} +:::moniker range=">= aspnetcore-8.0" -@code { - bool submitted = false; +Components are configured for interactivity with server rendering and enhanced navigation. For a client-side experience in a Blazor Web App, change the render mode in the `@rendermode` directive at the top of the component to either: - [SupplyParameterFromForm] - public string? Name { get; set; } + - private void Submit() => submitted = true; -} -``` +* `RenderMode.InteractiveWebAssembly` for interactive client rendering only. +* `RenderMode.InteractiveAuto` for interactive client rendering after interactive server rendering, which operates while the Blazor app bundle downloads in the background and the .NET WebAssembly runtime starts on the client. -### Supply a parameter from the form (`[SupplyParameterFromForm]`) +If working with a standalone Blazor WebAssembly app, render modes aren't used. Blazor WebAssembly apps always run interactively on WebAssembly. The example interactive forms in this article function in a standalone Blazor WebAssembly app as long as the code doesn't make assumptions about running on the server instead of the client. You can remove the `@rendermode` directive from the component when using the example forms in a Blazor WebAssembly app. -The `[SupplyParameterFromForm]` attribute indicates that the value of the associated property should be supplied from the form data for the form. Data in the request that matches the name of the property is bound to the property. Inputs based on `InputBase` generate form value names that match the names Blazor uses for model binding. +When using the WebAssembly render mode, keep in mind that all of the component code is compiled and sent to the client, where users can decompile and inspect it. Don't place private code, app secrets, or other sensitive information in client-rendered components. -You can specify the following form binding parameters to the `[SupplyParameterFromForm]` attribute: +Examples don't adopt enhanced form handling for form POST requests, but all of the examples can be updated to adopt the enhanced features by following the guidance in the [Enhanced form handling](#enhanced-form-handling) section. -* `Name`: Gets or sets the name for the parameter. The name is used to determine the prefix to use to match the form data and decide whether or not the value needs to be bound. -* `FormName`: Gets or sets the name for the handler. The name is used to match the parameter to the form by form name to decide whether or not the value needs to be bound. +:::moniker-end -The following example independently binds two forms to their models by form name. +:::moniker range="< aspnetcore-5.0" -`Starship3.razor`: +Examples use the [target-typed `new` operator](/dotnet/csharp/language-reference/operators/new-operator#target-typed-new), which was introduced with C# 9.0 and .NET 5. In the following example, the type isn't explicitly stated for the `new` operator: -```razor -@page "/starship-3" -@rendermode RenderMode.InteractiveServer -@inject ILogger Logger +```csharp +public ShipDescription ShipDescription { get; set; } = new(); +``` - - - - +If using C# 8.0 or earlier (.NET 3.1), modify the example code to state the type to the `new` operator: - - - - +```csharp +public ShipDescription ShipDescription { get; set; } = new ShipDescription(); +``` -@code { - [SupplyParameterFromForm(FormName = "Holodeck1")] - public Holodeck? Model1 { get; set; } +:::moniker-end - [SupplyParameterFromForm(FormName = "Holodeck2")] - public Holodeck? Model2 { get; set; } +:::moniker range="< aspnetcore-6.0" - protected override void OnInitialized() - { - Model1 ??= new(); - Model2 ??= new(); - } +Components use nullable reference types (NRTs), and the .NET compiler performs null-state static analysis, both of which are supported in .NET 6 or later. For more information, see . - private void Submit1() - { - Logger.LogInformation("Submit1: Id = {Id}", Model1?.Id); - } +If using C# 9.0 or earlier (.NET 5 or earlier), remove the NRTs from the examples. Usually, this merely involves removing the question marks (`?`) and exclamation points (`!`) from the types in the example code. - private void Submit2() - { - Logger.LogInformation("Submit2: Id = {Id}", Model2?.Id); - } +The .NET SDK applies implicit global `using` directives to projects when targeting .NET 6 or later. The examples use a logger to log information about form processing, but it isn't necessary to specify an `@using` directive for the namespace in the component examples. For more information, see [.NET project SDKs: Implicit using directives](/dotnet/core/project-sdk/overview#implicit-using-directives). - public class Holodeck - { - public string? Id { get; set; } - } -} -``` +If using C# 9.0 or earlier (.NET 5 or earlier), add `@using` directives to the top of the component after the `@page` directive for any API required by the example. Find API namespaces through Visual Studio (right-click the object and select **Peek Definition**) or the [.NET API browser](/dotnet/api/). -### Nest and bind forms +:::moniker-end -The following guidance demonstrates how to nest and bind child forms. +To demonstrate how forms work with [data annotations](xref:mvc/models/validation) validation, example components rely on API. To avoid an extra line of code in each example to use the namespace, make the namespace available throughout the app's components with the imports file. Add the following line to the project's `_Imports.razor` file: - +```razor +@using System.ComponentModel.DataAnnotations +``` -The following ship details class (`ShipDetails`) holds a description and length for a subform. +Form examples reference aspects of the [Star Trek](http://www.startrek.com/) universe. Star Trek is a copyright ©1966-2023 of [CBS Studios](https://www.paramount.com/brand/cbs-studios) and [Paramount](https://www.paramount.com). -`ShipDetails.cs`: + -`Ship.cs`: + -The main form is bound to the `Ship` class. The `StarshipSubform` component is used to edit ship details, bound as `Model!.Details`. +## Additional resources -`Starship4.razor`: +:::moniker range=">= aspnetcore-8.0" -```razor -@page "/starship-4" -@rendermode RenderMode.InteractiveServer -@inject ILogger Logger +* +* [Blazor samples GitHub repository (`dotnet/blazor-samples`)](https://github.com/dotnet/blazor-samples) +* [ASP.NET Core GitHub repository (`dotnet/aspnetcore`) forms test assets](https://github.com/dotnet/aspnetcore/tree/main/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms) - -
- -
- -
- -
-
+:::moniker-end -@code { - [SupplyParameterFromForm] - public Ship? Model { get; set; } +:::moniker range="< aspnetcore-8.0" - protected override void OnInitialized() => Model ??= new(); +* +* +* +* +* [Blazor samples GitHub repository (`dotnet/blazor-samples`)](https://github.com/dotnet/blazor-samples) +* [ASP.NET Core GitHub repository (`dotnet/aspnetcore`) forms test assets](https://github.com/dotnet/aspnetcore/tree/main/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms) - private void Submit() - { - Logger.LogInformation("Id = {Id} Desc = {Description} Length = {Length}", - Model?.Id, Model?.Details?.Description, Model?.Details?.Length); - } -} -``` +:::moniker-end -### Advanced form mapping error scenarios -The framework instantiates and populates the `FormMappingContext` for a form, which is the context associated with a given form's mapping operation. Each mapping scope (defined by a `FormMappingScope` component) instantiates `FormMappingContext`. Each time a `[SupplyParameterFromForm]` asks the context for a value, the framework populates the `FormMappingContext` with the attempted value and any mapping errors. -Developers aren't expected to interact with `FormMappingContext` directly, as it's mainly a source of data for `InputBase`, `EditContext`, and other internal implementations to show mapping errors as validation errors. In advanced custom scenarios, developers can access `FormMappingContext` directly as a `[CascadingParameter]` to write custom code that consumes the attempted values and mapping errors. -:::moniker-end -## Handle form submission -The provides the following callbacks for handling form submission: -* Use to assign an event handler to run when a form with valid fields is submitted. -* Use to assign an event handler to run when a form with invalid fields is submitted. -* Use to assign an event handler to run regardless of the form fields' validation status. The form is validated by calling in the event handler method. If returns `true`, the form is valid. ## Built-in input components @@ -800,7 +583,7 @@ public class Starship The following form accepts and validates user input using: * The properties and validation defined in the preceding `Starship` model. -* Several of Blazor's [built-in input components](#built-in-input-components). +* Several of Blazor's [built-in input components](xref:blazor/forms/input-components). `Starship5.razor`: @@ -1271,7 +1054,7 @@ In the following example, the user must select at least two starship classificat :::moniker range=">= aspnetcore-6.0" -For information on how empty strings and `null` values are handled in data binding, see the [Binding `InputSelect` options to C# object `null` values](#binding-inputselect-options-to-c-object-null-values) section. +For information on how empty strings and `null` values are handled in data binding, see the [Binding `InputSelect` options to C# object `null` values](xref:blazor/forms/binding#binding-inputselect-options-to-c-object-null-values) section. :::moniker-end @@ -1415,7 +1198,7 @@ Assign a custom template to instance can use declared and instances to validate form fields. A handler for the event of the executes custom validation logic. The handler's result updates the instance. -Basic form validation is useful in cases where the form's model is defined within the component hosting the form, either as members directly on the component or in a subclass. Use of a [validator component](#validator-components) is recommended where an independent model class is used across several components. +Basic form validation is useful in cases where the form's model is defined within the component hosting the form, either as members directly on the component or in a subclass. Use of a [validator component](xref:blazor/forms/validation#validator-components) is recommended where an independent model class is used across several components. In the following component, the `HandleValidationRequested` handler method clears any existing validation messages by calling before validating the form. @@ -1614,8 +1397,8 @@ Validator components support form validation by managing a component to attach validation support to forms based on [validation attributes (data annotations)](xref:mvc/models/validation#validation-attributes). You can create custom validator components to process validation messages for different forms on the same page or the same form at different steps of form processing (for example, client validation followed by server validation). The validator component example shown in this section, `CustomValidation`, is used in the following sections of this article: -* [Business logic validation with a validator component](#business-logic-validation-with-a-validator-component) -* [Server validation with a validator component](#server-validation-with-a-validator-component) +* [Business logic validation with a validator component](xref:blazor/forms/validation#business-logic-validation-with-a-validator-component) +* [Server validation with a validator component](xref:blazor/forms/validation#server-validation-with-a-validator-component) > [!NOTE] > Custom data annotation validation attributes can be used instead of custom validator components in many cases. Custom attributes applied to the form's model activate with the use of the component. When used with server validation, any custom attributes applied to the model must be executable on the server. For more information, see . @@ -1703,14 +1486,14 @@ public class CustomValidation : ComponentBase ## Business logic validation with a validator component -For general business logic validation, use a [validator component](#validator-components) that receives form errors in a dictionary. +For general business logic validation, use a [validator component](xref:blazor/forms/validation#validator-components) that receives form errors in a dictionary. [Basic validation](#basic-validation) is useful in cases where the form's model is defined within the component hosting the form, either as members directly on the component or in a subclass. Use of a validator component is recommended where an independent model class is used across several components. In the following example: * A shortened version of the `Starfleet Starship Database` form (`Starship5` component) of the [Example form](#example-form) section is used that only accepts the starship's classification and description. Data annotation validation is **not** triggered on form submission because the `DataAnnotationsValidator` component isn't included in the form. -* The `CustomValidation` component from the [Validator components](#validator-components) section of this article is used. +* The `CustomValidation` component from the [Validator components](xref:blazor/forms/validation#validator-components) section of this article is used. * The validation requires a value for the ship's description (`Description`) if the user selects the "`Defense`" ship classification (`Classification`). When validation messages are set in the component, they're added to the validator's and shown in the 's validation summary. @@ -1870,7 +1653,7 @@ When validation messages are set in the component, they're added to the validato :::moniker-end > [!NOTE] -> As an alternative to using [validation components](#validator-components), data annotation validation attributes can be used. Custom attributes applied to the form's model activate with the use of the component. When used with server validation, the attributes must be executable on the server. For more information, see . +> As an alternative to using [validation components](xref:blazor/forms/validation#validator-components), data annotation validation attributes can be used. Custom attributes applied to the form's model activate with the use of the component. When used with server validation, the attributes must be executable on the server. For more information, see . ## Server validation with a validator component @@ -1901,7 +1684,7 @@ The following example is based on: * A hosted Blazor WebAssembly [solution](xref:blazor/tooling#visual-studio-solution-file-sln) created from the [Blazor WebAssembly project template](xref:blazor/project-structure). The approach is supported for any of the secure hosted Blazor solutions described in the [hosted Blazor WebAssembly security documentation](xref:blazor/security/webassembly/index#implementation-guidance). * The `Starship` model (`Starship.cs`) of the [Example form](#example-form) section. -* The `CustomValidation` component shown in the [Validator components](#validator-components) section. +* The `CustomValidation` component shown in the [Validator components](xref:blazor/forms/validation#validator-components) section. Place the `Starship` model (`Starship.cs`) into the solution's **`Shared`** project so that both the client and server apps can use the model. Add or update the namespace to match the namespace of the shared app (for example, `namespace BlazorSample.Shared`). Since the model requires data annotations, add the [`System.ComponentModel.Annotations`](https://www.nuget.org/packages/System.ComponentModel.Annotations) package to the **`Shared`** project. @@ -2042,7 +1825,7 @@ builder.Services.AddControllersWithViews() For more information, see . -In the **:::no-loc text="Client":::** project, add the `CustomValidation` component shown in the [Validator components](#validator-components) section. Update the namespace to match the app (for example, `namespace BlazorSample.Client`). +In the **:::no-loc text="Client":::** project, add the `CustomValidation` component shown in the [Validator components](xref:blazor/forms/validation#validator-components) section. Update the namespace to match the app (for example, `namespace BlazorSample.Client`). In the **:::no-loc text="Client":::** project, the `Starfleet Starship Database` form is updated to show server validation errors with help of the `CustomValidation` component. When the server API returns validation messages, they're added to the `CustomValidation` component's . The errors are available in the form's for display by the form's validation summary. @@ -2326,7 +2109,7 @@ moniker-end --> > [!NOTE] -> As an alternative to the use of a [validation component](#validator-components), data annotation validation attributes can be used. Custom attributes applied to the form's model activate with the use of the component. When used with server validation, the attributes must be executable on the server. For more information, see . +> As an alternative to the use of a [validation component](xref:blazor/forms/validation#validator-components), data annotation validation attributes can be used. Custom attributes applied to the form's model activate with the use of the component. When used with server validation, the attributes must be executable on the server. For more information, see . > [!NOTE] > The server validation approach in this section is suitable for any of the hosted Blazor WebAssembly solution examples in this documentation set: @@ -3786,98 +3569,7 @@ To submit a form based on another element's DOM events, for example `oninput` or :::moniker-end -:::moniker range=">= aspnetcore-8.0" - -## Antiforgery support - -The `AntiforgeryToken` component renders an antiforgery token as a hidden field, and the `[RequireAntiforgeryToken]` attribute enables antiforgery protection. If an antiforgery check fails, a [`400 - Bad Request`](https://developer.mozilla.org/docs/Web/HTTP/Status/400) response is thrown and the form isn't processed. - -For forms based on , the `AntiforgeryToken` component and `[RequireAntiforgeryToken]` attribute are automatically added to provide antiforgery protection by default. - -For [forms based on the HTML `` element](#html-forms), manually add the `AntiforgeryToken` component to the form: - -```razor -@rendermode RenderMode.InteractiveServer - - - - - - -@if (submitted) -{ -

Form submitted!

-} - -@code{ - private bool submitted = false; - - private void Submit() => submitted = true; -} -``` - -> [!WARNING] -> For forms based on either or the HTML `
` element, antiforgery protection can be disabled by passing `required: false` to the `[RequireAntiforgeryToken]` attribute. The following example disables antiforgery and is ***not recommended*** for public apps: -> -> ```razor -> @using Microsoft.AspNetCore.Antiforgery -> @attribute [RequireAntiforgeryToken(required: false)] -> ``` - -For more information, see . - -:::moniker-end - -:::moniker range=">= aspnetcore-8.0" - -## Enhanced form handling -Examples adopt [enhanced navigation](xref:blazor/fundamentals/routing#enhanced-navigation-and-form-handling) for form POST requests with the `Enhance` parameter for `EditForm` forms or the `data-enhance` attribute for HTML forms (``): - -```razor - - ... - -``` - -```html - - ... - -``` - -Unsupported: You can't set enhanced navigation on a form's ancestor element to enable enhanced form handling. - -```html -
-
- -
-
-``` - -Enhanced form posts only work with Blazor endpoints. Posting an enhanced form to non-Blazor endpoint results in an error. - -To disable enhanced form handling: - -* For an `EditForm`, remove the `Enhance` parameter from the form element (or set it to `false`: `Enhance="false"`). -* For an HTML `
`, remove the `data-enhance` attribute from form element (or set it to `false`: `data-enhance="false"`). - -Blazor's enhanced navigation and form handing may undo dynamic changes to the DOM if the updated content isn't part of the server rendering. To preserve the content of an element, use the `data-permanent` attribute. - -In the following example, the content of the `
` element is updated dynamically by a script when the page loads: - -```html -
- ... -
-``` - -To disable enhanced navigation and form handling globally, see . - -For guidance on using the `enhancedload` event to listen for enhanced page updates, see . - -:::moniker-end ## Troubleshoot @@ -3897,26 +3589,7 @@ When assigning to , c For more information and guidance, see the following resources: -* [Large form payloads and the SignalR message size limit](#large-form-payloads-and-the-signalr-message-size-limit) +* [Large form payloads and the SignalR message size limit](xref:blazor/forms/troubleshoot#large-form-payloads-and-the-signalr-message-size-limit) * -## Additional resources - -:::moniker range=">= aspnetcore-8.0" - -* -* [Blazor samples GitHub repository (`dotnet/blazor-samples`)](https://github.com/dotnet/blazor-samples) -* [ASP.NET Core GitHub repository (`dotnet/aspnetcore`) forms test assets](https://github.com/dotnet/aspnetcore/tree/main/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms) - -:::moniker-end - -:::moniker range="< aspnetcore-8.0" -* -* -* -* -* [Blazor samples GitHub repository (`dotnet/blazor-samples`)](https://github.com/dotnet/blazor-samples) -* [ASP.NET Core GitHub repository (`dotnet/aspnetcore`) forms test assets](https://github.com/dotnet/aspnetcore/tree/main/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Forms) - -:::moniker-end diff --git a/aspnetcore/blazor/forms/input-components.md b/aspnetcore/blazor/forms/input-components.md new file mode 100644 index 000000000000..dfc357dbf66e --- /dev/null +++ b/aspnetcore/blazor/forms/input-components.md @@ -0,0 +1,729 @@ +--- +title: ASP.NET Core Blazor input components +author: guardrex +description: Learn about built-in Blazor input components. +monikerRange: '>= aspnetcore-3.1' +ms.author: riande +ms.custom: mvc +ms.date: 11/08/2022 +uid: blazor/forms/input-components +--- +# ASP.NET Core Blazor input components + +[!INCLUDE[](~/includes/not-latest-version.md)] + +The Blazor framework provides built-in input components to receive and validate user input. The built-in input components in the following table are supported in an with an . + +:::moniker range=">= aspnetcore-7.0" + +The components in the table are also supported outside of a form in Razor component markup. Inputs are validated when they're changed and when a form is submitted. + +:::moniker-end + +:::moniker range=">= aspnetcore-5.0" + +| Input component | Rendered as… | +| --------------- | ------------------- | +| | `` | +| | `` | +| | `` | +| | `` | +| | `` | +| | Group of child | +| | `` | +| | `