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 (`
-
- Star Trek ,
- ©1966-2019 CBS Studios, Inc. and
- Paramount Pictures
-
@code {
@@ -1059,9 +1904,14 @@ In the following `FormExample6` component, update the namespace of the **`Shared
private string? message;
private string? messageStyles = "visibility:hidden";
private CustomValidation? customValidation;
- private Starship starship = new() { ProductionDate = DateTime.UtcNow };
+
+ [SupplyParameterFromForm]
+ public Starship? Model { get; set; }
- private async Task HandleValidSubmit(EditContext editContext)
+ protected override void OnInitialized() =>
+ Model ??= new() { ProductionDate = DateTime.UtcNow };
+
+ private async Task Submit(EditContext editContext)
{
customValidation?.ClearErrors();
@@ -1106,48 +1956,56 @@ In the following `FormExample6` component, update the namespace of the **`Shared
}
```
+
+
:::moniker-end
-:::moniker range="< aspnetcore-6.0"
+:::moniker range="< aspnetcore-8.0"
```razor
-@page "/form-example-6"
+@page "/starship-9"
@using System.Net
@using System.Net.Http.Json
@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
-@using Microsoft.Extensions.Logging
@using BlazorSample.Shared
@attribute [Authorize]
@inject HttpClient Http
-@inject ILogger Logger
+@inject ILogger Logger
Starfleet Starship Database
New Ship Entry Form
-
+
+
+
+
- Identifier:
-
+ Id:
+
+
Description (optional):
-
+
Primary Classification:
-
+
Select classification ...
Exploration
Diplomacy
@@ -1155,24 +2013,27 @@ In the following `FormExample6` component, update the namespace of the **`Shared
+
Maximum Accommodation:
-
+
Engineering Approval:
-
+
Production Date:
-
+
@@ -1182,23 +2043,22 @@ In the following `FormExample6` component, update the namespace of the **`Shared
@message
-
- Star Trek ,
- ©1966-2019 CBS Studios, Inc. and
- Paramount Pictures
-
@code {
private bool disabled;
- private string message;
- private string messageStyles = "visibility:hidden";
- private CustomValidation customValidation;
- private Starship starship = new() { ProductionDate = DateTime.UtcNow };
+ private string? message;
+ private string? messageStyles = "visibility:hidden";
+ private CustomValidation? customValidation;
+
+ public Starship? Model { get; set; }
+
+ protected override void OnInitialized() =>
+ Model ??= new() { ProductionDate = DateTime.UtcNow };
- private async Task HandleValidSubmit(EditContext editContext)
+ private async Task Submit(EditContext editContext)
{
- customValidation.ClearErrors();
+ customValidation?.ClearErrors();
try
{
@@ -1206,12 +2066,13 @@ In the following `FormExample6` component, update the namespace of the **`Shared
"StarshipValidation", (Starship)editContext.Model);
var errors = await response.Content
- .ReadFromJsonAsync>>();
+ .ReadFromJsonAsync>>() ??
+ new Dictionary>();
if (response.StatusCode == HttpStatusCode.BadRequest &&
errors.Any())
{
- customValidation.DisplayErrors(errors);
+ customValidation?.DisplayErrors(errors);
}
else if (!response.IsSuccessStatusCode)
{
@@ -1240,6 +2101,10 @@ In the following `FormExample6` component, update the namespace of the **`Shared
}
```
+
+
:::moniker-end
> [!NOTE]
@@ -1256,87 +2121,116 @@ In the following `FormExample6` component, update the namespace of the **`Shared
Use the component to create a custom component that uses the `oninput` event ([`input`](https://developer.mozilla.org/docs/Web/API/HTMLElement/input_event)) instead of the `onchange` event ([`change`](https://developer.mozilla.org/docs/Web/API/HTMLElement/change_event)). Use of the `input` event triggers field validation on each keystroke.
-The following example uses the `ExampleModel` class.
-
-`ExampleModel.cs`:
-
-:::moniker range=">= aspnetcore-7.0"
-
-:::code language="csharp" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/ExampleModel.cs":::
-
-:::moniker-end
+The following `CustomInputText` component inherits the framework's `InputText` component and sets event binding to the `oninput` event ([`input`](https://developer.mozilla.org/docs/Web/API/HTMLElement/input_event)).
-:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0"
+`CustomInputText.razor`:
-:::code language="csharp" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/ExampleModel.cs":::
+```razor
+@inherits InputText
-:::moniker-end
+
+```
-:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0"
+The `CustomInputText` component can be used anywhere is used. The following component uses the shared `CustomInputText` component.
-:::code language="csharp" source="~/../blazor-samples/5.0/BlazorSample_WebAssembly/ExampleModel.cs":::
+`Starship10.razor`:
-:::moniker-end
+:::moniker range=">= aspnetcore-8.0"
-:::moniker range="< aspnetcore-5.0"
+```razor
+@page "/starship-10"
+@attribute [RenderModeServer]
+@inject ILogger Logger
-:::code language="csharp" source="~/../blazor-samples/3.1/BlazorSample_WebAssembly/ExampleModel.cs":::
+
-:::moniker-end
+
-The following `CustomInputText` component inherits the framework's `InputText` component and sets event binding to the `oninput` event ([`input`](https://developer.mozilla.org/docs/Web/API/HTMLElement/input_event)).
+
-`Shared/CustomInputText.razor`:
+
-:::moniker range=">= aspnetcore-7.0"
+ Submit
-:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Shared/forms-and-validation/CustomInputText.razor":::
+
-:::moniker-end
+
+ CurrentValue: @Model?.Id
+
-:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0"
+@code {
+ [SupplyParameterFromForm]
+ public Starship? Model { get; set; }
-:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Shared/forms-and-validation/CustomInputText.razor":::
+ protected override void OnInitialized() => Model ??= new();
-:::moniker-end
+ private void Submit()
+ {
+ Logger.LogInformation("Submit called: Processing the form");
+ }
-:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0"
+ public class Starship
+ {
+ [Required]
+ [StringLength(10, ErrorMessage = "Id is too long.")]
+ public string? Id { get; set; }
+ }
+}
+```
-:::code language="razor" source="~/../blazor-samples/5.0/BlazorSample_WebAssembly/Shared/forms-and-validation/CustomInputText.razor":::
+
:::moniker-end
-:::moniker range="< aspnetcore-5.0"
-
-:::code language="razor" source="~/../blazor-samples/3.1/BlazorSample_WebAssembly/Shared/forms-and-validation/CustomInputText.razor":::
-
-:::moniker-end
+:::moniker range="< aspnetcore-8.0"
-The `CustomInputText` component can be used anywhere is used. The following `FormExample7` component uses the shared `CustomInputText` component.
+```razor
+@page "/starship-10"
+@inject ILogger Logger
-`Pages/FormExample7.razor`:
+
-:::moniker range=">= aspnetcore-7.0"
+
-:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/forms-and-validation/FormExample7.razor" highlight="9":::
+
-:::moniker-end
+
-:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0"
+ Submit
-:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/forms-and-validation/FormExample7.razor" highlight="9":::
+
-:::moniker-end
+
+ CurrentValue: @Model?.Id
+
-:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0"
+@code {
+ public Starship? Model { get; set; }
-:::code language="razor" source="~/../blazor-samples/5.0/BlazorSample_WebAssembly/Pages/forms-and-validation/FormExample7.razor" highlight="9":::
+ protected override void OnInitialized() => Model ??= new();
-:::moniker-end
+ private void Submit()
+ {
+ Logger.LogInformation("Submit called: Processing the form");
+ }
-:::moniker range="< aspnetcore-5.0"
+ public class Starship
+ {
+ [Required]
+ [StringLength(10, ErrorMessage = "Id is too long.")]
+ public string? Id { get; set; }
+ }
+}
+```
-:::code language="razor" source="~/../blazor-samples/3.1/BlazorSample_WebAssembly/Pages/forms-and-validation/FormExample7.razor" highlight="9":::
+
:::moniker-end
@@ -1402,7 +2296,7 @@ public Engine Engine { get; set; } = null;
:::moniker range=">= aspnetcore-5.0"
-Update the `Starfleet Starship Database` form (`FormExample2` component) from the [Example form](#example-form) section. Add the components to produce:
+Update the `Starfleet Starship Database` form (`Starship4` component) from 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.
@@ -1410,10 +2304,59 @@ Update the `Starfleet Starship Database` form (`FormExample2` component) from th
> [!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.
+:::moniker-end
+
+:::moniker range=">= aspnetcore-8.0"
+
+```razor
+
+ Manufacturer
+
+ @foreach (var manufacturer in (Manufacturer[])Enum
+ .GetValues(typeof(Manufacturer)))
+ {
+
+
+ @manufacturer
+
+ }
+
+
+
+
+ Select one engine and one color. Recommendations are paired but any
+ combination of engine and color is allowed:
+
+
+
+ Engine: Ion
+
+ Color: Imperial Red
+
+ Engine: Plasma
+
+ Color: Spacecruiser Green
+
+ Engine: Fusion
+
+ Color: Starship Blue
+
+ Engine: Warp
+
+ Color: Voyager Orange
+
+
+
+```
+
+:::moniker-end
+
+:::moniker range=">= aspnetcore-5.0 < aspnetcore-8.0"
+
```razor
Manufacturer
-
+
@foreach (var manufacturer in (Manufacturer[])Enum
.GetValues(typeof(Manufacturer)))
{
@@ -1428,8 +2371,8 @@ Update the `Starfleet Starship Database` form (`FormExample2` component) from th
Select one engine and one color. Recommendations are paired but any
combination of engine and color is allowed:
-
-
+
+
Engine: Ion
@@ -1451,6 +2394,10 @@ Update the `Starfleet Starship Database` form (`FormExample2` component) from th
```
+:::moniker-end
+
+:::moniker range=">= aspnetcore-5.0"
+
> [!NOTE]
> If `Name` is omitted, components are grouped by their most recent ancestor.
@@ -1463,7 +2410,7 @@ When working with radio buttons in a form, data binding is handled differently t
* Handle data binding for a radio button group.
* Support validation using a custom component.
-`Shared/InputRadio.razor`:
+`InputRadio.razor`:
```razor
@using System.Globalization
@@ -1497,7 +2444,7 @@ When working with radio buttons in a form, data binding is handled differently t
else
{
result = default;
- errorMessage = $"{FieldIdentifier.FieldName} field isn't valid.";
+ errorMessage = $"{FieldId.FieldName} field isn't valid.";
return false;
}
@@ -1523,31 +2470,34 @@ The following `RadioButtonExample` component uses the preceding `InputRadio` com
Radio Button Example
-
+
+
+
@for (int i = 1; i <= 5; i++)
{
-
+
@i
}
Submit
+
-You chose: @model.Rating
+@Model?.Rating
@code {
- private Model model = new();
+ public Starship? Model { get; set; }
+
+ protected override void OnInitialized() => Model ??= new();
private void HandleValidSubmit()
{
Logger.LogInformation("HandleValidSubmit called");
-
- // Process the valid form
}
public class Model
@@ -1571,13 +2521,13 @@ The component sum
Output validation messages for a specific model with the `Model` parameter:
```razor
-
+
```
The component displays validation messages for a specific field, which is similar to the [Validation Message Tag Helper](xref:mvc/views/working-with-forms#the-validation-message-tag-helper). Specify the field for validation with the attribute and a lambda expression naming the model property:
```razor
-
+
```
The and components support arbitrary attributes. Any attribute that doesn't match a component parameter is added to the generated `` or `
` element.
@@ -1615,15 +2565,15 @@ public class CustomValidator : ValidationAttribute
Inject services into custom validation attributes through the . The following example demonstrates a salad chef form that validates user input with dependency injection (DI).
-The `SaladChef` class indicates the approved fruit ingredient list for a salad.
+The `SaladChef` class indicates the approved starship ingredient list for a Ten Forward salad.
`SaladChef.cs`:
```csharp
public class SaladChef
{
- public string[] ThingsYouCanPutInASalad = { "Strawberries", "Pineapple",
- "Honeydew", "Watermelon", "Grapes" };
+ public string[] ThingsYouCanPutInASalad = { "Horva", "Kanda Root",
+ "Krintar", "Plomeek", "Syto Bean" };
}
```
@@ -1637,8 +2587,6 @@ The `IsValid` method of the following `SaladChefValidatorAttribute` class obtain
`SaladChefValidatorAttribute.cs`:
-:::moniker range=">= aspnetcore-6.0"
-
```csharp
using System.ComponentModel.DataAnnotations;
@@ -1654,49 +2602,25 @@ public class SaladChefValidatorAttribute : ValidationAttribute
return ValidationResult.Success;
}
- return new ValidationResult("You should not put that in a salad!");
- }
-}
-```
-
-:::moniker-end
-
-:::moniker range="< aspnetcore-6.0"
-
-```csharp
-using System.ComponentModel.DataAnnotations;
-
-public class SaladChefValidatorAttribute : ValidationAttribute
-{
- protected override ValidationResult IsValid(object value,
- ValidationContext validationContext)
- {
- var saladChef = validationContext.GetRequiredService();
-
- if (saladChef.ThingsYouCanPutInASalad.Contains(value?.ToString()))
- {
- return ValidationResult.Success;
- }
-
- return new ValidationResult("You should not put that in a salad!");
+ return new ValidationResult("You should not put that in a salad! " +
+ "Only use an ingredient from this list: " +
+ string.Join(", ", saladChef.ThingsYouCanPutInASalad));
}
}
```
-:::moniker-end
-
-The following `ValidationWithDI` component validates user input by applying the `SaladChefValidatorAttribute` (`[SaladChefValidator]`) to the salad ingredient string (`SaladIngredient`).
+The following component validates user input by applying the `SaladChefValidatorAttribute` (`[SaladChefValidator]`) to the salad ingredient string (`SaladIngredient`).
-`Pages/ValidationWithDI.razor`:
+`Starship11.razor`:
-:::moniker range=">= aspnetcore-6.0"
+:::moniker range=">= aspnetcore-8.0"
```razor
-@page "/validation-with-di"
-@using System.ComponentModel.DataAnnotations
-@using Microsoft.AspNetCore.Components.Forms
+@page "/starship-11"
+@attribute [RenderModeServer]
+
@@ -1723,14 +2647,13 @@ The following `ValidationWithDI` component validates user input by applying the
:::moniker-end
-:::moniker range="< aspnetcore-6.0"
+:::moniker range="< aspnetcore-8.0"
```razor
-@page "/validation-with-di"
-@using System.ComponentModel.DataAnnotations
-@using Microsoft.AspNetCore.Components.Forms
+@page "/starship-11"
+
@@ -1751,7 +2674,7 @@ The following `ValidationWithDI` component validates user input by applying the
@code {
[SaladChefValidator]
- public string SaladIngredient { get; set; }
+ public string? SaladIngredient { get; set; }
}
```
@@ -1763,15 +2686,9 @@ The following `ValidationWithDI` component validates user input by applying the
Custom validation CSS class attributes are useful when integrating with CSS frameworks, such as [Bootstrap](https://getbootstrap.com/).
-The following example uses the `ExampleModel` class.
-
-`ExampleModel.cs`:
-
-:::code language="csharp" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/ExampleModel.cs":::
-
To specify custom validation CSS class attributes, start by providing CSS styles for custom validation. In the following example, valid (`validField`) and invalid (`invalidField`) styles are specified.
-`wwwroot/css/app.css` (Blazor WebAssembly) or `wwwroot/css/site.css` (Blazor Server):
+Add the following CSS classes to the app's stylesheet:
```css
.validField {
@@ -1787,239 +2704,188 @@ Create a class derived from instance with .
-
-`Pages/FormExample8.razor`:
-
-:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/forms-and-validation/FormExample8.razor" highlight="21":::
-
-The preceding example checks the validity of all form fields and applies a style to each field. If the form should only apply custom styles to a subset of the fields, make `CustomFieldClassProvider` apply styles conditionally. The following `CustomFieldClassProvider2` example only applies a style to the `Name` field. For any fields with names not matching `Name`, `string.Empty` is returned, and no style is applied. Using [reflection](/dotnet/csharp/advanced-topics/reflection-and-attributes/), the field is matched to the model member's property or field name, not an `id` assigned to the HTML entity.
-
-`CustomFieldClassProvider2.cs`:
-
-:::code language="csharp" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/CustomFieldClassProvider2.cs":::
-
-> [!NOTE]
-> Matching the field name in the preceding example is case sensitive, so a model property member designated "`Name`" must match a conditional check on "`Name`":
->
-> * ✔️ Correctly matches: `fieldIdentifier.FieldName == "Name"`
-> * ❌ Fails to match: `fieldIdentifier.FieldName == "name"`
-> * ❌ Fails to match: `fieldIdentifier.FieldName == "NAME"`
-> * ❌ Fails to match: `fieldIdentifier.FieldName == "nAmE"`
-
-Add an additional property to `ExampleModel`, for example:
-
```csharp
-[StringLength(10, ErrorMessage = "Description is too long.")]
-public string? Description { get; set; }
-```
-
-Add the `Description` to the `ExampleForm7` component's form:
+using Microsoft.AspNetCore.Components.Forms;
-```razor
-
-```
-
-Update the `EditContext` instance in the component's `OnInitialized` method to use the new Field CSS Class Provider:
+public class CustomFieldClassProvider : FieldCssClassProvider
+{
+ public override string GetFieldCssClass(EditContext editContext,
+ in FieldIdentifier fieldIdentifier)
+ {
+ var isValid = !editContext.GetValidationMessages(fieldIdentifier).Any();
-```csharp
-editContext?.SetFieldCssClassProvider(new CustomFieldClassProvider2());
+ return isValid ? "validField" : "invalidField";
+ }
+}
```
-Because a CSS validation class isn't applied to the `Description` field, it isn't styled. However, field validation runs normally. If more than 10 characters are provided, the validation summary indicates the error:
-
-> Description is too long.
-
-In the following example:
-
-* The custom CSS style is applied to the `Name` field.
-* Any other fields apply logic similar to Blazor's default logic and using Blazor's default field CSS validation styles, `modified` with `valid` or `invalid`. Note that for the default styles, you don't need to add them to the app's stylesheet if the app is based on a Blazor project template. For apps not based on a Blazor project template, the default styles can be added to the app's stylesheet:
-
- ```css
- .valid.modified:not([type=checkbox]) {
- outline: 1px solid #26b050;
- }
-
- .invalid {
- outline: 1px solid red;
- }
- ```
+
-`CustomFieldClassProvider3.cs`:
+Set the `CustomFieldClassProvider` class as the Field CSS Class Provider on the form's instance with .
-:::code language="csharp" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/CustomFieldClassProvider3.cs":::
+`Starship12.razor`:
-Update the `EditContext` instance in the component's `OnInitialized` method to use the preceding Field CSS Class Provider:
+:::moniker-end
-```csharp
-editContext.SetFieldCssClassProvider(new CustomFieldClassProvider3());
-```
+:::moniker range=">= aspnetcore-8.0"
-Using `CustomFieldClassProvider3`:
+```razor
+@page "/starship-12"
+@attribute [RenderModeServer]
+@inject ILogger Logger
-* The `Name` field uses the app's custom validation CSS styles.
-* The `Description` field uses logic similar to Blazor's logic and Blazor's default field CSS validation styles.
+
-:::moniker-end
+
-:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0"
+
-Custom validation CSS class attributes are useful when integrating with CSS frameworks, such as [Bootstrap](https://getbootstrap.com/).
+
-The following example uses the `ExampleModel` class.
+ Submit
-`ExampleModel.cs`:
+
-:::code language="csharp" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/ExampleModel.cs":::
+@code {
+ private EditContext? editContext;
-To specify custom validation CSS class attributes, start by providing CSS styles for custom validation. In the following example, valid (`validField`) and invalid (`invalidField`) styles are specified.
+ [SupplyParameterFromForm]
+ public Starship? Model { get; set; }
-`wwwroot/css/app.css` (Blazor WebAssembly) or `wwwroot/css/site.css` (Blazor Server):
+ protected override void OnInitialized()
+ {
+ Model ??= new();
+ editContext = new(Model);
+ editContext.SetFieldCssClassProvider(new CustomFieldClassProvider());
+ }
-```css
-.validField {
- border-color: lawngreen;
-}
+ private void Submit()
+ {
+ Logger.LogInformation("Submit called: Processing the form");
+ }
-.invalidField {
- background-color: tomato;
+ public class Starship
+ {
+ [Required]
+ [StringLength(10, ErrorMessage = "Id is too long.")]
+ public string? Id { get; set; }
+ }
}
```
-Create a class derived from that checks for field validation messages and applies the appropriate valid or invalid style.
-
-`CustomFieldClassProvider.cs`:
-
-:::code language="csharp" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/CustomFieldClassProvider.cs":::
-
-Set the `CustomFieldClassProvider` class as the Field CSS Class Provider on the form's instance with .
-
-`Pages/FormExample8.razor`:
-
-:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/forms-and-validation/FormExample8.razor" highlight="21":::
-
-The preceding example checks the validity of all form fields and applies a style to each field. If the form should only apply custom styles to a subset of the fields, make `CustomFieldClassProvider` apply styles conditionally. The following `CustomFieldClassProvider2` example only applies a style to the `Name` field. For any fields with names not matching `Name`, `string.Empty` is returned, and no style is applied.
-
-`CustomFieldClassProvider2.cs`:
-
-:::code language="csharp" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/CustomFieldClassProvider2.cs":::
-
-Add an additional property to `ExampleModel`, for example:
+
-```csharp
-[StringLength(10, ErrorMessage = "Description is too long.")]
-public string? Description { get; set; }
-```
+:::moniker-end
-Add the `Description` to the `ExampleForm7` component's form:
+:::moniker range="< aspnetcore-8.0"
```razor
-
-```
-
-Update the `EditContext` instance in the component's `OnInitialized` method to use the new Field CSS Class Provider:
-
-```csharp
-editContext?.SetFieldCssClassProvider(new CustomFieldClassProvider2());
-```
-
-Because a CSS validation class isn't applied to the `Description` field, it isn't styled. However, field validation runs normally. If more than 10 characters are provided, the validation summary indicates the error:
-
-> Description is too long.
-
-In the following example:
-
-* The custom CSS style is applied to the `Name` field.
-* Any other fields apply logic similar to Blazor's default logic and using Blazor's default field CSS validation styles, `modified` with `valid` or `invalid`. Note that for the default styles, you don't need to add them to the app's stylesheet if the app is based on a Blazor project template. For apps not based on a Blazor project template, the default styles can be added to the app's stylesheet:
-
- ```css
- .valid.modified:not([type=checkbox]) {
- outline: 1px solid #26b050;
- }
-
- .invalid {
- outline: 1px solid red;
- }
- ```
-
-`CustomFieldClassProvider3.cs`:
-
-:::code language="csharp" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/CustomFieldClassProvider3.cs":::
-
-Update the `EditContext` instance in the component's `OnInitialized` method to use the preceding Field CSS Class Provider:
-
-```csharp
-editContext.SetFieldCssClassProvider(new CustomFieldClassProvider3());
-```
+@page "/starship-12"
+@inject ILogger Logger
-Using `CustomFieldClassProvider3`:
-
-* The `Name` field uses the app's custom validation CSS styles.
-* The `Description` field uses logic similar to Blazor's logic and Blazor's default field CSS validation styles.
+
-:::moniker-end
+
-:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0"
+
-Custom validation CSS class attributes are useful when integrating with CSS frameworks, such as [Bootstrap](https://getbootstrap.com/).
+
-The following example uses the `ExampleModel` class.
+ Submit
-`ExampleModel.cs`:
+
-:::code language="csharp" source="~/../blazor-samples/5.0/BlazorSample_WebAssembly/ExampleModel.cs":::
+@code {
+ private EditContext? editContext;
-To specify custom validation CSS class attributes, start by providing CSS styles for custom validation. In the following example, valid (`validField`) and invalid (`invalidField`) styles are specified.
+ public Starship? Model { get; set; }
-`wwwroot/css/app.css` (Blazor WebAssembly) or `wwwroot/css/site.css` (Blazor Server):
+ protected override void OnInitialized()
+ {
+ Model ??= new();
+ editContext = new(Model);
+ editContext.SetFieldCssClassProvider(new CustomFieldClassProvider());
+ }
-```css
-.validField {
- border-color: lawngreen;
-}
+ private void Submit()
+ {
+ Logger.LogInformation("Submit called: Processing the form");
+ }
-.invalidField {
- background-color: tomato;
+ public class Starship
+ {
+ [Required]
+ [StringLength(10, ErrorMessage = "Id is too long.")]
+ public string? Id { get; set; }
+ }
}
```
-Create a class derived from that checks for field validation messages and applies the appropriate valid or invalid style.
+
-`CustomFieldClassProvider.cs`:
+:::moniker-end
-:::code language="csharp" source="~/../blazor-samples/5.0/BlazorSample_WebAssembly/CustomFieldClassProvider.cs" highlight="11":::
+:::moniker range=">= aspnetcore-7.0"
-Set the `CustomFieldClassProvider` class as the Field CSS Class Provider on the form's instance with .
+The preceding example checks the validity of all form fields and applies a style to each field. If the form should only apply custom styles to a subset of the fields, make `CustomFieldClassProvider` apply styles conditionally. The following `CustomFieldClassProvider2` example only applies a style to the `Name` field. For any fields with names not matching `Name`, `string.Empty` is returned, and no style is applied. Using [reflection](/dotnet/csharp/advanced-topics/reflection-and-attributes/), the field is matched to the model member's property or field name, not an `id` assigned to the HTML entity.
-`Pages/FormExample8.razor`:
+`CustomFieldClassProvider2.cs`:
-:::code language="razor" source="~/../blazor-samples/5.0/BlazorSample_WebAssembly/Pages/forms-and-validation/FormExample8.razor" highlight="21":::
+```csharp
+using Microsoft.AspNetCore.Components.Forms;
-The preceding example checks the validity of all form fields and applies a style to each field. If the form should only apply custom styles to a subset of the fields, make `CustomFieldClassProvider` apply styles conditionally. The following `CustomFieldClassProvider2` example only applies a style to the `Name` field. For any fields with names not matching `Name`, `string.Empty` is returned, and no style is applied.
+public class CustomFieldClassProvider2 : FieldCssClassProvider
+{
+ public override string GetFieldCssClass(EditContext editContext,
+ in FieldIdentifier fieldIdentifier)
+ {
+ if (fieldIdentifier.FieldName == "Name")
+ {
+ var isValid = !editContext.GetValidationMessages(fieldIdentifier).Any();
-`CustomFieldClassProvider2.cs`:
+ return isValid ? "validField" : "invalidField";
+ }
-:::code language="csharp" source="~/../blazor-samples/5.0/BlazorSample_WebAssembly/CustomFieldClassProvider2.cs" highlight="9,16":::
+ return string.Empty;
+ }
+}
+```
+
+
+
+> [!NOTE]
+> Matching the field name in the preceding example is case sensitive, so a model property member designated "`Name`" must match a conditional check on "`Name`":
+>
+> * ✔️ Correctly matches: `fieldId.FieldName == "Name"`
+> * ❌ Fails to match: `fieldId.FieldName == "name"`
+> * ❌ Fails to match: `fieldId.FieldName == "NAME"`
+> * ❌ Fails to match: `fieldId.FieldName == "nAmE"`
-Add an additional property to `ExampleModel`, for example:
+Add an additional property to `Model`, for example:
```csharp
[StringLength(10, ErrorMessage = "Description is too long.")]
-public string Description { get; set; }
+public string? Description { get; set; }
```
-Add the `Description` to the `ExampleForm7` component's form:
+Add the `Description` to the `CustomValidationForm` component's form:
```razor
-
+
```
Update the `EditContext` instance in the component's `OnInitialized` method to use the new Field CSS Class Provider:
```csharp
-editContext.SetFieldCssClassProvider(new CustomFieldClassProvider2());
+editContext?.SetFieldCssClassProvider(new CustomFieldClassProvider2());
```
Because a CSS validation class isn't applied to the `Description` field, it isn't styled. However, field validation runs normally. If more than 10 characters are provided, the validation summary indicates the error:
@@ -2043,7 +2909,38 @@ In the following example:
`CustomFieldClassProvider3.cs`:
-:::code language="csharp" source="~/../blazor-samples/5.0/BlazorSample_WebAssembly/CustomFieldClassProvider3.cs" highlight="17-24":::
+```csharp
+using Microsoft.AspNetCore.Components.Forms;
+
+public class CustomFieldClassProvider3 : FieldCssClassProvider
+{
+ public override string GetFieldCssClass(EditContext editContext,
+ in FieldIdentifier fieldIdentifier)
+ {
+ var isValid = !editContext.GetValidationMessages(fieldIdentifier).Any();
+
+ if (fieldIdentifier.FieldName == "Name")
+ {
+ return isValid ? "validField" : "invalidField";
+ }
+ else
+ {
+ if (editContext.IsModified(fieldIdentifier))
+ {
+ return isValid ? "modified valid" : "modified invalid";
+ }
+ else
+ {
+ return isValid ? "valid" : "invalid";
+ }
+ }
+ }
+}
+```
+
+
Update the `EditContext` instance in the component's `OnInitialized` method to use the preceding Field CSS Class Provider:
@@ -2080,7 +2977,7 @@ Blazor provides support for validating form input using data annotations with th
To validate the bound model's entire object graph, including collection- and complex-type properties, use the `ObjectGraphDataAnnotationsValidator` provided by the *experimental* [`Microsoft.AspNetCore.Components.DataAnnotations.Validation`](https://www.nuget.org/packages/Microsoft.AspNetCore.Components.DataAnnotations.Validation) package:
```razor
-
+
...
@@ -2130,8 +3027,6 @@ public class Starship
`ShipDescription.cs`:
-:::moniker range=">= aspnetcore-6.0"
-
```csharp
using System;
using System.ComponentModel.DataAnnotations;
@@ -2148,33 +3043,11 @@ public class ShipDescription
}
```
-:::moniker-end
-
-:::moniker range="< aspnetcore-6.0"
-
-```csharp
-using System;
-using System.ComponentModel.DataAnnotations;
-
-public class ShipDescription
-{
- [Required]
- [StringLength(40, ErrorMessage = "Description too long (40 char).")]
- public string ShortDescription { get; set; }
-
- [Required]
- [StringLength(240, ErrorMessage = "Description too long (240 char).")]
- public string LongDescription { get; set; }
-}
-```
-
-:::moniker-end
-
## Enable the submit button based on form validation
To enable and disable the submit button based on form validation, the following example:
-* Uses a shortened version of the preceding `Starfleet Starship Database` form (`FormExample2` component) that only accepts a value for the ship's identifier. The other `Starship` properties receive valid default values when an instance of the `Starship` type is created.
+* Uses a shortened version of the preceding `Starfleet Starship Database` form (`Starship4` component) from the [Example form](#example-form) section that only accepts a value for the ship's Id. The other `Starship` properties receive valid default values when an instance of the `Starship` type is created.
* Uses the form's to assign the model when the component is initialized.
* Validates the form in the context's callback to enable and disable the submit button.
* Implements and unsubscribes the event handler in the `Dispose` method. For more information, see .
@@ -2182,29 +3055,157 @@ To enable and disable the submit button based on form validation, the following
> [!NOTE]
> When assigning to the , don't also assign an to the .
-`Pages/FormExample9.razor`:
+`Starship13.razor`:
-:::moniker range=">= aspnetcore-7.0"
+:::moniker range=">= aspnetcore-8.0"
-:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/forms-and-validation/FormExample9.razor":::
+```razor
+@page "/starship-13"
+@attribute [RenderModeServer]
+@implements IDisposable
+@inject ILogger Logger
-:::moniker-end
+
-:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0"
+
-:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/forms-and-validation/FormExample9.razor":::
+
-:::moniker-end
+
+
+ Id:
+
+
+
-:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0"
+ Submit
+
+
+
+@code {
+ private bool formInvalid = false;
+ private EditContext? editContext;
+
+ [SupplyParameterFromForm]
+ private Starship? Model { get; set; }
+
+ protected override void OnInitialized()
+ {
+ Model ??=
+ new()
+ {
+ Id = "NCC-1701",
+ Classification = "Exploration",
+ MaximumAccommodation = 150,
+ IsValidatedDesign = true,
+ ProductionDate = new DateTime(2245, 4, 11)
+ };
+ editContext = new(Model);
+ editContext.OnFieldChanged += HandleFieldChanged;
+ }
+
+ private void HandleFieldChanged(object? sender, FieldChangedEventArgs e)
+ {
+ if (editContext is not null)
+ {
+ formInvalid = !editContext.Validate();
+ StateHasChanged();
+ }
+ }
+
+ private void Submit()
+ {
+ Logger.LogInformation("Submit called: Processing the form");
+ }
+
+ public void Dispose()
+ {
+ if (editContext is not null)
+ {
+ editContext.OnFieldChanged -= HandleFieldChanged;
+ }
+ }
+}
+```
-:::code language="razor" source="~/../blazor-samples/5.0/BlazorSample_WebAssembly/Pages/forms-and-validation/FormExample9.razor":::
+
:::moniker-end
-:::moniker range="< aspnetcore-5.0"
+:::moniker range="< aspnetcore-8.0"
+
+```razor
+@page "/starship-13"
+@implements IDisposable
+@inject ILogger Logger
+
+
+
+
+
+
+
+
+
+ Id:
+
+
+
-:::code language="razor" source="~/../blazor-samples/3.1/BlazorSample_WebAssembly/Pages/forms-and-validation/FormExample9.razor":::
+ Submit
+
+
+
+@code {
+ private bool formInvalid = false;
+ private EditContext? editContext;
+
+ private Starship? Model { get; set; }
+
+ protected override void OnInitialized()
+ {
+ Model ??=
+ new()
+ {
+ Id = "NCC-1701",
+ Classification = "Exploration",
+ MaximumAccommodation = 150,
+ IsValidatedDesign = true,
+ ProductionDate = new DateTime(2245, 4, 11)
+ };
+ editContext = new(Model);
+ editContext.OnFieldChanged += HandleFieldChanged;
+ }
+
+ private void HandleFieldChanged(object? sender, FieldChangedEventArgs e)
+ {
+ if (editContext is not null)
+ {
+ formInvalid = !editContext.Validate();
+ StateHasChanged();
+ }
+ }
+
+ private void Submit()
+ {
+ Logger.LogInformation("Submit called: Processing the form");
+ }
+
+ public void Dispose()
+ {
+ if (editContext is not null)
+ {
+ editContext.OnFieldChanged -= HandleFieldChanged;
+ }
+ }
+}
+```
+
+
:::moniker-end
@@ -2213,16 +3214,19 @@ If a form isn't preloaded with valid values and you wish to disable the **`Submi
A side effect of the preceding approach is that a validation summary ( component) is populated with invalid fields after the user interacts with any one field. Address this scenario in either of the following ways:
* Don't use a component on the form.
-* Make the component visible when the submit button is selected (for example, in a `HandleValidSubmit` method).
+* Make the component visible when the submit button is selected (for example, in a `Submit` method).
```razor
-
+
+
+
...
Submit
+
@code {
@@ -2230,7 +3234,7 @@ A side effect of the preceding approach is that a validation summary (`) is used with streaming JS in
Add a JavaScript (JS) `getText` function to the app:
+:::moniker range=">= aspnetcore-8.0"
+
+
+
+> [!NOTE]
+> During the .NET 8 preview, add `suppress-error="BL9992"` to `