mrpmorris / blazor-validation

Validation extensions for Microsoft Blazor / FluentValidation
MIT License
201 stars 26 forks source link

Address validation not working #4

Closed guswct closed 5 years ago

guswct commented 5 years ago

Thanks for this great tool. In the example projects, when I click submit, the addresses don't show any validation errors, am I missing something? Thanks

mrpmorris commented 5 years ago

This is a known problem in the current version of Blazor.

mrpmorris commented 5 years ago

I'll see if there is anything I can do

guswct commented 5 years ago

Thank you! That would be awesome

mrpmorris commented 5 years ago

Do this in your form

<EditForm Model=Person OnSubmit=FormSubmitted>
</EditForm>

@code {
    Person Person = new Person();

    void FormSubmitted(EditContext editContext)
    {
        bool formIsValid = editContext.ValidateWholeForm();
        System.Diagnostics.Debug.WriteLine($"Form is valid: {formIsValid}");
    }
}

And here is a helper class to fix the Blazor bug.

using Microsoft.AspNetCore.Components.Forms;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace HandlingFormSubmission
{
    public static class EditContextExtensions
    {
        static PropertyInfo IsModifiedProperty;
        static MethodInfo GetFieldStateMethod;

        public static bool ValidateWholeForm(this EditContext editContext)
        {
            var validatedObjects = new HashSet<object>();
            ValidateObject(editContext, editContext.Model, validatedObjects);
            editContext.NotifyValidationStateChanged();
            return !editContext.GetValidationMessages().Any();
        }

        public static void ValidateProperty(this EditContext editContext, FieldIdentifier fieldIdentifier)
        {
            if (fieldIdentifier.Model == null)
                return;

            var propertyInfo = fieldIdentifier.Model.GetType().GetProperty(
                fieldIdentifier.FieldName,
                BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Static);

            var validatedObjects = new HashSet<object>();
            ValidateProperty(editContext, fieldIdentifier.Model, propertyInfo, validatedObjects);
        }

        private static void ValidateObject(
            EditContext editContext,
            object instance,
            HashSet<object> validatedObjects)
        {
            if (instance == null)
                return;

            if (instance.GetType().Assembly == typeof(string).Assembly)
                return;

            if (validatedObjects.Contains(instance))
                return;

            validatedObjects.Add(instance);

            var properties = instance.GetType().GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
            foreach (PropertyInfo property in properties)
                ValidateProperty(editContext, instance, property, validatedObjects);
        }

        private static void ValidateProperty(
            EditContext editContext,
            object instance,
            PropertyInfo property,
            HashSet<object> validatedObjects)
        {
            NotifyPropertyChanged(editContext, instance, property.Name);

            object value = property.GetValue(instance);
            ValidateObject(editContext, value, validatedObjects);
        }

        private static void NotifyPropertyChanged(
            EditContext editContext,
            object instance,
            string propertyName)
        {
            if (GetFieldStateMethod == null)
            {
                GetFieldStateMethod = editContext.GetType().GetMethod(
                    "GetFieldState",
                    BindingFlags.NonPublic | BindingFlags.Instance);
            }

            var fieldIdentifier = new FieldIdentifier(instance, propertyName);
            object fieldState = GetFieldStateMethod.Invoke(editContext, new object[] { fieldIdentifier, true });

            if (IsModifiedProperty == null)
            {
                IsModifiedProperty = fieldState.GetType().GetProperty(
                    "IsModified",
                    BindingFlags.Public | BindingFlags.Instance);
            }

            object originalIsModified = IsModifiedProperty.GetValue(fieldState);
            editContext.NotifyFieldChanged(fieldIdentifier);
            IsModifiedProperty.SetValue(fieldState, originalIsModified);
        }

    }
}
guswct commented 5 years ago

This worked like a charm, thanks a lot ;-)

mrpmorris commented 5 years ago

You are welcome :)

mrpmorris commented 5 years ago

I've added the following Extension

<EditForm Model=Person OnSubmit=FormSubmitted>
</EditForm>

void FormSubmitted(EditContext editContext)
{
  bool isValid = editContext.ValidateWholeForm();
}

This will validate the EditForm.Model and all its properties (recursively).

https://github.com/mrpmorris/blazor-validation/blob/master/src/PeterLeslieMorris.Blazor.Validation/EditContextExtensions.cs#L13