private void OnFieldChanged(object?sender, FieldChangedEventArgs eventArgs)
            {
                var fieldIdentifier = eventArgs.FieldIdentifier;

                if (TryGetValidatableProperty(fieldIdentifier, out var propertyInfo))
                {
                    var propertyValue     = propertyInfo.GetValue(fieldIdentifier.Model);
                    var validationContext = new ValidationContext(fieldIdentifier.Model)
                    {
                        MemberName = propertyInfo.Name
                    };
                    var results = new List <ValidationResult>();

                    Validator.TryValidateProperty(propertyValue, validationContext, results);
                    _messages.Clear(fieldIdentifier);
                    foreach (var result in CollectionsMarshal.AsSpan(results))
                    {
                        _messages.Add(fieldIdentifier, result.ErrorMessage !);
                    }

                    // We have to notify even if there were no messages before and are still no messages now,
                    // because the "state" that changed might be the completion of some async validation task
                    _editContext.NotifyValidationStateChanged();
                }
            }
예제 #2
0
        public void CanEnumerateValidationMessagesAcrossAllStoresForAllFields()
        {
            // Arrange
            var editContext = new EditContext(new object());
            var store1      = new ValidationMessageStore(editContext);
            var store2      = new ValidationMessageStore(editContext);
            var field1      = new FieldIdentifier(new object(), "field1");
            var field2      = new FieldIdentifier(new object(), "field2");

            store1.Add(field1, "Store 1 field 1 message 1");
            store1.Add(field1, "Store 1 field 1 message 2");
            store1.Add(field2, "Store 1 field 2 message 1");
            store2.Add(field1, "Store 2 field 1 message 1");

            // Act/Assert
            Assert.Equal(new[]
            {
                "Store 1 field 1 message 1",
                "Store 1 field 1 message 2",
                "Store 1 field 2 message 1",
                "Store 2 field 1 message 1",
            }, editContext.GetValidationMessages().OrderBy(x => x)); // Sort because the order isn't defined

            // Act/Assert: After clearing a single store, we only see the results from other stores
            store1.Clear();
            Assert.Equal(new[] { "Store 2 field 1 message 1", }, editContext.GetValidationMessages());
        }
예제 #3
0
        public void CanEnumerateValidationMessagesAcrossAllStoresForSingleField()
        {
            // Arrange
            var editContext      = new EditContext(new object());
            var store1           = new ValidationMessageStore(editContext);
            var store2           = new ValidationMessageStore(editContext);
            var field            = new FieldIdentifier(new object(), "field");
            var fieldWithNoState = new FieldIdentifier(new object(), "field with no state");

            store1.Add(field, "Store 1 message 1");
            store1.Add(field, "Store 1 message 2");
            store1.Add(new FieldIdentifier(new object(), "otherfield"), "Message for other field that should not appear in results");
            store2.Add(field, "Store 2 message 1");

            // Act/Assert: Can pick out the messages for a field
            Assert.Equal(new[]
            {
                "Store 1 message 1",
                "Store 1 message 2",
                "Store 2 message 1",
            }, editContext.GetValidationMessages(field).OrderBy(x => x)); // Sort because the order isn't defined

            // Act/Assert: It's fine to ask for messages for a field with no associated state
            Assert.Empty(editContext.GetValidationMessages(fieldWithNoState));

            // Act/Assert: After clearing a single store, we only see the results from other stores
            store1.Clear(field);
            Assert.Equal(new[] { "Store 2 message 1", }, editContext.GetValidationMessages(field));
        }
예제 #4
0
        public async Task SuppliesFieldClassCorrespondingToFieldState()
        {
            // Arrange
            var model         = new TestModel();
            var rootComponent = new TestInputHostComponent <string, TestInputComponent <string> >
            {
                EditContext     = new EditContext(model),
                ValueExpression = () => model.StringProperty
            };
            var fieldIdentifier = FieldIdentifier.Create(() => model.StringProperty);

            // Act/Assert: Initally, it's valid and unmodified
            var inputComponent = await RenderAndGetTestInputComponentAsync(rootComponent);

            Assert.Equal("valid", inputComponent.CssClass); //  no Class was specified

            // Act/Assert: Modify the field
            rootComponent.EditContext.NotifyFieldChanged(fieldIdentifier);
            Assert.Equal("modified valid", inputComponent.CssClass);

            // Act/Assert: Make it invalid
            var messages = new ValidationMessageStore(rootComponent.EditContext);

            messages.Add(fieldIdentifier, "I do not like this value");
            Assert.Equal("modified invalid", inputComponent.CssClass);

            // Act/Assert: Clear the modification flag
            rootComponent.EditContext.MarkAsUnmodified(fieldIdentifier);
            Assert.Equal("invalid", inputComponent.CssClass);

            // Act/Assert: Make it valid
            messages.Clear();
            Assert.Equal("valid", inputComponent.CssClass);
        }
        private static void ValidateModel(EditContext editContext, ValidationMessageStore messages, IStringLocalizer localizer)
        {
            var validator         = GetValidatorForModel(editContext.Model, editContext.Model, localizer);
            var validationResults = validator.Validate(CreateValidationContext(editContext.Model));

            messages.Clear();
            foreach (var validationResult in validationResults.Errors.Distinct(CompareError.Instance))
            {
                messages.Add(editContext.Field(validationResult.PropertyName), validationResult.ErrorMessage);
            }

            editContext.NotifyValidationStateChanged();
        }
