Giter VIP home page Giter VIP logo

ryanelian / fluentvalidation.blazor Goto Github PK

View Code? Open in Web Editor NEW
236.0 8.0 27.0 486 KB

Fluent Validation-powered Blazor component for validating standard <EditForm> :milky_way: :white_check_mark:

Home Page: https://www.nuget.org/packages/Accelist.FluentValidation.Blazor

License: MIT License

C# 99.77% HTML 0.23%
asp-net-core blazor validation linq lambda dependency-injection component

fluentvalidation.blazor's Introduction

FluentValidation.Blazor

Fluent Validation-powered Blazor component for validating standard <EditForm> ๐ŸŒ€ โœ…

GitHub Actions NuGet

Install

dotnet add package FluentValidation
dotnet add package Accelist.FluentValidation.Blazor

Getting Started

This library is a direct replacement to the default Blazor <DataAnnotationValidator> with zero configuration required โšก in the application code base:

<EditForm Model="Form">


    <FluentValidator></FluentValidator>


    <div class="form-group">
        <label for="email">Email</label>
        <InputText id="email" type="email" class="form-control" @bind-Value="Form.Email"></InputText>
        <ValidationMessage For="() => Form.Email"></ValidationMessage>
    </div>
    <div class="form-group">
        <button type="submit" class="btn btn-primary">
            <i class="fas fa-chevron-up"></i>
            Submit
        </button>
    </div>
</EditForm>
@code {
    FormModel Form = new FormModel();
}
public class FormModel
{
    public string Email { set; get; }
}

public class FormModelValidator : AbstractValidator<FormModel>
{
    public FormModelValidator()
    {
        RuleFor(Q => Q.Email).NotEmpty().EmailAddress().MaximumLength(255);
    }
}

The <FluentValidator> component automatically detects the Model data type used by the parent <EditForm> then attempts to acquire the corresponding FluentValidation.IValidator<T> for that model data type.

For this reason, in addition to coding the usual FluentValidation.AbstractValidator<T> Fluent Validation implementation, you are required to register the FluentValidation.IValidator<T> implementation in the Startup.cs Service Provider (Dependency Injection):

services.AddTransient<IValidator<CreateAccountFormModel>, CreateAccountFormModelValidator>();
// Alternatively, use FluentValidation.DependencyInjectionExtensions package (read further down below...)

This effectively allows you, dear programmer, to inject required services to your validation implementations for writing amazing custom validation methods! ๐Ÿ”ฅ

public class FormModelValidator : AbstractValidator<FormModel>
{
    readonly AppDbContext DB;
    readonly IServiceProvider SP;

    public FormModelValidator(AppDbContext db, IServiceProvider sp)
    {
        this.DB = db;
        this.SP = sp;

        RuleFor(Q => Q.Email).NotEmpty().EmailAddress().MaximumLength(255)
            .Must(BeUniqueEmail).WithMessage("Email address is already registered.");
    }

    bool BeUniqueEmail(string email)
    {
        var exist = DB.Account.Where(Q => Q.Email == email).Any();
        return (exist == false);
    }
}

Inlined Validator

Validator parameter may also be passed directly to the component to inline the AbstractValidator implementation instead of relying on .NET Core DI:

<FluentValidator Validator="Validator"></FluentValidator>
@code {
    FormModelValidator Validator = new FormModelValidator();
}

FluentValidation.DependencyInjectionExtensions

dotnet add package FluentValidation.DependencyInjectionExtensions
services.AddValidatorsFromAssemblyContaining<Program>();

Can be used to auto-populate all validators from current application / other project automatically!

Nested Objects & Arrays Validation

FluentValidation offers SetValidator method for validating nested objects and arrays. Combined with Dependency Injection capability of this component library, a complex form can be validated easily:

public class RootValidator : AbstractValidator<Root>
{
    public RootValidator(IValidator<Child> childValidator, IValidator<Item> itemValidator)
    {
        RuleFor(Q => Q.Child).SetValidator(childValidator);
        RuleForEach(Q => Q.ArrayOfItems).SetValidator(itemValidator); // Array, List, IList, ...
    }
}

The validators used MUST be registered in the ASP.NET Core service provider!

If for some reason Dependency Injection is not possible, the parameter ChildValidators (inlined Dictionary<Type, IValidator> defining validators used for each children model types) MUST be passed into the component due to technical reasons.

Repeated Remote Call Warning โš ๏ธ

By default, Fluent Validation does NOT short-circuit the validation chain on first error.

This may cause performance issues when a validation logic hits the database / remote service multiple times in short burst due to validation triggers on field change!

