From f61aecbe05a00d3d1bfe8146f7882344a9c97e5f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 22 May 2025 15:56:12 +0000 Subject: [PATCH 01/18] Initial plan for issue From 194ea8d6aa18071e275a6718fff0c3c44d9084d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Rozto=C4=8Dil?= Date: Sat, 17 May 2025 00:25:57 +0200 Subject: [PATCH 02/18] Use the unified validation instractructure for EditForm validation --- .../EditContextDataAnnotationsExtensions.cs | 71 +++++++++++++++++++ ...crosoft.AspNetCore.Components.Forms.csproj | 1 + 2 files changed, 72 insertions(+) diff --git a/src/Components/Forms/src/EditContextDataAnnotationsExtensions.cs b/src/Components/Forms/src/EditContextDataAnnotationsExtensions.cs index e15b90c0e42d..46aa41ddff24 100644 --- a/src/Components/Forms/src/EditContextDataAnnotationsExtensions.cs +++ b/src/Components/Forms/src/EditContextDataAnnotationsExtensions.cs @@ -7,6 +7,9 @@ using System.Reflection; using System.Reflection.Metadata; using System.Runtime.InteropServices; +using Microsoft.AspNetCore.Http.Validation; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; [assembly: MetadataUpdateHandler(typeof(Microsoft.AspNetCore.Components.Forms.EditContextDataAnnotationsExtensions))] @@ -112,6 +115,13 @@ private void OnFieldChanged(object? sender, FieldChangedEventArgs eventArgs) private void OnValidationRequested(object? sender, ValidationRequestedEventArgs e) { var validationContext = new ValidationContext(_editContext.Model, _serviceProvider, items: null); + + if (TryValidateTypeInfo(validationContext)) + { + _editContext.NotifyValidationStateChanged(); + return; + } + var validationResults = new List(); Validator.TryValidateObject(_editContext.Model, validationContext, validationResults, true); @@ -140,6 +150,52 @@ private void OnValidationRequested(object? sender, ValidationRequestedEventArgs _editContext.NotifyValidationStateChanged(); } +#pragma warning disable ASP0029 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. + private bool TryValidateTypeInfo(ValidationContext validationContext) + { + var options = _serviceProvider?.GetService>()?.Value; + + if (options == null || !options.TryGetValidatableTypeInfo(_editContext.Model.GetType(), out var typeInfo)) + { + return false; + } + + var validateContext = new ValidateContext + { + ValidationOptions = options, + ValidationContext = validationContext, + }; + + var validationTask = typeInfo.ValidateAsync(_editContext.Model, validateContext, CancellationToken.None); + + if (!validationTask.IsCompleted) + { + throw new InvalidOperationException("Async validation is not supported"); + } + + var validationErrors = validateContext.ValidationErrors; + + // Transfer results to the ValidationMessageStore + _messages.Clear(); + + if (validationErrors is not null && validationErrors.Count > 0) + { + foreach (var (key, value) in validationErrors) + { + var keySegments = key.Split('.'); + var container = keySegments.Length > 1 + ? GetPropertyByPath(_editContext.Model, keySegments[..^1]) + : _editContext.Model; + var fieldName = keySegments[^1]; + + _messages.Add(new FieldIdentifier(container, fieldName), value); + } + } + + return true; + } +#pragma warning restore ASP0029 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. + public void Dispose() { _messages.Clear(); @@ -174,5 +230,20 @@ internal void ClearCache() { _propertyInfoCache.Clear(); } + + // TODO(OR): Replace this with more robust implementation. + private static object GetPropertyByPath(object obj, string[] path) + { + var currentObject = obj; + + foreach (string propertyName in path) + { + Type currentType = currentObject!.GetType(); + PropertyInfo propertyInfo = currentType.GetProperty(propertyName)!; + currentObject = propertyInfo.GetValue(currentObject); + } + + return currentObject!; + } } } diff --git a/src/Components/Forms/src/Microsoft.AspNetCore.Components.Forms.csproj b/src/Components/Forms/src/Microsoft.AspNetCore.Components.Forms.csproj index 654c4ac2d3fe..45643f54d35e 100644 --- a/src/Components/Forms/src/Microsoft.AspNetCore.Components.Forms.csproj +++ b/src/Components/Forms/src/Microsoft.AspNetCore.Components.Forms.csproj @@ -11,6 +11,7 @@ + From cc518808846457a55b87b12061cbb9fb4494fdde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Rozto=C4=8Dil?= Date: Sat, 17 May 2025 00:26:44 +0200 Subject: [PATCH 03/18] Add EditForm validation sample --- .../BlazorUnitedApp/BlazorUnitedApp.csproj | 9 +++ .../Samples/BlazorUnitedApp/Pages/Index.razor | 37 +--------- .../BlazorUnitedApp/Pages/ValidatedForm.razor | 70 +++++++++++++++++++ .../Samples/BlazorUnitedApp/Program.cs | 2 + .../BlazorUnitedApp/Shared/NavMenu.razor | 5 ++ .../BlazorUnitedApp/Validation/Types.cs | 41 +++++++++++ 6 files changed, 128 insertions(+), 36 deletions(-) create mode 100644 src/Components/Samples/BlazorUnitedApp/Pages/ValidatedForm.razor create mode 100644 src/Components/Samples/BlazorUnitedApp/Validation/Types.cs diff --git a/src/Components/Samples/BlazorUnitedApp/BlazorUnitedApp.csproj b/src/Components/Samples/BlazorUnitedApp/BlazorUnitedApp.csproj index 62460261002b..cf687ec7ba1c 100644 --- a/src/Components/Samples/BlazorUnitedApp/BlazorUnitedApp.csproj +++ b/src/Components/Samples/BlazorUnitedApp/BlazorUnitedApp.csproj @@ -4,12 +4,21 @@ $(DefaultNetCoreTargetFramework) false enable + + $(InterceptorsNamespaces);Microsoft.AspNetCore.Http.Validation.Generated + true + + + + diff --git a/src/Components/Samples/BlazorUnitedApp/Pages/Index.razor b/src/Components/Samples/BlazorUnitedApp/Pages/Index.razor index 6604c511e2ba..caa747cb9240 100644 --- a/src/Components/Samples/BlazorUnitedApp/Pages/Index.razor +++ b/src/Components/Samples/BlazorUnitedApp/Pages/Index.razor @@ -2,39 +2,4 @@ @using BlazorUnitedApp.Data; Index - -
- -
- - -
- -@if (_submitted) -{ - -

Customer

-

Name: @Value!.Name

-

Street: @Value.BillingAddress.Street

-

City: @Value.BillingAddress.City

-

State: @Value.BillingAddress.State

-

Zip: @Value.BillingAddress.Zip

-} - -@code { - - public void DisplayCustomer() - { - _submitted = true; - } - - [SupplyParameterFromForm] Customer? Value { get; set; } - - protected override void OnInitialized() => Value ??= new(); - - bool _submitted = false; - public void Submit() => _submitted = true; -} +

Welcome to Blazor

diff --git a/src/Components/Samples/BlazorUnitedApp/Pages/ValidatedForm.razor b/src/Components/Samples/BlazorUnitedApp/Pages/ValidatedForm.razor new file mode 100644 index 000000000000..c97f5597672f --- /dev/null +++ b/src/Components/Samples/BlazorUnitedApp/Pages/ValidatedForm.razor @@ -0,0 +1,70 @@ +@page "/validated-form" +@rendermode InteractiveServer + +@using System.ComponentModel.DataAnnotations +@using System.Text.Json +@using BlazorUnitedApp.Validation + +Validated Form + +

Validated Form

+ + +
@StatusMessage
+ + + + +
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+ + +
+ +@code +{ + private string? StatusMessage; + private string? StatusClass; + + private GuestbookEntry Model = new GuestbookEntry(); + + protected void HandleValidSubmit() + { + StatusClass = "alert-info"; + StatusMessage = DateTime.Now + " Handle valid submit"; + } + + protected void HandleInvalidSubmit() + { + StatusClass = "alert-danger"; + StatusMessage = DateTime.Now + " Handle invalid submit"; + } +} diff --git a/src/Components/Samples/BlazorUnitedApp/Program.cs b/src/Components/Samples/BlazorUnitedApp/Program.cs index 6492f3fb3e50..9fb1293a52ab 100644 --- a/src/Components/Samples/BlazorUnitedApp/Program.cs +++ b/src/Components/Samples/BlazorUnitedApp/Program.cs @@ -13,6 +13,8 @@ builder.Services.AddSingleton(); +builder.Services.AddValidation(); + var app = builder.Build(); // Configure the HTTP request pipeline. diff --git a/src/Components/Samples/BlazorUnitedApp/Shared/NavMenu.razor b/src/Components/Samples/BlazorUnitedApp/Shared/NavMenu.razor index 3801dfc18932..99a708e11b31 100644 --- a/src/Components/Samples/BlazorUnitedApp/Shared/NavMenu.razor +++ b/src/Components/Samples/BlazorUnitedApp/Shared/NavMenu.razor @@ -14,6 +14,11 @@ Home +