예제 #6
0
        protected override void OnInitialized()
        {
            _validationMessageStore = new ValidationMessageStore(EditContext);

            // Perform object-level validation (starting from the root model) on request
            EditContext.OnValidationRequested += (sender, eventArgs) =>
            {
                _validationMessageStore.Clear();
                ValidateObject(EditContext.Model, new HashSet <object>());
                EditContext.NotifyValidationStateChanged();
            };

            // Perform per-field validation on each field edit
            EditContext.OnFieldChanged += (sender, eventArgs) =>
                                          ValidateField(EditContext, _validationMessageStore, eventArgs.FieldIdentifier);
        }
        public void CanClearMessagesForAllFields()
        {
            // Arrange
            var messages = new ValidationMessageStore(new EditContext(new object()));
            var field1   = new FieldIdentifier(new object(), "field1");
            var field2   = new FieldIdentifier(new object(), "field2");

            messages.Add(field1, "Field 1 message 1");
            messages.Add(field2, "Field 2 message 1");

            // Act
            messages.Clear();

            // Assert
            Assert.Empty(messages[field1]);
            Assert.Empty(messages[field2]);
        }
예제 #8
0
        public async Task AriaAttributeRemovedWhenStateChangesToValidFromInvalid()
        {
            // Arrange
            var model         = new TestModel();
            var rootComponent = new TestInputHostComponent <string, TestInputComponent <string> >
            {
                EditContext     = new EditContext(model),
                ValueExpression = () => model.StringProperty
            };
            var fieldIdentifier = FieldIdentifier.Create(() => model.StringProperty);
            var renderer        = new TestRenderer();
            var messageStore    = new ValidationMessageStore(rootComponent.EditContext);

            messageStore.Add(fieldIdentifier, "Artificial error message");
            var rootComponentId = renderer.AssignRootComponentId(rootComponent);
            await renderer.RenderRootComponentAsync(rootComponentId);

            // Initally, it rendered one batch and is invalid
            var batch1           = renderer.Batches.Single();
            var componentFrame1  = batch1.GetComponentFrames <TestInputComponent <string> >().Single();
            var inputComponentId = componentFrame1.ComponentId;
            var component        = (TestInputComponent <string>)componentFrame1.Component;

            Assert.Equal("invalid", component.CssClass);
            Assert.NotNull(component.AdditionalAttributes);
            Assert.True(component.AdditionalAttributes.ContainsKey("aria-invalid"));

            // Act: update the field state in the EditContext and notify
            messageStore.Clear(fieldIdentifier);
            await renderer.Dispatcher.InvokeAsync(rootComponent.EditContext.NotifyValidationStateChanged);

            // Assert: The input component rendered itself again and now has the new class
            var batch2 = renderer.Batches.Skip(1).Single();

            Assert.Equal(inputComponentId, batch2.DiffsByComponentId.Keys.Single());
            Assert.Equal("valid", component.CssClass);
            Assert.Null(component.AdditionalAttributes);
        }
        /// <summary>
        /// Validate the whole form and trigger client UI update.
        /// </summary>
        /// <param name="editContext"></param>
        /// <param name="messages"></param>
        private async void ValidateModel(EditContext editContext, ValidationMessageStore messages)
        {
            // <EditForm> should now be able to run async validations:
            // https://github.com/dotnet/aspnetcore/issues/11914
            var validationResults = await TryValidateModel(editContext);

            messages.Clear();

            var graph = new ModelGraphCache(editContext.Model);

            foreach (var error in validationResults.Errors)
            {
                var(propertyValue, propertyName) = graph.EvalObjectProperty(error.PropertyName);
                // while it is impossible to have a validation error for a null child property, better be safe than sorry...
                if (propertyValue != null)
                {
                    var fieldID = new FieldIdentifier(propertyValue, propertyName);
                    messages.Add(fieldID, error.ErrorMessage);
                }
            }

            editContext.NotifyValidationStateChanged();
        }
예제 #10
0
        /// <summary>
        /// Validate the whole form and trigger client UI update.
        /// </summary>
        /// <param name="editContext"></param>
        /// <param name="messages"></param>
        private void ValidateModel(EditContext editContext, ValidationMessageStore messages)
        {
            // WARNING: DO NOT USE Async Void + ValidateAsync here
            // Explanation: Blazor UI will get VERY BUGGY for some reason if you do that. (Field CSS lagged behind validation)
            var validationResults = TryValidateModel(editContext);

            messages.Clear();

            var graph = new ModelGraphCache(editContext.Model);

            foreach (var error in validationResults.Errors)
            {
                var(propertyValue, propertyName) = graph.EvalObjectProperty(error.PropertyName);
                // while it is impossible to have a validation error for a null child property, better be safe than sorry...
                if (propertyValue != null)
                {
                    var fieldID = new FieldIdentifier(propertyValue, propertyName);
                    messages.Add(fieldID, error.ErrorMessage);
                }
            }

            editContext.NotifyValidationStateChanged();
        }
예제 #11
0
 /// <summary>
 /// Removes all messages within this <see cref="ValidationMessageStore"/> for the specified field.
 /// </summary>
 /// <param name="store">The <see cref="ValidationMessageStore"/>.</param>
 /// <param name="accessor">Identifies the field for which to remove the messages.</param>
 public static void Clear(this ValidationMessageStore store, Expression <Func <object> > accessor)
 => store.Clear(FieldIdentifier.Create(accessor));