To reduce the impact of repeated custom validation calls, use:

  • .Cascade(CascadeMode.Stop) after RuleFor() chain,

  • Or set ValidatorOptions.CascadeMode = CascadeMode.Stop; on Program.cs application entry point.

Why Not Just Use <DataAnnotationValidator>?

  1. <DataAnnotationValidator> cannot use DI services at the moment...

  2. ... but <DataAnnotationValidator> also cannot do AddModelError() like Razor Pages. Which rules out validation AFTER form submission!

  3. [ValidationAttribute] top-level IsValid() method cannot be async!

fluentvalidation.blazor's People

Contributors

ryanelian avatar sfmskywalker avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

fluentvalidation.blazor's Issues

Library is not compatible with Client-Side Blazor due to Exception with async validation

Hello

Have been using your FluentValidation component and note that it indicates there are issues when calling async validators. I had tried following two versions of an asynchronous rule that verifies if an email address is already registered and thus unavailable. The first causes browser to lock up, whilst second is executed and Failure added to context - though not displayed in UI. PS.

public class RegisterRequestValidator : AbstractValidator<RegisterRequest>
{
      public RegisterRequestValidator(AuthService _svc)
      {            
           // this rule causes browser lockup so not used
            RuleFor(p => p.EmailAddress).MustAsync(async (email, cancellation) => {
                     bool available = await _svc.VerifyEmailAvailableAsync(email);
                     return !available;
            }).WithMessage("EmailAddress is already Registered");

            // this rule is executed but validation message is not displayed in UI
            RuleFor(p => p.EmailAddress).NotEmpty().EmailAddress()
                .Custom(async (email, context) => {
                    var available = await _svc.VerifyEmailAvailableAsync(email);                  
                    if (!available) 
                    {                      
                        context.AddFailure("EmailAddress", "Email has already been registered. Please use another.");
                    }
                }
            );              
      }    
}

The form

<EditForm Model="RegisterModel" OnValidSubmit="HandleRegistration">
        <FluentValidator />

        <div class="form-group">
                <label for="email">Email address</label>
                <InputText Id="email" class="form-control" @bind-Value="RegisterModel.EmailAddress" />
                <ValidationMessage For="@(() => RegisterModel.EmailAddress)" />
        </div>

        <button type="submit" class="btn btn-primary">Submit</button>
</EditForm>

Any ideas?

Thanks

Aiden

Upgraded to FluentValidation 10.x and .NET 6 Preview 3, getting the following error

Error: System.MissingMethodException: Method not found: 'System.Collections.Generic.IList`1<FluentValidation.Results.ValidationFailure> FluentValidation.Results.ValidationResult.get_Errors()'.
   at Microsoft.AspNetCore.Components.Forms.FluentValidator.ValidateModel(EditContext editContext, ValidationMessageStore messages)
   at System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start[TStateMachine](TStateMachine& stateMachine)
   at Microsoft.AspNetCore.Components.Forms.FluentValidator.ValidateModel(EditContext editContext, ValidationMessageStore messages)
   at Microsoft.AspNetCore.Components.Forms.FluentValidator.<>c__DisplayClass24_0.<AddValidation>b__0(Object sender, ValidationRequestedEventArgs eventArgs)
   at Microsoft.AspNetCore.Components.Forms.EditContext.Validate()
   at Microsoft.AspNetCore.Components.Forms.EditForm.HandleSubmitAsync()
   at Microsoft.AspNetCore.Components.ComponentBase.CallStateHasChangedOnAsyncCompletion(Task task)
   at Microsoft.AspNetCore.Components.RenderTree.Renderer.GetErrorHandledTask(Task taskToHandle)

Async validation to server-side call

Does this library support async validation within an EditContext? I'm having issues specifically validating with MustAsync which calls out to a database to ensure data is valid before returning the success/failure to the UI. EditContext seems to Validate successfully before this asynchronous event is called and thus is bypassing the validator when it's invalid.

I see other Blazor Fluent Validation libraries are also possibly having issues with this.

Is this repo abandoned?

Hi maintainers,

PRs to upgrade to FluentValidation 10 are still pending after weeks. Is there a problem with those or is this repo abandoned? I'm not asking to be rude/mean, I prefer this lib over the others for FluentValidation in Blazor, but the potential future lack of support might be a problem if I use this lib in my projects.

Thank you!

Blazor WASM Project With Both Client & Server Validation

Hi there!

