From f86d0c71e20cf08cc6b887cc36467d6deb42cf1a Mon Sep 17 00:00:00 2001 From: Luke Latham <1622880+guardrex@users.noreply.github.com> Date: Thu, 1 Jun 2023 14:03:43 -0400 Subject: [PATCH] Forms/input components article NRT updates (#29404) --- aspnetcore/blazor/call-web-api.md | 45 +++- .../blazor/forms-and-input-components.md | 232 +++++++++++++++++- 2 files changed, 270 insertions(+), 7 deletions(-) diff --git a/aspnetcore/blazor/call-web-api.md b/aspnetcore/blazor/call-web-api.md index 235d0dd75b..38500801c4 100644 --- a/aspnetcore/blazor/call-web-api.md +++ b/aspnetcore/blazor/call-web-api.md @@ -150,12 +150,27 @@ In the following component code, `newItemName` is provided by a bound element of } ``` - returns an . To deserialize the JSON content from the response message, use the extension method. The following example reads JSON weather data: + returns an . To deserialize the JSON content from the response message, use the extension method. The following example reads JSON weather data as an array: + +:::moniker range=">= aspnetcore-6.0" ```csharp -var content = await response.Content.ReadFromJsonAsync(); +var content = await response.Content.ReadFromJsonAsync() ?? + Array.Empty(); ``` +In the preceding example, an empty array is created if no weather data is returned by the method, so `content` isn't null after the statement executes. + +:::moniker-end + +:::moniker range="< aspnetcore-6.0" + +```csharp +var content = await response.Content.ReadFromJsonAsync(); +``` + +:::moniker-end + ### PUT as JSON (`PutAsJsonAsync`) sends an HTTP PUT request with JSON-encoded content. @@ -186,12 +201,27 @@ In the following component code, `editItem` values for `Name` and `IsCompleted` } ``` - returns an . To deserialize the JSON content from the response message, use the extension method. The following example reads JSON weather data: + returns an . To deserialize the JSON content from the response message, use the extension method. The following example reads JSON weather data as an array: + +:::moniker range=">= aspnetcore-6.0" ```csharp -var content = await response.Content.ReadFromJsonAsync(); +var content = await response.Content.ReadFromJsonAsync() ?? + Array.Empty(); ``` +In the preceding example, an empty array is created if no weather data is returned by the method, so `content` isn't null after the statement executes. + +:::moniker-end + +:::moniker range="< aspnetcore-6.0" + +```csharp +var content = await response.Content.ReadFromJsonAsync(); +``` + +:::moniker-end + :::moniker range=">= aspnetcore-7.0" ### PATCH as JSON (`PatchAsJsonAsync`) @@ -234,12 +264,15 @@ The following example doesn't show loading `incompleteTodoItems` for brevity. Se } ``` - returns an . To deserialize the JSON content from the response message, use the extension method. The following example reads JSON weather data: + returns an . To deserialize the JSON content from the response message, use the extension method. The following example reads JSON weather data as an array: ```csharp -var content = await response.Content.ReadFromJsonAsync(); +var content = await response.Content.ReadFromJsonAsync() ?? + Array.Empty(); ``` +In the preceding example, an empty array is created if no weather data is returned by the method, so `content` isn't null after the statement executes. + :::moniker-end ### Additional extension methods diff --git a/aspnetcore/blazor/forms-and-input-components.md b/aspnetcore/blazor/forms-and-input-components.md index e6da5e4df7..f697847a5c 100644 --- a/aspnetcore/blazor/forms-and-input-components.md +++ b/aspnetcore/blazor/forms-and-input-components.md @@ -973,6 +973,8 @@ In the following `FormExample6` component, update the namespace of the **`Shared `Pages/FormExample6.razor`: +:::moniker range=">= aspnetcore-6.0" + ```razor @page "/form-example-6" @using System.Net @@ -1069,7 +1071,8 @@ 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()) @@ -1103,6 +1106,142 @@ In the following `FormExample6` component, update the namespace of the **`Shared } ``` +:::moniker-end + +:::moniker range="< aspnetcore-6.0" + +```razor +@page "/form-example-6" +@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 + +

Starfleet Starship Database

+ +

New Ship Entry Form

+ + + + + + +

+ +

+

+ +

+

+ +

+

+ +

+

+ +

+

+ +

+ + + +

+ @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 async Task HandleValidSubmit(EditContext editContext) + { + customValidation.ClearErrors(); + + try + { + var response = await Http.PostAsJsonAsync( + "StarshipValidation", (Starship)editContext.Model); + + var errors = await response.Content + .ReadFromJsonAsync>>(); + + if (response.StatusCode == HttpStatusCode.BadRequest && + errors.Any()) + { + customValidation.DisplayErrors(errors); + } + else if (!response.IsSuccessStatusCode) + { + throw new HttpRequestException( + $"Validation failed. Status Code: {response.StatusCode}"); + } + else + { + disabled = true; + messageStyles = "color:green"; + message = "The form has been processed."; + } + } + catch (AccessTokenNotAvailableException ex) + { + ex.Redirect(); + } + catch (Exception ex) + { + Logger.LogError("Form processing error: {Message}", ex.Message); + disabled = true; + messageStyles = "color:red"; + message = "There was an error processing the form."; + } + } +} +``` + +:::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-side validation, the attributes must be executable on the server. For more information, see . @@ -1225,6 +1364,10 @@ Make the `enums` accessible to the: 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: +:::moniker-end + +:::moniker range=">= aspnetcore-6.0" + ```csharp [Required] [Range(typeof(Manufacturer), nameof(Manufacturer.SpaceX), @@ -1238,6 +1381,27 @@ public Color? Color { get; set; } = null; public Engine? Engine { get; set; } = null; ``` +:::moniker-end + +:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0" + +```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; +``` + +:::moniker-end + +:::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: * A radio button group for the ship manufacturer. @@ -1473,6 +1637,8 @@ The `IsValid` method of the following `SaladChefValidatorAttribute` class obtain `SaladChefValidatorAttribute.cs`: +:::moniker range=">= aspnetcore-6.0" + ```csharp using System.ComponentModel.DataAnnotations; @@ -1493,10 +1659,38 @@ public class SaladChefValidatorAttribute : ValidationAttribute } ``` +:::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!"); + } +} +``` + +:::moniker-end + The following `ValidationWithDI` component validates user input by applying the `SaladChefValidatorAttribute` (`[SaladChefValidator]`) to the salad ingredient string (`SaladIngredient`). `Pages/ValidationWithDI.razor`: +:::moniker range=">= aspnetcore-6.0" + ```razor @page "/validation-with-di" @using System.ComponentModel.DataAnnotations @@ -1527,6 +1721,42 @@ The following `ValidationWithDI` component validates user input by applying the } ``` +:::moniker-end + +:::moniker range="< aspnetcore-6.0" + +```razor +@page "/validation-with-di" +@using System.ComponentModel.DataAnnotations +@using Microsoft.AspNetCore.Components.Forms + + + + +

+ Name something you can put in a salad: + +

+ + + +
    + @foreach (var message in context.GetValidationMessages()) + { +
  • @message
  • + } +
+ +
+ +@code { + [SaladChefValidator] + public string SaladIngredient { get; set; } +} +``` + +:::moniker-end + ## Custom validation CSS class attributes :::moniker range=">= aspnetcore-7.0"