Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Fluent Validation changing CustomAsync to MustAsync

Could some one please help me to resolved this? i'm trying to change CustomAsync to MustAsync, but i couldn't make things to work. Below is my custom method

RuleFor(o => o).MustAsync(o => {
            return  CheckIdNumberAlreadyExist(o)
          });

      private static async Task<ValidationFailure> CheckIdNumberAlreadyExist(SaveProxyCommand command)
      {
          if (command.Id > 0)
              return null;

          using (IDbConnection connection = new SqlConnection(ConnectionSettings.LicensingConnectionString))
          {
              var param = new DynamicParameters();
              param.Add("@idnumber", command.IdNumber);

              var vehicle = await connection.QueryFirstOrDefaultAsync<dynamic>("new_checkDuplicateProxyIdNumber", param, commandType: CommandType.StoredProcedure);

              return vehicle != null
                  ? new ValidationFailure("IdNumber", "Id Number Already Exist")
                  : null;
          }
       }
like image 323
Velkumar Avatar asked Sep 29 '17 09:09

Velkumar


People also ask

Is fluent validation good?

FluentValidation provides a great alternative to Data Annotations in order to validate our models. As we've seen, the validation rules are easy to read, easy to test, and enable great separation of concerns keeping our controllers lightweight.

How does fluent validation works?

FluentValidation is a server-side library and does not provide any client-side validation directly. However, it can provide metadata which can be applied to the generated HTML elements for use with a client-side framework such as jQuery Validate in the same way that ASP. NET's default validation attributes work.

What is fluent validation C#?

FluentValidation is a .NET library for building strongly-typed validation rules. It Uses a fluent interface and lambda expressions for building validation rules. It helps clean up your domain code and make it more cohesive, as well as giving you a single place to look for validation logic.

Is fluent validation open source?

Fluent Validation is a popular open source library for solving complex validation requirements written by Jeremy Skinner. You can find the source code and documentation for the library at https://github.com/JeremySkinner/fluentvalidation.


2 Answers

To make it work with the latest version of the FluentValidation, I had to use the codes like below.

RuleFor(ws => ws).MustAsync((x, cancellation) => UserHasAccess(x)).WithMessage("User doesn't have access to perform this action");

Please notice the lambda expression here MustAsync((x, cancellation) => UserHasAccess(x)), without this I was always getting an error as cannot convert from 'method group' to 'Func<Worksheet, CancellationToken, Task<bool>>

Below is my custom UserHasAccess function.

private async Task <bool> UserHasAccess(Worksheet worksheet) {
    var permissionObject = await _dataProviderService.GetItemAsync(worksheet.FileItemId);
    if (permissionObject is null) return false;
    if (EditAccess(permissionObject.Permission)) return true;
    return false;
}
like image 164
Sibeesh Venu Avatar answered Oct 17 '22 14:10

Sibeesh Venu


I'm assuming you're using a version of FluentValidation prior to version 6, as you're not passing in a Continuation Token, so I've based my answer on version 5.6.2.

Your example code does not compile, for starters, as you're missing a semi-colon in your actual rule. You are also evaluating two different properties on the SaveProxyCommand parameter.

I've built a very small POC based on some assumptions:

Given 2 classes:

public class SaveProxyCommand {
    public int Id { get; set; }
}

public class ValidationFailure {
    public string PropertyName { get; }
    public string Message { get; }

    public ValidationFailure(string propertyName, string message){
        Message = message;
        PropertyName = propertyName;
    }
}

And a validator:

public class SaveProxyCommandValidator : AbstractValidator<SaveProxyCommand>{

    public SaveProxyCommandValidator()
    {
        RuleFor(o => o).MustAsync(CheckIdNumberAlreadyExists)
                       .WithName("Id")
                       .WithState(o => new ValidationFailure(nameof(o.IdNumber), "Id Number Already Exist"));
    }

    private static async Task<bool> CheckIdNumberAlreadyExists(SaveProxyCommand command) {
        if (command.Id > 0)
            return true;

        var existingIdNumbers = new[] {
            1, 2, 3, 4
        };

        // This is a fudge, but you'd make your db call here
        var isNewNumber = !(await Task.FromResult(existingIdNumbers.Contains(command.IdNumber)));

        return isNewNumber;

    }
}

I didn't include the call to the database, as that's not part of your problem. There are a couple of things of note here:

  1. You're not setting the .WithName annotation method, but when you're setting up a validation rule for an object you have to do this, as FluentValidation expects you to specify specific properties to be validated by default, if you pass in an entire object it just doesn't know how to report errors back.
  2. Must/MustAsync need to return a bool/Task<bool> instead of a custom object. To get around this, you can specify a custom state to be returned when failing validation.

You can then get access to this like this:

var sut = new SaveProxyCommand { Id = 0, IdNumber = 3 };
var validator = new SaveProxyCommandValidator();
var result = validator.ValidateAsync(sut).GetAwaiter().GetResult();
var ValidationFailures = result.Errors?.Select(s => s.CustomState).Cast<ValidationFailure>();

The above does not take into account empty collections, it's just an example of how to dig into the object graph to retrieve custom state.

As a suggestion, fluentvalidation works best if you set up individual rules per property, instead of validating the entire object. My take on this would be something like this:

public class SaveProxyCommandValidator : AbstractValidator<SaveProxyCommand>{

    public SaveProxyCommandValidator()
    {
        RuleFor(o => o.IdNumber).MustAsync(CheckIdNumberAlreadyExists)
                                .Unless(o => o.Id > 0)
                                .WithState(o => new ValidationFailure(nameof(o.IdNumber), "Id Number Already Exist"));
    }

    private static async Task<bool> CheckIdNumberAlreadyExists(int numberToEvaluate) {
        var existingIdNumbers = new[] {
            1, 2, 3, 4
        };

        // This is a fudge, but you'd make your db call here
        var isNewNumber = !(await Task.FromResult(existingIdNumbers.Contains(numberToEvaluate)));

        return isNewNumber;
    }
}

This read more like a narrative, it uses the .Unless construct to only run the rule if Id is not more than 0, and does not require the evaluation of the entire object.

like image 4
Yannick Meeus Avatar answered Oct 17 '22 12:10

Yannick Meeus