Just as the title says, I have a project with both Client & Server Fluent validation. I use the FluentValidator for the client side validation. I wanted to take advantage of your ModelGraphCache for mapping the server validations to a validator (similar to your FluentValidator except manual) on the client side, but it's marked internal. I'm just curious if you had any reservations to opening it up?

How to trigger the validate method manually

Hey,

I want to check if an email already exists or not. But since I can change an e-mail address, I only want to initiate this check if the e-mail address is changed. So I have an original and a copy on my form. If the email address of the models does not match anymore, I want to overwrite the validator. This works so far. But now I would have to tell the validator that it has to validate again. How can i trigger the validate method manually.

<EditForm EditContext="@_editContext" OnValidSubmit="@Save">
            <FluentValidator Validator="_validator" @ref="fluentvalidator"/>
                <div class="k-edit-label">
                    <Translation Key="EINSTELLUNGEN_PERSONEN_EMAIL" />
                </div>
                <div class="k-edit-field">
                    <TelerikTextBox Id="textbox_email" @bind-Value="@SelectedItem.Email" OnChange="SetValidator"/>
                    <ValidationMessage For="() => SelectedItem.Email" />
                </div>
</EditForm>
private async Task SetValidator()
    {
        if (SelectedItem != null || OriginalItem != null)
        {
            if (SelectedItem.Email != OriginalItem.Email)
            {
                _validator = new PersonValidator(ClientExtension.TranslateMessage, _fullpath, true, true);
            }
            else
            {
                _validator = new PersonValidator(ClientExtension.TranslateMessage, _fullpath, false, true);
            }
            fluentvalidator.Validator.ValidateAsync() // <--- here i have to trigger the validation manually but how?
        }
        else
        {
            _validator = new PersonValidator(ClientExtension.TranslateMessage, _fullpath, false, true);
        }
        StateHasChanged();
    }

Minor FluentValidation integration improvements

Hi @ryanelian,

I was looking through your library and noticed a couple of minor things that you may want to consider for better integration with FluentValidation.

  • If you wanted to support InjectValidator, then you could pass the service provider into the ValidationContext
  • FluentValidator.CreateValidationContext could be simplified so you don't have to use reflection.
private IValidationContext CreateValidationContext(object model, IValidatorSelector validatorSelector = null)
{
	if (validatorSelector == null) 
	{
		// No selector specified - use the default.
		validatorSelector = ValidatorOptions.Global.ValidatorSelectors.DefaultValidatorSelectorFactory()
	}

        // Don't need to use reflection to construct the context. 
        // If you create it as a ValidationContext<object> instead of a ValidationContext<T> then FluentValidation will perform the conversion internally, assuming the types are compatible. 
	var context = new ValidationContext<object>(model, new PropertyChain(), validatorSelector);
        // InjectValidator looks for a service provider inside the ValidationContext with this key. 
	context.RootContextData["_FV_ServiceProvider"] = ServiceProvider;
	return context;
}

Hope that's useful

How to do async validation

Hi,

Currently, I'm struggling with async validation. I need to call a REST API to check something.
My test code is

public class PersonValidator : AbstractValidator<Person>
	{
		public PersonValidator()
		{
			RuleFor(x => x.Name).NotEmpty();
			RuleFor(x => x.Age).InclusiveBetween(18, 80).MustAsync(RunAsync);
		}
		private async Task<bool> RunAsync(int ssss, CancellationToken ct = default)
		{
			await Task.Delay(1000);
			return false;
		}
	}

Do you have some experience how to get it working?
Currently this just does nothing.

EditForm OnValidSubmit event is raised when validation failed using MustAsync() method.

Hi @ryanelian. I'm having an issue doing async validation using MustAsync() method. Even though the validation is performed fine, so I get validation errors, the EditForm's OnValidSubmit event callback i raised. This situation only occurs when getting on page and submitting for the first time. I reproduced the issue using your EmailCheckerService.
The model class:

public class TestModel
    {
        public string Id { get; set; }
    }

The validator:

public class TestModelValidator : AbstractValidator<TestModel>
    {
        private readonly EmailCheckerService _emailCheckerService;

        public TestModelValidator(EmailCheckerService emailCheckerService)
        {
            _emailCheckerService = emailCheckerService;
            RuleFor(Q => Q.Id).NotEmpty().MustAsync(CheckEmail);
        }

        private async Task<bool> CheckEmail(string emailId, CancellationToken cancellationToken = default)
        {
            return await _emailCheckerService.IsAvailableAsync(emailId);
        }
    }

The view:

