dotnet / aspnetcore

ASP.NET Core is a cross-platform .NET framework for building modern cloud-based web applications on Windows, Mac, or Linux.
https://asp.net
MIT License
35.37k stars 9.99k forks source link

Custom validation class in Blazor is not working as expected to apply CSS library validation styles. #30496

Closed TanvirArjel closed 3 years ago

TanvirArjel commented 3 years ago

To apply the Bootstrap validation styles in Blazor If we extend the FieldCssClassProvider as follows:

public class BootstrapValidationClassProvider : FieldCssClassProvider
{
    public override string GetFieldCssClass(EditContext editContext, in FieldIdentifier fieldIdentifier)
    {
        bool isValid = !editContext.GetValidationMessages(fieldIdentifier).Any();

        return isValid ? "is-valid" : "is-invalid"; // Bootstrap validation class
    }
}

and use as follows:

<EditForm EditContext="@editContext" OnSubmit="@HandleSubmit">

    <button type="submit">Submit</button>
</EditForm>

@code {
    private SetUserPasswordModel SetUserPasswordModel = new SetUserPasswordModel();
    private EditContext editContext;

    protected override void OnInitialized()
    {
        editContext = new EditContext(SetUserPasswordModel);
        editContext.SetFieldCssClassProvider(new BootstrapValidationClassProvider());
    }

    private async Task HandleSubmit()
    {
      .....
    }
}

Then the form initially renders as follows: InkedCapture_LI

Although the form inputs have not been touched yet, the validation has been applied this is because FieldCssClassProvider is setting is-valid class even before the form is touched which should not be. So currently setting a custom validation class of any CSS library would not work with this. I hope you understood. Validation state class should only be set if the input field is touched and actual validation is done.

To overcome this If do as follows:

public class BootstrapValidationClassProvider : FieldCssClassProvider
{
    public override string GetFieldCssClass(EditContext editContext, in FieldIdentifier fieldIdentifier)
    {
        if (editContext == null)
        {
            throw new ArgumentNullException(nameof(editContext));
        }

        if (!editContext.IsModified())
        {
            return string.Empty;
        }

        bool isValid = !editContext.GetValidationMessages(fieldIdentifier).Any();

        return isValid ? "is-valid" : "is-invalid";
    }
}

Then "is-valid" and "is-invalid" classes are not applied on the form submit because on form submit without touching the input fields editContext.IsModified() returns false. If editContext.IsModified() would have returned true on form submit and marked all fields as modified then It would have worked as expected.

I have also found that there is no way to mark editContext as modified on the form submit. Angular and React have this option to mark the form and its inputs as modified on form submit manually.

Possible Solution:

  1. Trigger the FieldCssClassProvider only if the input field is touched or the form is submitted.
  2. Mark the editContext and all the inputs as modified if the form is submitted or add an option to mark editContext and all the inputs as modified on the form submit like Angular and React. Surprisingly, there is a EditContext.MarkAsUnmodified() method but no EditContext.MarkAsModified() method. How was that possible?. Adding EditContext.MarkAsModified() methods would solve the issue.
guardrex commented 3 years ago

:eyes:

ghost commented 3 years ago

We've moved this issue to the Backlog milestone. This means that it is not going to be worked on for the coming release. We will reassess the backlog following the current release and consider this item at that time. To learn more about our issue management process and to have better expectation regarding different types of issues you can read our Triage Process.

VahidN commented 3 years ago

You can use the IsModified method on an input field too:

var isValid = !editContext.GetValidationMessages(fieldIdentifier).Any();
if (editContext.IsModified(fieldIdentifier))
{
    return isValid ? "is-valid" : "is-invalid";
}
return isValid ? "" : "is-invalid";
TanvirArjel commented 3 years ago

@VahidN Your solution does not work because whenever the form is submitted without touching any input field it does not apply the validation classes as currently on form submit input fields in the edit context are not marked as modified.

VahidN commented 3 years ago

@TanvirArjel
It works for me OnValidSubmit : editform

Do you know why? Because the validation system doesn't care if you have touched anything or not. It cares about the defined validation rules such as [Required] and if it failed, it will mark the untouched field as an invalid one. That's enough to show the validation messages and applying the related CSS classes. All of these steps happen during the call to the _editContext.Validate() of OnValidSubmit . If you are not using the OnValidSubmit event handler, you should call the editContext.Validate() method yourself. The posted solution prevents showing the is-valid class on initially loaded form.

TanvirArjel commented 3 years ago

@VahidN Got your point. The following does not work:

public class BootstrapValidationClassProvider : FieldCssClassProvider
{
    public override string GetFieldCssClass(EditContext editContext, in FieldIdentifier fieldIdentifier)
    {
        if (editContext == null)
        {
            throw new ArgumentNullException(nameof(editContext));
        }

        if (!editContext.IsModified())
        {
            return string.Empty;
        }

        bool isValid = !editContext.GetValidationMessages(fieldIdentifier).Any();

        if (editContext.IsModified(fieldIdentifier))
        {
            return isValid ? "is-valid" : "is-invalid";
        }

        return isValid ? string.Empty : "is-invalid";
    }
}

Because editContext.IsModified() is not being marked as modified on submit. If I remove this then it works as follows:

public class BootstrapValidationClassProvider : FieldCssClassProvider
{
    public override string GetFieldCssClass(EditContext editContext, in FieldIdentifier fieldIdentifier)
    {
        if (editContext == null)
        {
            throw new ArgumentNullException(nameof(editContext));
        }

        bool isValid = !editContext.GetValidationMessages(fieldIdentifier).Any();

        if (editContext.IsModified(fieldIdentifier))
        {
            return isValid ? "is-valid" : "is-invalid";
        }

        return isValid ? string.Empty : "is-invalid";
    }
}

However, Thank you for your comment.

TanvirArjel commented 3 years ago

I am closing this as a workaround is working as expected. But for EditContext.MarkAsModified() I am going to open a new issue.