microsoft / fluentui-blazor

Microsoft Fluent UI Blazor components library. For use with ASP.NET Core Blazor applications
https://www.fluentui-blazor.net
MIT License
3.78k stars 363 forks source link

Question: Validation - Any plans to support FluentValidation? #1100

Closed gerfen closed 9 months ago

gerfen commented 10 months ago

As part of the potential validation work mentioned in issue #1055 -- I am curious if any thought has been given to supporting the FluentValidation library?

vnbaaij commented 10 months ago

As their docs state: FluentValidation does not provide integration with Blazor out of the box, but there are several third party libraries you can use to do this.

For our library, we do not want to have any external dependencies, but someone could definitely build an extension that adds support for this (based perhaps on one of the links the docs article points to)

gerfen commented 10 months ago

@vnbaaij Thanks for the quick reply. I just found #838 which discusses adding validation errors below input components. I understand that you do not want to have any external dependencies. My question is more along the lines of providing the appropriate abstraction(s) to allow an external library to provide validation results to the edit form/input components.

It would be nice not to have to provide a separate ValidationMessage for every input component, i.e.

<EditForm Model="@Data" OnValidSubmit="ValidHandlerAsync">
    <DataAnnotationsValidator />
     <FluentTextField Label="Last name:"
                      Placeholder="Enter your last name"
                      @bind-Value="@Data.LastName" Immediate="true" />
     <ValidationMessage For="@(() => Data.LastName)" />
</EditForm>
vnbaaij commented 10 months ago

I don't know FluentValidation enough to say if we have the extension points in place or not. Certainly not opposed to have those in our codebase, if possible. But I think we need to have someone contributing that...any volunteers?

838 has been implemented for v4.2 btw. We added a FluentValidationMessage component. See https://preview.fluentui-blazor.net/basicform-fluentui-components

gerfen commented 10 months ago

I'm willing to help out. FluentValidation is actually pretty easy to use. I have had good success with MudBlazor using a base class like this to trigger field level validation.

public abstract class BaseValidator<T> : AbstractValidator<T>
{
    public Func<object, string, Task<IEnumerable<string>>> ValidateValue => async (model, propertyName) =>
    {
        var result = await ValidateAsync(ValidationContext<T>.CreateWithOptions((T)model, x => x.IncludeProperties(propertyName)));
        return result.IsValid ? Array.Empty<string>() : result.Errors.Select(e => e.ErrorMessage);
    };
}

I will need to look at the code in your dev branch to see what abstractions are there and if they can be used to support an external library. I will not have time to that today unfortunately.

dvoituron commented 10 months ago

I also think it could be very interesting to allow other libraries to be interconnected with FLuentUI Blazor. As long as two important points are respected:

  1. avoid including Breaking changes in the current developement (the best is to add new functionalities)
  2. not be linked to external libraries (including FluentValidation or others).
gerfen commented 10 months ago

I did a small spike last night to test out creating wrapped form components. I only tested wrapping FluentTextField. The code looks like this...

FluentValidatingTextField .razor

@inherits Microsoft.FluentUI.AspNetCore.Components.FluentTextField

@{
    base.BuildRenderTree(__builder);
}
<FluentValidationMessage For="@For"/> 

FluentValidatingTextField.razor.cs

  public partial class FluentValidatingTextField : FluentTextField
  {
      /// <summary>
      /// Specifies the field for which validation messages should be displayed.
      /// </summary>
      [Parameter] public Expression<Func<string?>>? For { get; set; }

  }

example usage - with Blazor-FluentValidation:

 <EditForm Model="@message">
     <FluentStack Orientation="Orientation.Vertical">
         <Validate ValidationProperties="@(ValidationProperties.Set.FluentValidator<TestMessageValidator>())" OnTransformModel="OnTransformModel" />
         <FluentValidatingTextField @bind-Value="message.Text" For="@(()=>message.Text)" Label="Text" Appearance="FluentInputAppearance.Filled"/>
         <FluentButton Type="ButtonType.Submit" Appearance="Appearance.Accent">Submit</FluentButton>
     </FluentStack>
</EditForm>

I tried to create a generic component which inherits from EditForm which would obviate the need to explicitly define:

<Validate ValidationProperties="@(ValidationProperties.Set.FluentValidator<TestMessageValidator>())" OnTransformModel="OnTransformModel" />

but ran into some runtime errors that I have not been able to resolve yet. It maybe best to create a custom edit form implementation much like has been done with MudBlazor. I will continue to investigate as time permits.

Here is a screenshot (note: This used <ValidationnMesssage/> in lieu of <FluentValidationMessage/> from my POC)

image

UPDATE:

I should note that using DataAnnotationsValidator works as expected as well.

  <EditForm Model="@message">
      <FluentStack Orientation="Orientation.Vertical">
          <DataAnnotationsValidator></DataAnnotationsValidator>
          <FluentValidatingTextField @bind-Value="message.Text" For="@(() => message.Text)" Label="Text" Appearance="FluentInputAppearance.Filled"/>
          <FluentButton Type="ButtonType.Submit" Appearance="Appearance.Accent">Submit</FluentButton>
      </FluentStack>
 </EditForm>

image

dvoituron commented 10 months ago

I find it very interesting. Good work! My only comment (for now :-)) concerns the FluentValidatingTextField component.

I suppose the idea is to integrate [Parameter] public Expression<Func<string?>>? For { get; set; } to the FluentTextfield component and not have to duplicate all the components 😀

How will this new attribute be combined with the current features?

gerfen commented 10 months ago

I think there are two approaches:

  1. Just add the For property and the <FluentValidationMessage/> to the existing input controls in FluentUI-Blazor. Then create a new library project with a custom EditForm implementation which integrates with FluentValidation
  2. Create a new library project which references FluentUI-Blazor with a wrapped control per input control and some custom EditForm implementation that allows for integration with FluentValidation

I'd prefer approach #1 but can understand why your team may not prefer that. 😀

gerfen commented 10 months ago

I've looked a bit further into wrapping controls. There is definitely an issue WRT to supporting the Required attribute on input controls and integrating with third-party validation. As you are well aware, when Required is set to true, the controls show a tooltip with a message that is provided by fluentui-blazor. This effectively shorts circuits further validation. I'm currently not familiar enough with implementation to know how to override this behavior to inject a message from another source. At the moment, it appears that all of the code to do that is behind private methods. I'm wondering if either @vnbaaij or @dvoituron can point me at the code which controls this behavior? Thanks!

vnbaaij commented 9 months ago

Hi Michael,

We recently had some issues opened by @FritzTheCat9 and he has done some work in wrapping our components to make them work with FluentValidation. See the repo he created through https://github.com/microsoft/fluentui-blazor/issues/1311. Maybe you guys can get in contact? I'm going to close this issue and turn it into a discussion item