From de650e1a5eeffb9e894253cded4548d10a065309 Mon Sep 17 00:00:00 2001 From: Mario van Zeist Date: Tue, 11 Jan 2022 23:43:33 +0100 Subject: [PATCH 01/11] Add IServiceProvider to ValidationContext --- .../Forms/src/DataAnnotationsValidator.cs | 3 ++- .../EditContextDataAnnotationsExtensions.cs | 21 +++++++++++++++---- .../Forms/src/PublicAPI.Unshipped.txt | 1 + 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/Components/Forms/src/DataAnnotationsValidator.cs b/src/Components/Forms/src/DataAnnotationsValidator.cs index 0b52530d2271..3d372c8e1c71 100644 --- a/src/Components/Forms/src/DataAnnotationsValidator.cs +++ b/src/Components/Forms/src/DataAnnotationsValidator.cs @@ -12,6 +12,7 @@ public class DataAnnotationsValidator : ComponentBase, IDisposable private EditContext? _originalEditContext; [CascadingParameter] EditContext? CurrentEditContext { get; set; } + [Inject] IServiceProvider? ServiceProvider { get; set; } /// protected override void OnInitialized() @@ -23,7 +24,7 @@ protected override void OnInitialized() $"inside an EditForm."); } - _subscriptions = CurrentEditContext.EnableDataAnnotationsValidation(); + _subscriptions = CurrentEditContext.EnableDataAnnotationsValidation(ServiceProvider); _originalEditContext = CurrentEditContext; } diff --git a/src/Components/Forms/src/EditContextDataAnnotationsExtensions.cs b/src/Components/Forms/src/EditContextDataAnnotationsExtensions.cs index 9574a555f008..487798c84b03 100644 --- a/src/Components/Forms/src/EditContextDataAnnotationsExtensions.cs +++ b/src/Components/Forms/src/EditContextDataAnnotationsExtensions.cs @@ -35,8 +35,19 @@ public static EditContext AddDataAnnotationsValidation(this EditContext editCont /// A disposable object whose disposal will remove DataAnnotations validation support from the . public static IDisposable EnableDataAnnotationsValidation(this EditContext editContext) { - return new DataAnnotationsEventSubscriptions(editContext); + return new DataAnnotationsEventSubscriptions(editContext, null); } + /// + /// Enables DataAnnotations validation support for the . + /// + /// The . + /// The to be used in the . + /// A disposable object whose disposal will remove DataAnnotations validation support from the . + public static IDisposable EnableDataAnnotationsValidation(this EditContext editContext, IServiceProvider? serviceProvider) + { + return new DataAnnotationsEventSubscriptions(editContext, serviceProvider); + } + private static event Action? OnClearCache; @@ -50,11 +61,13 @@ private sealed class DataAnnotationsEventSubscriptions : IDisposable private static readonly ConcurrentDictionary<(Type ModelType, string FieldName), PropertyInfo?> _propertyInfoCache = new(); private readonly EditContext _editContext; + private readonly IServiceProvider? _serviceProvider; private readonly ValidationMessageStore _messages; - public DataAnnotationsEventSubscriptions(EditContext editContext) + public DataAnnotationsEventSubscriptions(EditContext editContext, IServiceProvider? serviceProvider) { _editContext = editContext ?? throw new ArgumentNullException(nameof(editContext)); + _serviceProvider = serviceProvider; _messages = new ValidationMessageStore(_editContext); _editContext.OnFieldChanged += OnFieldChanged; @@ -72,7 +85,7 @@ private void OnFieldChanged(object? sender, FieldChangedEventArgs eventArgs) if (TryGetValidatableProperty(fieldIdentifier, out var propertyInfo)) { var propertyValue = propertyInfo.GetValue(fieldIdentifier.Model); - var validationContext = new ValidationContext(fieldIdentifier.Model) + var validationContext = new ValidationContext(fieldIdentifier.Model, _serviceProvider, null) { MemberName = propertyInfo.Name }; @@ -93,7 +106,7 @@ private void OnFieldChanged(object? sender, FieldChangedEventArgs eventArgs) private void OnValidationRequested(object? sender, ValidationRequestedEventArgs e) { - var validationContext = new ValidationContext(_editContext.Model); + var validationContext = new ValidationContext(_editContext.Model, _serviceProvider, null); var validationResults = new List(); Validator.TryValidateObject(_editContext.Model, validationContext, validationResults, true); diff --git a/src/Components/Forms/src/PublicAPI.Unshipped.txt b/src/Components/Forms/src/PublicAPI.Unshipped.txt index 7dc5c58110bf..e71f0c67608b 100644 --- a/src/Components/Forms/src/PublicAPI.Unshipped.txt +++ b/src/Components/Forms/src/PublicAPI.Unshipped.txt @@ -1 +1,2 @@ #nullable enable +static Microsoft.AspNetCore.Components.Forms.EditContextDataAnnotationsExtensions.EnableDataAnnotationsValidation(this Microsoft.AspNetCore.Components.Forms.EditContext! editContext, System.IServiceProvider? serviceProvider) -> System.IDisposable! From 737bff1c12dfde779a1f9279352f5da0722d0604 Mon Sep 17 00:00:00 2001 From: Mario van Zeist Date: Tue, 11 Jan 2022 23:43:57 +0100 Subject: [PATCH 02/11] Add e2e test --- .../test/E2ETest/Tests/FormsTest.cs | 20 ++++++++ .../FormsTest/ValidationComponentDI.razor | 46 +++++++++++++++++++ .../test/testassets/BasicTestApp/Index.razor | 1 + .../test/testassets/BasicTestApp/Program.cs | 1 + 4 files changed, 68 insertions(+) create mode 100644 src/Components/test/testassets/BasicTestApp/FormsTest/ValidationComponentDI.razor diff --git a/src/Components/test/E2ETest/Tests/FormsTest.cs b/src/Components/test/E2ETest/Tests/FormsTest.cs index cae4ba273915..4565e877fb97 100644 --- a/src/Components/test/E2ETest/Tests/FormsTest.cs +++ b/src/Components/test/E2ETest/Tests/FormsTest.cs @@ -74,6 +74,26 @@ public async Task EditFormWorksWithDataAnnotationsValidator() Browser.Equal("OnValidSubmit", () => appElement.FindElement(By.Id("last-callback")).Text); } + [Fact] + public void EditFormWorksWithDataAnnotationsValidatorAndDI() + { + var appElement = Browser.MountTestComponent(); + var form = appElement.FindElement(By.TagName("form")); + var userNameInput = appElement.FindElement(By.ClassName("the-quiz")).FindElement(By.TagName("input")); + var submitButton = appElement.FindElement(By.CssSelector("button[type=submit]")); + var messagesAccessor = CreateValidationMessagesAccessor(appElement); + + userNameInput.SendKeys("Jaws\t"); + submitButton.Click(); + //We can only have this errormessage when DI is working + Browser.Equal(new[] { "Steven Spielberg did not star in this movie" }, messagesAccessor); + + userNameInput.Clear(); + userNameInput.SendKeys("Gremlins\t"); + submitButton.Click(); + Browser.Empty(messagesAccessor); + } + [Fact] public void InputTextInteractsWithEditContext() { diff --git a/src/Components/test/testassets/BasicTestApp/FormsTest/ValidationComponentDI.razor b/src/Components/test/testassets/BasicTestApp/FormsTest/ValidationComponentDI.razor new file mode 100644 index 000000000000..184e0917ec95 --- /dev/null +++ b/src/Components/test/testassets/BasicTestApp/FormsTest/ValidationComponentDI.razor @@ -0,0 +1,46 @@ +@using System.ComponentModel.DataAnnotations +@using Microsoft.AspNetCore.Components.Forms + + + + +