<EditForm OnValidSubmit="@ValidSubmit" Model="@_testModel">
    <FluentValidator></FluentValidator>
    <InputText @bind-Value="@_testModel.Id"></InputText>
    <ValidationMessage For="() => _testModel.Id"></ValidationMessage>
    <button type="submit">Save</button>
</EditForm>

protected override void OnInitialized()
    {
        _testModel = new TestModel();
    }

    private async Task ValidSubmit()
    {
        await Task.CompletedTask;
    }

    private TestModel _testModel;

The project:

<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>netcoreapp3.1</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Accelist.FluentValidation.Blazor" Version="3.0.0" />
    <PackageReference Include="FluentValidation" Version="9.2.2" />
    <PackageReference Include="FluentValidation.DependencyInjectionExtensions" Version="9.2.0" />
  </ItemGroup>

</Project>

Registration:

 services.AddScoped<EmailCheckerService>();
services.AddValidatorsFromAssemblyContaining<Program>();

To reproduce:

  1. Go into index page.
  2. Submit the empty form.
  3. Put a breakpoint inside the ValidSubmit() method.
  4. See that it is called when the async method from the service hasn't finished yet.

Problem having complex MUST()

Hello,
I want to have a complex Rule that checks if a given Date is in a List of objects that are stored in a Service registered class.
Problem now is that I'm not able to use the parent object in the Must() method and also I'm not able to get the values out of the registered Service, right?

Here you can see my try of creating a Rule:

https://ibb.co/M2mVkBw

In my parent object (AbstractValidator<Form_Checkout>) I have also a property with the required values, but can't access it in Must method.

Any hint how I can solve my issue?

Thx!

Allow Inlining Validator as Parameter

Handy for component library makers:

This allows <FluentValidation.FluentValidator Validator="MyValidator"></FluentValidation.FluentValidator>

Which lets component library makers to inline their validator definition into the package, instead of requiring component users to register the validator into the DI.

When the parameter is not NULL, FluentValidator should use the supplied validator instead of querying to the DI.

Nested objects are only validated when submitting the form

Hi!

Your implementation of FluentValidation works fine for me. However there is an issue when you validate properties from nested objects.

See NestedObjectsValidation.razor in my GitHub repo:
https://github.com/MarvinKlein1508/BlazorFluentValidation

Microsofts implementation behaves exactly as your one.However they have an experimental second implementation called
ObjectGraphDataAnnotationsValidator

See https://docs.microsoft.com/en-gb/aspnet/core/blazor/forms-validation?view=aspnetcore-3.1#nested-models-collection-types-and-complex-types

Is there any way to achive support for this?

-Marvin

OnFieldChanged not trigger

I have an input where I change it using javascript, even after calling EditContext.NotifyFieldChanged, NotifyValidationStateChanged, StateHasChanged()

FluentValidator CurrentEditContext.OnFieldChanged does not trigger..

anyidea? the 2 way bind does work, setting the property with new value

Async dispose results in null reference.

When my component disposes, it crashes because the validation is for some reason still triggered.

In TryGetFieldValidator adding

if (_disposedValue) 
      return null

Resolves this issue.

Validation Message is not working for items inside a list

Hi @ryanelian I'm having issues showing errors for my form, my model has a list of items and I wan to validate for duplicated items so I have this rule:

RuleForEach(model=> model.Items)
                .Must((model, item) => IsNameUnique(model.Items, item.Name))
                .WithMessage("Name must be distinct.");

so for show the error in the client I'm using <ValidationMessafe For="() => Item"> and is not working

Does not work with FluentValidation 9.x

FluentValidation 9.0.0 just got released a few days ago, any plans on adding support for it? When I submit a form and it tries to kick off the Validate function, it says it cannot find it:
Microsoft.AspNetCore.Components.WebAssembly.Rendering.WebAssemblyRenderer[100] Unhandled exception rendering component: Method not found: FluentValidation.Results.ValidationResult FluentValidation.IValidator.Validate(object)

InputSelect not clearing message

Id is a Guid so "select one" has a default Guid

                                        <InputSelect class="form-control" @bind-Value="Id">
                                            <option value="0000.0000.0000.0000.0000">Select One</option>
                                            @foreach (var item in items)
                                            {
                                                <option value="@item.Id">@item.Description</option>
                                            }
                                        </InputSelect>

                                        <ValidationMessage For="@(() => Id)" />
                                    </div>

RuleFor(x => x.PresenterIdentificationId).NotEqual(new Guid());
RuleFor(x => x.PresenterIdentificationId).NotEmpty();

the error message only disappears when button save is clicked where InputText removes error message after type and losing focus

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.