From 6670e7e54c41a89319081df3948ccca6480e5fcd Mon Sep 17 00:00:00 2001 From: guardrex <1622880+guardrex@users.noreply.github.com> Date: Fri, 11 Aug 2023 11:44:36 -0400 Subject: [PATCH 1/7] Blazor forms and antiforgery 8.0 --- aspnetcore/blazor/call-web-api.md | 27 + .../blazor/components/built-in-components.md | 1 + .../blazor/forms-and-input-components.md | 2520 ++++++++++++----- aspnetcore/blazor/security/index.md | 14 + aspnetcore/blazor/security/server/index.md | 2 +- aspnetcore/security/anti-request-forgery.md | 23 +- cspell.json | 8 +- 7 files changed, 1859 insertions(+), 736 deletions(-) diff --git a/aspnetcore/blazor/call-web-api.md b/aspnetcore/blazor/call-web-api.md index c77f1adf6450..49618a60494b 100644 --- a/aspnetcore/blazor/call-web-api.md +++ b/aspnetcore/blazor/call-web-api.md @@ -840,6 +840,33 @@ For more information, see . :::zone-end +:::moniker range=">= aspnetcore-8.0" + +## Antiforgery support + + + + +```razor +@inject AntiforgeryStateProvider Antiforgery +``` + +```csharp +private async Task OnSubmit() +{ + var antiforgery = Antiforgery.GetAntiforgeryToken(); + var request = new HttpRequestMessage(HttpMethod.Post, "action"); + request.Headers.Add("RequestVerificationToken", antiforgery.RequestToken); + var response = await client.SendAsync(request); + ... +} +``` + + +For more information, see . + +:::moniker-end + ## Blazor framework component examples for testing web API access Various network tools are publicly available for testing web API backend apps directly, such as [Firefox Browser Developer](https://www.mozilla.org/firefox/developer/) and [Postman](https://www.postman.com). Blazor framework's reference source includes test assets that are useful for testing: diff --git a/aspnetcore/blazor/components/built-in-components.md b/aspnetcore/blazor/components/built-in-components.md index 6fa6d9d26858..a87dabc3a62b 100644 --- a/aspnetcore/blazor/components/built-in-components.md +++ b/aspnetcore/blazor/components/built-in-components.md @@ -19,6 +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) * [`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) diff --git a/aspnetcore/blazor/forms-and-input-components.md b/aspnetcore/blazor/forms-and-input-components.md index 1aa059ea1186..d6d2b30c36cb 100644 --- a/aspnetcore/blazor/forms-and-input-components.md +++ b/aspnetcore/blazor/forms-and-input-components.md @@ -27,196 +27,330 @@ A project created from the Blazor project template includes the namespace by def ```razor @using Microsoft.AspNetCore.Components.Forms ``` +## Examples in this article -:::moniker range=">= aspnetcore-7.0" +Component example forms use more vertical line spacing than is normally found in production apps. The extra spacing is merely present to make the examples clearer. -To demonstrate how an component works, consider the following example. `ExampleModel` represents the data model bound to the form and defines a `Name` property, which is used to store the value of the form's `name` field provided by the user. +:::moniker range=">= aspnetcore-8.0" -`ExampleModel.cs`: +Components are configured for server-side rendering (SSR) and server interactivity. For a client-side experience, change the render mode in the `@attribute` directive at the top of the component to either: -```csharp -public class ExampleModel -{ - public string? Name { get; set; } -} -``` +* `RenderModeWebAssembly` for client-side rendering (CSR) in Blazor Web App projects or for Blazor WebAssembly projects. +* `RenderModeAuto` for CSR and interactivity. For CSR, keep in mind that all of the component code is compiled and set to the client, where users can decompile and inspect it. Never provide private code, app secrets, or other sensitive information in client-side code. :::moniker-end -:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0" +:::moniker range="< aspnetcore-5.0" + +Examples use [target-typed `new` operator](/dotnet/csharp/language-reference/operators/new-operator#target-typed-new), which was introduced at .NET 5 with C# 9.0. In the following example, the type isn't explicitly stated with the `new` operator: -To demonstrate how an component works with [data annotations](xref:mvc/models/validation) validation, consider the following `ExampleModel` type. The `Name` property is marked required with the and specifies a maximum string length limit and error message. +```csharp +public ShipDescription ShipDescription { get; set; } = new(); +``` -`ExampleModel.cs`: +In .NET 3.1, modify the example code to explicitly name the type: -:::code language="csharp" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/ExampleModel.cs" highlight="5-6"::: +```csharp +public ShipDescription ShipDescription { get; set; } = new ShipDescription(); +``` :::moniker-end -:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0" +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 . -To demonstrate how an component works with [data annotations](xref:mvc/models/validation) validation, consider the following `ExampleModel` type. The `Name` property is marked required with the and specifies a maximum string length limit and error message. +The .NET SDK applies implicit global `using` directives to Blazor 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). -`ExampleModel.cs`: +To demonstrate how an component works with [data annotations](xref:mvc/models/validation) validation, example components rely on API. To avoid an extra line of code in each example to add the namespace, make the namespace available throughout the app's components with the imports file. -:::code language="csharp" source="~/../blazor-samples/5.0/BlazorSample_WebAssembly/ExampleModel.cs" highlight="5-6"::: +Add the following line to the project's `_Imports.razor` file: -:::moniker-end +```razor +@using System.ComponentModel.DataAnnotations +``` -:::moniker range="< aspnetcore-5.0" +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). -To demonstrate how an component works with [data annotations](xref:mvc/models/validation) validation, consider the following `ExampleModel` type. The `Name` property is marked required with the and specifies a maximum string length limit and error message. + -:::code language="csharp" source="~/../blazor-samples/3.1/BlazorSample_WebAssembly/ExampleModel.cs" highlight="5-6"::: +## Additional form examples -:::moniker-end +The following additional form examples are available for inspection in the [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): -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, which is bound to the preceding `ExampleModel` type. +* examples + * xxx + * xxx +* HTML forms (`
`) examples + * xxx + * xxx -`Pages/FormExample1.razor`: +[!INCLUDE[](~/includes/aspnetcore-repo-ref-source-links.md)] -:::moniker range=">= aspnetcore-7.0" +## Introduction + +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. + +`Starship1.razor`: + +:::moniker range=">= aspnetcore-8.0" ```razor -@page "/form-example-1" -@using Microsoft.Extensions.Logging -@inject ILogger Logger +@page "/starship-1" +@attribute [RenderModeServer] +@inject ILogger Logger + + - - + + @code { - private ExampleModel exampleModel = new(); + [SupplyParameterFromForm] + public Starship? Model { get; set; } - private void HandleSubmit() + protected override void OnInitialized() => Model ??= new(); + + private void Submit() { - Logger.LogInformation("HandleSubmit called"); + Logger.LogInformation("Id = {Id}", Model?.Id); + } - // Process the form + public class Starship + { + public string? Id { get; set; } } } ``` -In the preceding `FormExample1` component: +In the preceding `Starship1` component: -* The component is rendered where the `` element appears. -* The model is created in the component's `@code` block and held in a private field (`exampleModel`). The field is assigned to 's attribute (`Model`) of the `` element. -* The component is an input component for editing string values. The `@bind-Value` directive attribute binds the `exampleModel.Name` model property to the component's property. -* The `HandleSubmit` method is registered as a handler for the callback. The handler is called when the form is submitted by the user. +* The component is rendered where the `` element appears. The `FormName` parameter sets the form handler name, which is required for posting to a server-side endpoint. The parameter isn't used during interactive rendering. +* The model is created in the component's `@code` block and held in a public property (`Model`). The property is assigned to is assigned to the parameter. The `[SupplyParameterFromForm]` attribute indicates that the value of the associated property should be supplied from the form data for the form. +* The component is an input component for editing string values. The `@bind-Value` directive attribute binds the `Model.Id` model property to the component's property. +* The `Submit` method is registered as a handler for the callback. The handler is called when the form is submitted by the user. + +:::moniker-end + +:::moniker range="< aspnetcore-8.0" + +```razor +@page "/starship-1" +@inject ILogger Logger + + + + + + + + + +@code { + public Starship? Model { get; set; } + + protected override void OnInitialized() => Model ??= new(); + + private void Submit() + { + Logger.LogInformation("Model.Id = {Id}", Model?.Id); + } + + public class Starship + { + public string? Id { get; set; } + } +} +``` -To demonstrate how the preceding component works with [data annotations](xref:mvc/models/validation) validation: +In the preceding `Starship1` component: -* The preceding `ExampleModel` uses the namespace. -* The `Name` property of `ExampleModel` is marked required with the and specifies a maximum string length limit and error message. +* The component is rendered where the `` element appears. +* The model is created in the component's `@code` block and held in a private field (`model`). The field is assigned to the parameter. +* The component is an input component for editing string values. The `@bind-Value` directive attribute binds the `Model.Id` model property to the component's property†. +* The `Submit` method is registered as a handler for the callback. The handler is called when the form is submitted by the user. -`ExampleModel.cs`: +:::moniker-end -:::code language="csharp" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/ExampleModel.cs" highlight="5-6"::: +†For more information on property binding, see . -The earlier `FormExample1` component is modified: +In the next example, the preceding component is modified to create the form in the `Starship2` component: -* is replaced with , which processes assigned event handler if the form is valid when submitted by the user. The method name is changed to `HandleValidSubmit`, which reflects that the method is called when the form is valid. +* is replaced with , which processes assigned event handler if the form is valid when submitted by the user. * A component is added to display validation messages when the form is invalid on form submission. * The data annotations validator ( component†) attaches validation support using data annotations: - * If the `` form field is left blank when the **`Submit`** button is selected, an error appears in the validation summary ( component‡) ("`The Name field is required.`") and `HandleValidSubmit` is **not** called. - * If the `` form field contains more than ten characters when the **`Submit`** button is selected, an error appears in the validation summary ("`Name is too long.`") and `HandleValidSubmit` is **not** called. - * If the `` form field contains a valid value when the **`Submit`** button is selected, `HandleValidSubmit` is called. + * If the `` form field is left blank when the **`Submit`** button is selected, an error appears in the validation summary ( component‡) ("`The Id field is required.`") and `Submit` is **not** called. + * 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.`") and `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. For more information on property binding, see . +†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. -`Pages/FormExample1.razor`: +`Starship2.razor`: -:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/forms-and-validation/FormExample1.razor"::: +:::moniker range=">= aspnetcore-8.0" -:::moniker-end +```razor +@page "/starship-2" +@attribute [RenderModeServer] +@inject ILogger Logger -:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0" + -:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/forms-and-validation/FormExample1.razor"::: + -:::moniker-end + -:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0" + -:::code language="razor" source="~/../blazor-samples/5.0/BlazorSample_WebAssembly/Pages/forms-and-validation/FormExample1.razor"::: + -:::moniker-end + -:::moniker range="< aspnetcore-5.0" +@code { + [SupplyParameterFromForm] + public Starship? Model { get; set; } + + protected override void OnInitialized() => Model ??= new(); + + private void Submit() + { + Logger.LogInformation("Id = {Id}", Model?.Id); + } -:::code language="razor" source="~/../blazor-samples/3.1/BlazorSample_WebAssembly/Pages/forms-and-validation/FormExample1.razor"::: + public class Starship + { + [Required] + [StringLength(10, ErrorMessage = "Id is too long.")] + public string? Id { get; set; } + } +} +``` :::moniker-end -:::moniker range="< aspnetcore-7.0" +:::moniker range="< aspnetcore-8.0" -In the preceding `FormExample1` component: +```razor +@page "/starship-2" +@inject ILogger Logger -* The component is rendered where the `` element appears. -* The model is created in the component's `@code` block and held in a private field (`exampleModel`). The field is assigned to 's attribute (`Model`) of the `` element. -* The component is an input component for editing string values. The `@bind-Value` directive attribute binds the `exampleModel.Name` model property to the component's property. -* The `HandleValidSubmit` method is assigned to . The handler is called if the form passes validation. -* The data annotations validator ( component†) attaches validation support using data annotations: - * If the `` form field is left blank when the **`Submit`** button is selected, an error appears in the validation summary ( component‡) ("`The Name field is required.`") and `HandleValidSubmit` is **not** called. - * If the `` form field contains more than ten characters when the **`Submit`** button is selected, an error appears in the validation summary ("`Name is too long.`") and `HandleValidSubmit` is **not** called. - * If the `` form field contains a valid value when the **`Submit`** button is selected, `HandleValidSubmit` 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. For more information on property binding, see . + + + + +@code { + public Starship? Model { get; set; } + + protected override void OnInitialized() => Model ??= new(); + + private void Submit() + { + Logger.LogInformation("Id = {Id}", Model?.Id); + } + + public class Starship + { + [Required] + [StringLength(10, ErrorMessage = "Id is too long.")] + public string? Id { get; set; } + } +} +``` :::moniker-end ## Binding a form -An creates an based on the assigned model instance 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 fields have been modified and the current validation messages. Assigning to either an or an can bind a form to data. +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. Assignment to : +:::moniker range=">= aspnetcore-8.0" + ```razor - + + ... + @code { - private ExampleModel exampleModel = new() { ... }; + [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; } = new(); +} +``` + +> [!NOTE] +> Most of this article's form model examples bind forms to C# *properties*, but C# field binding is also supported. + +:::moniker-end + Assignment to : -:::moniker range=">= aspnetcore-6.0" +:::moniker range=">= aspnetcore-8.0" ```razor - + + ... + @code { - private ExampleModel exampleModel = new() { ... }; + [SupplyParameterFromForm] + public Starship? Model { get; set; } + private EditContext? editContext; protected override void OnInitialized() { - editContext = new(exampleModel); + Model ??= new(); + editContext = new(Model); } } ``` :::moniker-end -:::moniker range="< aspnetcore-6.0" +:::moniker range="< aspnetcore-8.0" ```razor - + + ... + @code { - private ExampleModel exampleModel = new() { ... }; - private EditContext editContext; + private Starship model = new(); + private EditContext? editContext; - protected override void OnInitialized() - { - editContext = new(exampleModel); - } + protected override void OnInitialized() => editContext = new(model); } ``` @@ -226,6 +360,65 @@ Assign **either** an Unhandled exception rendering component: EditForm requires a Model parameter, or an EditContext parameter, but not both. +:::moniker range=">= aspnetcore-8.0" + +To bind multiple forms, provide each form a unique form name in the `FormName` parameter and pass the form name to the appropriate binding model property in the `[SupplyParameterFromForm]` attribute. + +`Starship3.razor`: + +```razor +@page "/starship-3" +@attribute [RenderModeServer] +@inject ILogger Logger + + + + + + + + + + + + + + + + + +@code { + [SupplyParameterFromForm(Handler = "Starship1")] + public Customer? Model1 { get; set; } + + [SupplyParameterFromForm(Handler = "Starship2")] + public Customer? 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 Starship + { + public string? Id { get; set; } + } +} +``` + +:::moniker-end + ## Handle form submission The provides the following callbacks for handling form submission: @@ -234,6 +427,51 @@ The provides the following * 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" + +## 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, manually add the `AntiforgeryToken` component to the form: + +```razor +@attribute [RenderModeServer] + + + + + + + + + +@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 + ## Built-in input components 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 . @@ -258,178 +496,522 @@ The components in the table are also supported outside of a form in Razor compon | | `` | | | `