+ Name a movie in which Steven Spielberg acted: + +

+ + +
    + @foreach (var message in context.GetValidationMessages()) + { +
  • @message
  • + } +
+ +
+ +@code { + [MovieQuizValidator] + public string MovieName { get; set; } + + public class MovieQuizValidatorAttribute : ValidationAttribute + { + protected override ValidationResult IsValid(object value, ValidationContext validationContext) + { + var quizHost = validationContext.GetService(); + if (quizHost == null) return new ValidationResult("Can't find moviehost"); + if (quizHost.MoviesWhereStevenSpielbergActed.Contains(value.ToString())) + { + return ValidationResult.Success; + } + return new ValidationResult("Steven Spielberg did not star in this movie"); + } + } + + + //Simple class to check if DI can be used in Validation attributes + public class MovieQuizHost + { + public string[] MoviesWhereStevenSpielbergActed = { "The Blues Brothers", "Gremlins", "Vanilla Sky", "Double Dare", "Goldmember" }; + } +} diff --git a/src/Components/test/testassets/BasicTestApp/Index.razor b/src/Components/test/testassets/BasicTestApp/Index.razor index 78aef6d94e11..db9fb10c7647 100644 --- a/src/Components/test/testassets/BasicTestApp/Index.razor +++ b/src/Components/test/testassets/BasicTestApp/Index.razor @@ -40,6 +40,7 @@ + diff --git a/src/Components/test/testassets/BasicTestApp/Program.cs b/src/Components/test/testassets/BasicTestApp/Program.cs index ad3424dabff4..20ff8a69852a 100644 --- a/src/Components/test/testassets/BasicTestApp/Program.cs +++ b/src/Components/test/testassets/BasicTestApp/Program.cs @@ -38,6 +38,7 @@ public static async Task Main(string[] args) }); builder.Services.AddScoped(); + builder.Services.AddTransient(); builder.Logging.AddConfiguration(builder.Configuration.GetSection("Logging")); From 4e8357d5bab74a1a44dfe8a73e8001d4b598c4b8 Mon Sep 17 00:00:00 2001 From: Mario van Zeist Date: Wed, 12 Jan 2022 00:40:11 +0100 Subject: [PATCH 03/11] Salad chef to the rescue --- .../test/E2ETest/Tests/FormsTest.cs | 6 ++--- .../FormsTest/ValidationComponentDI.razor | 22 +++++++++---------- .../test/testassets/BasicTestApp/Program.cs | 2 +- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/Components/test/E2ETest/Tests/FormsTest.cs b/src/Components/test/E2ETest/Tests/FormsTest.cs index 4565e877fb97..757198693e6a 100644 --- a/src/Components/test/E2ETest/Tests/FormsTest.cs +++ b/src/Components/test/E2ETest/Tests/FormsTest.cs @@ -83,13 +83,13 @@ public void EditFormWorksWithDataAnnotationsValidatorAndDI() var submitButton = appElement.FindElement(By.CssSelector("button[type=submit]")); var messagesAccessor = CreateValidationMessagesAccessor(appElement); - userNameInput.SendKeys("Jaws\t"); + userNameInput.SendKeys("Bacon\t"); submitButton.Click(); //We can only have this errormessage when DI is working - Browser.Equal(new[] { "Steven Spielberg did not star in this movie" }, messagesAccessor); + Browser.Equal(new[] { "You should not put that in a salad!" }, messagesAccessor); userNameInput.Clear(); - userNameInput.SendKeys("Gremlins\t"); + userNameInput.SendKeys("Watermelon\t"); submitButton.Click(); Browser.Empty(messagesAccessor); } diff --git a/src/Components/test/testassets/BasicTestApp/FormsTest/ValidationComponentDI.razor b/src/Components/test/testassets/BasicTestApp/FormsTest/ValidationComponentDI.razor index 184e0917ec95..f330953e552e 100644 --- a/src/Components/test/testassets/BasicTestApp/FormsTest/ValidationComponentDI.razor +++ b/src/Components/test/testassets/BasicTestApp/FormsTest/ValidationComponentDI.razor @@ -5,8 +5,8 @@

- Name a movie in which Steven Spielberg acted: - + Name something you can put in a salad: +

@@ -20,27 +20,27 @@ @code { - [MovieQuizValidator] - public string MovieName { get; set; } + [SaladChefValidator] + public string SaladIngredient { get; set; } - public class MovieQuizValidatorAttribute : ValidationAttribute + public class SaladChefValidatorAttribute : ValidationAttribute { protected override ValidationResult IsValid(object value, ValidationContext validationContext) { - var quizHost = validationContext.GetService(); - if (quizHost == null) return new ValidationResult("Can't find moviehost"); - if (quizHost.MoviesWhereStevenSpielbergActed.Contains(value.ToString())) + var saladChef= validationContext.GetService(); + if (saladChef == null) return new ValidationResult("Can't find SaladChef"); + if (saladChef.ThingsYouCanPutInASalad.Contains(value.ToString())) { return ValidationResult.Success; } - return new ValidationResult("Steven Spielberg did not star in this movie"); + return new ValidationResult("You should not put that in a salad!"); } } //Simple class to check if DI can be used in Validation attributes - public class MovieQuizHost + public class SaladChef { - public string[] MoviesWhereStevenSpielbergActed = { "The Blues Brothers", "Gremlins", "Vanilla Sky", "Double Dare", "Goldmember" }; + public string[] ThingsYouCanPutInASalad = { "Strawberries", "Pineapple", "Honeydew", "Watermelon", "Grapes" }; } } diff --git a/src/Components/test/testassets/BasicTestApp/Program.cs b/src/Components/test/testassets/BasicTestApp/Program.cs index 20ff8a69852a..b669190d7de0 100644 --- a/src/Components/test/testassets/BasicTestApp/Program.cs +++ b/src/Components/test/testassets/BasicTestApp/Program.cs @@ -38,7 +38,7 @@ public static async Task Main(string[] args) }); builder.Services.AddScoped(); - builder.Services.AddTransient(); + builder.Services.AddTransient(); builder.Logging.AddConfiguration(builder.Configuration.GetSection("Logging")); From 072bdce2daf8e3c26c696f3cc2170acd3d4eaceb Mon Sep 17 00:00:00 2001 From: Pranav K Date: Thu, 13 Jan 2022 13:18:13 -0800 Subject: [PATCH 04/11] Apply suggestions from code review --- src/Components/Forms/src/DataAnnotationsValidator.cs | 3 ++- .../BasicTestApp/FormsTest/ValidationComponentDI.razor | 6 ++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/Components/Forms/src/DataAnnotationsValidator.cs b/src/Components/Forms/src/DataAnnotationsValidator.cs index 3d372c8e1c71..0fbda2282136 100644 --- a/src/Components/Forms/src/DataAnnotationsValidator.cs +++ b/src/Components/Forms/src/DataAnnotationsValidator.cs @@ -12,7 +12,8 @@ public class DataAnnotationsValidator : ComponentBase, IDisposable private EditContext? _originalEditContext; [CascadingParameter] EditContext? CurrentEditContext { get; set; } - [Inject] IServiceProvider? ServiceProvider { get; set; } + + [Inject] private IServiceProvider? ServiceProvider { get; set; } /// protected override void OnInitialized() diff --git a/src/Components/test/testassets/BasicTestApp/FormsTest/ValidationComponentDI.razor b/src/Components/test/testassets/BasicTestApp/FormsTest/ValidationComponentDI.razor index f330953e552e..3c739df99f99 100644 --- a/src/Components/test/testassets/BasicTestApp/FormsTest/ValidationComponentDI.razor +++ b/src/Components/test/testassets/BasicTestApp/FormsTest/ValidationComponentDI.razor @@ -27,8 +27,7 @@ { protected override ValidationResult IsValid(object value, ValidationContext validationContext) { - var saladChef= validationContext.GetService(); - if (saladChef == null) return new ValidationResult("Can't find SaladChef"); + var saladChef= validationContext.GetRequiredService(); if (saladChef.ThingsYouCanPutInASalad.Contains(value.ToString())) { return ValidationResult.Success; @@ -36,9 +35,8 @@ return new ValidationResult("You should not put that in a salad!"); } } - - //Simple class to check if DI can be used in Validation attributes + // Simple class to check if DI can be used in Validation attributes public class SaladChef { public string[] ThingsYouCanPutInASalad = { "Strawberries", "Pineapple", "Honeydew", "Watermelon", "Grapes" }; From c18dabb3ae7e154440cfe8ec397673a4cd2de8b2 Mon Sep 17 00:00:00 2001 From: Tanay Parikh Date: Thu, 13 Jan 2022 13:19:36 -0800 Subject: [PATCH 05/11] Update src/Components/test/testassets/BasicTestApp/FormsTest/ValidationComponentDI.razor --- .../BasicTestApp/FormsTest/ValidationComponentDI.razor | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Components/test/testassets/BasicTestApp/FormsTest/ValidationComponentDI.razor b/src/Components/test/testassets/BasicTestApp/FormsTest/ValidationComponentDI.razor index 3c739df99f99..40b5fc6c1bda 100644 --- a/src/Components/test/testassets/BasicTestApp/FormsTest/ValidationComponentDI.razor +++ b/src/Components/test/testassets/BasicTestApp/FormsTest/ValidationComponentDI.razor @@ -27,7 +27,7 @@ { protected override ValidationResult IsValid(object value, ValidationContext validationContext) { - var saladChef= validationContext.GetRequiredService(); + var saladChef = validationContext.GetRequiredService(); if (saladChef.ThingsYouCanPutInASalad.Contains(value.ToString())) { return ValidationResult.Success; From ccebda4d7fdcd8c2fb524e3cb39c0e7267cd4590 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Tue, 18 Jan 2022 13:35:51 -0800 Subject: [PATCH 06/11] Apply suggestions from code review --- .../Forms/src/EditContextDataAnnotationsExtensions.cs | 11 ++++++----- src/Components/Forms/src/PublicAPI.Unshipped.txt | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/Components/Forms/src/EditContextDataAnnotationsExtensions.cs b/src/Components/Forms/src/EditContextDataAnnotationsExtensions.cs index 487798c84b03..7cf9dac3e2e8 100644 --- a/src/Components/Forms/src/EditContextDataAnnotationsExtensions.cs +++ b/src/Components/Forms/src/EditContextDataAnnotationsExtensions.cs @@ -33,9 +33,10 @@ public static EditContext AddDataAnnotationsValidation(this EditContext editCont /// /// The . /// A disposable object whose disposal will remove DataAnnotations validation support from the . + [Obsolete("This API is obsolete and may be removed in future versions.")] public static IDisposable EnableDataAnnotationsValidation(this EditContext editContext) { - return new DataAnnotationsEventSubscriptions(editContext, null); + return new DataAnnotationsEventSubscriptions(editContext, null!); } /// /// Enables DataAnnotations validation support for the . @@ -43,7 +44,7 @@ public static IDisposable EnableDataAnnotationsValidation(this EditContext editC /// The . /// The to be used in the . /// A disposable object whose disposal will remove DataAnnotations validation support from the . - public static IDisposable EnableDataAnnotationsValidation(this EditContext editContext, IServiceProvider? serviceProvider) + public static IDisposable EnableDataAnnotationsValidation(this EditContext editContext, IServiceProvider serviceProvider) { return new DataAnnotationsEventSubscriptions(editContext, serviceProvider); } @@ -64,7 +65,7 @@ private sealed class DataAnnotationsEventSubscriptions : IDisposable private readonly IServiceProvider? _serviceProvider; private readonly ValidationMessageStore _messages; - public DataAnnotationsEventSubscriptions(EditContext editContext, IServiceProvider? serviceProvider) + public DataAnnotationsEventSubscriptions(EditContext editContext, IServiceProvider serviceProvider) { _editContext = editContext ?? throw new ArgumentNullException(nameof(editContext)); _serviceProvider = serviceProvider; @@ -85,7 +86,7 @@ private void OnFieldChanged(object? sender, FieldChangedEventArgs eventArgs) if (TryGetValidatableProperty(fieldIdentifier, out var propertyInfo)) { var propertyValue = propertyInfo.GetValue(fieldIdentifier.Model); - var validationContext = new ValidationContext(fieldIdentifier.Model, _serviceProvider, null) + var validationContext = new ValidationContext(fieldIdentifier.Model, _serviceProvider, items: null) { MemberName = propertyInfo.Name }; @@ -106,7 +107,7 @@ private void OnFieldChanged(object? sender, FieldChangedEventArgs eventArgs) private void OnValidationRequested(object? sender, ValidationRequestedEventArgs e) { - var validationContext = new ValidationContext(_editContext.Model, _serviceProvider, null); + var validationContext = new ValidationContext(_editContext.Model, _serviceProvider, items: null); var validationResults = new List(); Validator.TryValidateObject(_editContext.Model, validationContext, validationResults, true); diff --git a/src/Components/Forms/src/PublicAPI.Unshipped.txt b/src/Components/Forms/src/PublicAPI.Unshipped.txt index e71f0c67608b..4f93873599a9 100644 --- a/src/Components/Forms/src/PublicAPI.Unshipped.txt +++ b/src/Components/Forms/src/PublicAPI.Unshipped.txt @@ -1,2 +1,2 @@ #nullable enable -static Microsoft.AspNetCore.Components.Forms.EditContextDataAnnotationsExtensions.EnableDataAnnotationsValidation(this Microsoft.AspNetCore.Components.Forms.EditContext! editContext, System.IServiceProvider? serviceProvider) -> System.IDisposable! +static Microsoft.AspNetCore.Components.Forms.EditContextDataAnnotationsExtensions.EnableDataAnnotationsValidation(this Microsoft.AspNetCore.Components.Forms.EditContext! editContext, System.IServiceProvider! serviceProvider) -> System.IDisposable! From d866cf0c2327a21621f5110dfe1b3c7b16cd7ffb Mon Sep 17 00:00:00 2001 From: Pranav K Date: Tue, 18 Jan 2022 13:36:26 -0800 Subject: [PATCH 07/11] Apply suggestions from code review --- src/Components/Forms/src/DataAnnotationsValidator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Components/Forms/src/DataAnnotationsValidator.cs b/src/Components/Forms/src/DataAnnotationsValidator.cs index 0fbda2282136..ed75d97d7708 100644 --- a/src/Components/Forms/src/DataAnnotationsValidator.cs +++ b/src/Components/Forms/src/DataAnnotationsValidator.cs @@ -13,7 +13,7 @@ public class DataAnnotationsValidator : ComponentBase, IDisposable [CascadingParameter] EditContext? CurrentEditContext { get; set; } - [Inject] private IServiceProvider? ServiceProvider { get; set; } + [Inject] private IServiceProvider ServiceProvider { get; set; } /// protected override void OnInitialized() From 6f593056645af733d9dc5cf64029ce18904d91d9 Mon Sep 17 00:00:00 2001 From: Mario van Zeist Date: Wed, 19 Jan 2022 10:40:40 +0100 Subject: [PATCH 08/11] Fix nullable warning. --- src/Components/Forms/src/DataAnnotationsValidator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Components/Forms/src/DataAnnotationsValidator.cs b/src/Components/Forms/src/DataAnnotationsValidator.cs index ed75d97d7708..21a67723e753 100644 --- a/src/Components/Forms/src/DataAnnotationsValidator.cs +++ b/src/Components/Forms/src/DataAnnotationsValidator.cs @@ -13,7 +13,7 @@ public class DataAnnotationsValidator : ComponentBase, IDisposable [CascadingParameter] EditContext? CurrentEditContext { get; set; } - [Inject] private IServiceProvider ServiceProvider { get; set; } + [Inject] private IServiceProvider ServiceProvider { get; set; } = default!; /// protected override void OnInitialized() From 912abe41304486fa53832a54c46e7cc63d90ae95 Mon Sep 17 00:00:00 2001 From: Mario van Zeist Date: Wed, 19 Jan 2022 10:57:34 +0100 Subject: [PATCH 09/11] Updated tests in repsonse of obsoleting EnableDataAnnotationsValidation() --- .../EditContextDataAnnotationsExtensionsTest.cs | 14 +++++++------- .../NotifyPropertyChangedValidationComponent.razor | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Components/Forms/test/EditContextDataAnnotationsExtensionsTest.cs b/src/Components/Forms/test/EditContextDataAnnotationsExtensionsTest.cs index cb264bba9b74..d91e5844ecb7 100644 --- a/src/Components/Forms/test/EditContextDataAnnotationsExtensionsTest.cs +++ b/src/Components/Forms/test/EditContextDataAnnotationsExtensionsTest.cs @@ -11,7 +11,7 @@ public class EditContextDataAnnotationsExtensionsTest public void CannotUseNullEditContext() { var editContext = (EditContext)null; - var ex = Assert.Throws(() => editContext.EnableDataAnnotationsValidation()); + var ex = Assert.Throws(() => editContext.EnableDataAnnotationsValidation(null!)); Assert.Equal("editContext", ex.ParamName); } @@ -31,7 +31,7 @@ public void GetsValidationMessagesFromDataAnnotations() // Arrange var model = new TestModel { IntFrom1To100 = 101 }; var editContext = new EditContext(model); - editContext.EnableDataAnnotationsValidation(); + editContext.EnableDataAnnotationsValidation(null!); // Act var isValid = editContext.Validate(); @@ -61,7 +61,7 @@ public void ClearsExistingValidationMessagesOnFurtherRuns() // Arrange var model = new TestModel { IntFrom1To100 = 101 }; var editContext = new EditContext(model); - editContext.EnableDataAnnotationsValidation(); + editContext.EnableDataAnnotationsValidation(null!); // Act/Assert 1: Initially invalid Assert.False(editContext.Validate()); @@ -78,7 +78,7 @@ public void NotifiesValidationStateChangedAfterObjectValidation() // Arrange var model = new TestModel { IntFrom1To100 = 101 }; var editContext = new EditContext(model); - editContext.EnableDataAnnotationsValidation(); + editContext.EnableDataAnnotationsValidation(null!); var onValidationStateChangedCount = 0; editContext.OnValidationStateChanged += (sender, eventArgs) => onValidationStateChangedCount++; @@ -106,7 +106,7 @@ public void PerformsPerPropertyValidationOnFieldChange() var model = new TestModel { IntFrom1To100 = 101 }; var independentTopLevelModel = new object(); // To show we can validate things on any model, not just the top-level one var editContext = new EditContext(independentTopLevelModel); - editContext.EnableDataAnnotationsValidation(); + editContext.EnableDataAnnotationsValidation(null!); var onValidationStateChangedCount = 0; var requiredStringIdentifier = new FieldIdentifier(model, nameof(TestModel.RequiredString)); var intFrom1To100Identifier = new FieldIdentifier(model, nameof(TestModel.IntFrom1To100)); @@ -146,7 +146,7 @@ public void IgnoresFieldChangesThatDoNotCorrespondToAValidatableProperty(string { // Arrange var editContext = new EditContext(new TestModel()); - editContext.EnableDataAnnotationsValidation(); + editContext.EnableDataAnnotationsValidation(null!); var onValidationStateChangedCount = 0; editContext.OnValidationStateChanged += (sender, eventArgs) => onValidationStateChangedCount++; @@ -165,7 +165,7 @@ public void CanDetachFromEditContext() // Arrange var model = new TestModel { IntFrom1To100 = 101 }; var editContext = new EditContext(model); - var subscription = editContext.EnableDataAnnotationsValidation(); + var subscription = editContext.EnableDataAnnotationsValidation(null!); // Act/Assert 1: when we're attached Assert.False(editContext.Validate()); diff --git a/src/Components/test/testassets/BasicTestApp/FormsTest/NotifyPropertyChangedValidationComponent.razor b/src/Components/test/testassets/BasicTestApp/FormsTest/NotifyPropertyChangedValidationComponent.razor index 936416016ab9..1ca50a8bf9e7 100644 --- a/src/Components/test/testassets/BasicTestApp/FormsTest/NotifyPropertyChangedValidationComponent.razor +++ b/src/Components/test/testassets/BasicTestApp/FormsTest/NotifyPropertyChangedValidationComponent.razor @@ -42,7 +42,7 @@ protected override void OnInitialized() { editContext = new EditContext(person); - editContext.EnableDataAnnotationsValidation(); + editContext.EnableDataAnnotationsValidation(null!); // Wire up INotifyPropertyChanged to the EditContext person.PropertyChanged += (sender, eventArgs) => From 65fc71c2b7bbe2ddfb2ab80b45d9ed05c00c05a6 Mon Sep 17 00:00:00 2001 From: Mario van Zeist Date: Wed, 19 Jan 2022 11:20:31 +0100 Subject: [PATCH 10/11] Added explicit null check, and Updated tests to reflect this change by adding a dummy IServiceProvider --- .../src/EditContextDataAnnotationsExtensions.cs | 4 ++++ .../EditContextDataAnnotationsExtensionsTest.cs | 17 ++++++++++------- ...tifyPropertyChangedValidationComponent.razor | 8 +++++++- 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/src/Components/Forms/src/EditContextDataAnnotationsExtensions.cs b/src/Components/Forms/src/EditContextDataAnnotationsExtensions.cs index 7cf9dac3e2e8..30937f5bb248 100644 --- a/src/Components/Forms/src/EditContextDataAnnotationsExtensions.cs +++ b/src/Components/Forms/src/EditContextDataAnnotationsExtensions.cs @@ -46,6 +46,10 @@ public static IDisposable EnableDataAnnotationsValidation(this EditContext editC /// A disposable object whose disposal will remove DataAnnotations validation support from the . public static IDisposable EnableDataAnnotationsValidation(this EditContext editContext, IServiceProvider serviceProvider) { + if (serviceProvider == null) + { + throw new ArgumentNullException(nameof(serviceProvider)); + } return new DataAnnotationsEventSubscriptions(editContext, serviceProvider); } diff --git a/src/Components/Forms/test/EditContextDataAnnotationsExtensionsTest.cs b/src/Components/Forms/test/EditContextDataAnnotationsExtensionsTest.cs index d91e5844ecb7..3ebd566d4f65 100644 --- a/src/Components/Forms/test/EditContextDataAnnotationsExtensionsTest.cs +++ b/src/Components/Forms/test/EditContextDataAnnotationsExtensionsTest.cs @@ -2,16 +2,19 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.ComponentModel.DataAnnotations; +using Microsoft.AspNetCore.Components.Test.Helpers; namespace Microsoft.AspNetCore.Components.Forms; public class EditContextDataAnnotationsExtensionsTest { + private static readonly IServiceProvider _serviceProvider = new TestServiceProvider(); + [Fact] public void CannotUseNullEditContext() { var editContext = (EditContext)null; - var ex = Assert.Throws(() => editContext.EnableDataAnnotationsValidation(null!)); + var ex = Assert.Throws(() => editContext.EnableDataAnnotationsValidation(_serviceProvider)); Assert.Equal("editContext", ex.ParamName); } @@ -31,7 +34,7 @@ public void GetsValidationMessagesFromDataAnnotations() // Arrange var model = new TestModel { IntFrom1To100 = 101 }; var editContext = new EditContext(model); - editContext.EnableDataAnnotationsValidation(null!); + editContext.EnableDataAnnotationsValidation(_serviceProvider); // Act var isValid = editContext.Validate(); @@ -61,7 +64,7 @@ public void ClearsExistingValidationMessagesOnFurtherRuns() // Arrange var model = new TestModel { IntFrom1To100 = 101 }; var editContext = new EditContext(model); - editContext.EnableDataAnnotationsValidation(null!); + editContext.EnableDataAnnotationsValidation(_serviceProvider); // Act/Assert 1: Initially invalid Assert.False(editContext.Validate()); @@ -78,7 +81,7 @@ public void NotifiesValidationStateChangedAfterObjectValidation() // Arrange var model = new TestModel { IntFrom1To100 = 101 }; var editContext = new EditContext(model); - editContext.EnableDataAnnotationsValidation(null!); + editContext.EnableDataAnnotationsValidation(_serviceProvider); var onValidationStateChangedCount = 0; editContext.OnValidationStateChanged += (sender, eventArgs) => onValidationStateChangedCount++; @@ -106,7 +109,7 @@ public void PerformsPerPropertyValidationOnFieldChange() var model = new TestModel { IntFrom1To100 = 101 }; var independentTopLevelModel = new object(); // To show we can validate things on any model, not just the top-level one var editContext = new EditContext(independentTopLevelModel); - editContext.EnableDataAnnotationsValidation(null!); + editContext.EnableDataAnnotationsValidation(_serviceProvider); var onValidationStateChangedCount = 0; var requiredStringIdentifier = new FieldIdentifier(model, nameof(TestModel.RequiredString)); var intFrom1To100Identifier = new FieldIdentifier(model, nameof(TestModel.IntFrom1To100)); @@ -146,7 +149,7 @@ public void IgnoresFieldChangesThatDoNotCorrespondToAValidatableProperty(string { // Arrange var editContext = new EditContext(new TestModel()); - editContext.EnableDataAnnotationsValidation(null!); + editContext.EnableDataAnnotationsValidation(_serviceProvider); var onValidationStateChangedCount = 0; editContext.OnValidationStateChanged += (sender, eventArgs) => onValidationStateChangedCount++; @@ -165,7 +168,7 @@ public void CanDetachFromEditContext() // Arrange var model = new TestModel { IntFrom1To100 = 101 }; var editContext = new EditContext(model); - var subscription = editContext.EnableDataAnnotationsValidation(null!); + var subscription = editContext.EnableDataAnnotationsValidation(_serviceProvider); // Act/Assert 1: when we're attached Assert.False(editContext.Validate()); diff --git a/src/Components/test/testassets/BasicTestApp/FormsTest/NotifyPropertyChangedValidationComponent.razor b/src/Components/test/testassets/BasicTestApp/FormsTest/NotifyPropertyChangedValidationComponent.razor index 1ca50a8bf9e7..f08a419ef6f6 100644 --- a/src/Components/test/testassets/BasicTestApp/FormsTest/NotifyPropertyChangedValidationComponent.razor +++ b/src/Components/test/testassets/BasicTestApp/FormsTest/NotifyPropertyChangedValidationComponent.razor @@ -42,7 +42,7 @@ protected override void OnInitialized() { editContext = new EditContext(person); - editContext.EnableDataAnnotationsValidation(null!); + editContext.EnableDataAnnotationsValidation(new TestServiceProvider()); // Wire up INotifyPropertyChanged to the EditContext person.PropertyChanged += (sender, eventArgs) => @@ -103,4 +103,10 @@ #endregion } + + public class TestServiceProvider : IServiceProvider + { + public object GetService(Type serviceType) + => throw new NotImplementedException(); + } } From 442733ee85402ec7f6584906023182163539cabe Mon Sep 17 00:00:00 2001 From: Mario van Zeist Date: Wed, 19 Jan 2022 12:37:02 +0100 Subject: [PATCH 11/11] Fix test not working when running Blazor-Server due to different DI Configuration in tests. --- src/Components/test/testassets/TestServer/ServerStartup.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Components/test/testassets/TestServer/ServerStartup.cs b/src/Components/test/testassets/TestServer/ServerStartup.cs index ca6dcc0b3104..9da10dfe1a72 100644 --- a/src/Components/test/testassets/TestServer/ServerStartup.cs +++ b/src/Components/test/testassets/TestServer/ServerStartup.cs @@ -29,6 +29,7 @@ public void ConfigureServices(IServiceCollection services) javaScriptInitializer: "myJsRootComponentInitializers.testInitializer"); }); services.AddSingleton(); + services.AddTransient(); // Since tests run in parallel, we use an ephemeral key provider to avoid filesystem // contention issues.