【问题标题】:Input string was not in a correct format, fluent validation输入字符串的格式不正确,流畅的验证
【发布时间】:2021-06-09 02:55:22
【问题描述】:

我正在尝试使用 FluentValidation nuget 包验证请求。我正在使用一个具有多个文本框字段的 winform,如下面的方法所示。当用户单击确认按钮时,系统将尝试使用文本框中的数据参数创建我的车辆类的新实例。我想使用验证检查器来查看他们创建的车辆实例是否有效。我遇到的问题是,当有一个文本框需要转换为 in 或 decimal 以创建实例时,我收到错误消息 Input string was not in a correct format. 有没有办法可以传递 int/decimal 值在我检查验证它们之前作为字符串,然后将它们作为正确的形式传回?

这是我的按钮点击方法:

protected override void CheckInputFields()
{
    string registration = textBoxRegistrationNumber.Text;
    string make = textBoxMake.Text;
    string model = textBoxModel.Text;
    string year = textBoxYear.Text;
    string cost = textBoxHireCost.Text;

    ValidationAddVehicle validator = new ValidationAddVehicle();

    //The issue is here. When the cost or year fields are empty, it cant convert them to decimal/int.
    //Which means that the validation checker cannot do the .NotEmpty() method
    ValidationResult results = validator.Validate(Vehicle.New(registration, make, model, Convert.ToDecimal(cost), Convert.ToInt32(year)));

    if (results.IsValid == false)
    {
        foreach (ValidationFailure error in results.Errors)
            MessageBox.Show($"{error.PropertyName} : {error.ErrorMessage}");
        return;
    }
    else
    {
        MessageBox.Show("Success");
        return;
    }
}

这是我创建车辆的方法:

    public static Vehicle New(string registration, string make, string model, decimal cost, int year)
    {
        return new Vehicle
        {
            Registration = registration,
            Model = model,
            Make = make,
            Year = year,
            Cost = cost
        };
    }

这是我的验证检查器:

public class ValidationAddVehicle : AbstractValidator<Vehicle>
{
    public ValidationAddVehicle()
    {
        //Registration
        RuleFor(v => v.Registration)
            .Cascade(CascadeMode.Stop)
            .NotEmpty().WithMessage("{PropertyName} is empty")
            .Length(2, 10).WithMessage("Length ({TotalLength}) of {PropertyName} is invalid")
            .Must(IsValidRegistration).WithMessage("{PropertyName} contains invalid characters")
            .Must(Exists).WithMessage("{PropertyName} already exists");

        //Make
        RuleFor(v => v.Make)
            .Cascade(CascadeMode.Stop)
            .NotEmpty().WithMessage("{PropertyName} is empty")
            .Length(1, 50).WithMessage("Length ({TotalLength}) of {PropertyName} is invalid")
            .Must(IsValidMake).WithMessage("{PropertyName} contains invalid characters");

        //Model
        RuleFor(v => v.Model)
            .Cascade(CascadeMode.Stop)
            .NotEmpty().WithMessage("{PropertyName} is empty")
            .Length(1, 50).WithMessage("Length ({TotalLength}) of {PropertyName} is invalid")
            .Must(IsValidModel).WithMessage("{PropertyName} contains invalid characters");


        //Cost
        RuleFor(v => v.Cost)
            .Cascade(CascadeMode.Stop)
            .NotEmpty().WithMessage("{PropertyName} is empty")
            .Must(IsValidCost).WithMessage("{PropertyName} contains invalid characters")
            .Must(CostValue).WithMessage("{PropertyName} must be greater than zero");

        //Year
        RuleFor(v => v.Year)
            .Cascade(CascadeMode.Stop)
            .NotEmpty().WithMessage("{PropertyName} is empty")
            .Must(IsValidYear).WithMessage("{PropertryName} is an invalid value")
            .Must(Range).WithMessage("{PropertryName} must be between 1886 and {DateTime.Now.AddYears(1).ToString()}");
    }

    protected bool IsValidRegistration(string registration) => String.Concat(registration.Where(r => !Char.IsWhiteSpace(r))) != "" && registration.All(Char.IsLetterOrDigit);

    protected bool Exists(string registration) => !Business.VehicleList.Any(x => x.Registration == registration);

    protected bool IsValidMake(string make) => String.Concat(make.Where(m => !Char.IsWhiteSpace(m))) != "" && make.All(Char.IsLetter);

    protected bool IsValidModel(string model) => String.Concat(model.Where(m => !Char.IsWhiteSpace(m))) != "" && model.All(Char.IsLetterOrDigit);

    protected bool IsValidCost(decimal cost) => String.Concat(cost.ToString().Where(c => !Char.IsWhiteSpace(c))) != "" && Regex.IsMatch(cost.ToString(), @"^-?[0-9]*\.?[0-9]+$");

    protected bool CostValue(decimal cost) => cost > 0;

    protected bool IsValidYear(int year) => String.Concat(year.ToString().Where(y => !Char.IsWhiteSpace(y))) != "" && Regex.IsMatch(year.ToString(), @"^[0-9]");

    protected bool Range(int year) => year.ToString().Length == 4 && Convert.ToDateTime($"{year},1,1") <= DateTime.Now.AddYears(1) && year >= 1886;
}

有没有一种方法可以首先将成本和年份作为字符串传递给车辆类,检查有效性,然后将它们转换为正确的数据类型,或者我可以使用另一种方法来实现这一点?

【问题讨论】:

  • 一种方法是在类上为 YearString 添加一个额外的属性,并在您的验证中使用它,但我更愿意在用户输入时验证文本框和只允许接受的字符(数字、逗号、句号...等),然后应用您的流利验证

标签: c# winforms validation fluentvalidation


【解决方案1】:

有没有办法可以将 int/decimal 值作为字符串传递 在我检查验证它们之前,然后将它们作为正确的传回 形式?

有点。 Transform 允许您进行转换以进行验证:

Transform(v => v.Cost, v => Convert.ToDecimal(v)
            // validation rules

虽然这不会返回转换后的值,您可能需要使用dependent rules 来确保可以执行转换,例如,检查是否为空或空白然后进行转换。

我的想法是退后一步,考虑一下方法。这是验证问题还是模型绑定问题?考虑 ASP.NET 中的 http 请求管道如何处理相同的场景。后者会将其视为模型绑定错误。如果您在调用 FluentValidation 之前先进行这些转换,那么您可以将验证器限定为强类型模型,而不会出现此问题。

如果您想在 FluentValidation 中完成所有这些工作 - 这样您就可以获得输入的单一合并错误源 - 那么感觉就像您拥有一个具有字符串属性的输入模型。验证输入模型,如果验证通过,则可以将输入模型映射到您的服务/数据/任何模型,并在其中包含类型转换。 FluentValidation 是关于确保模型有效,而不是验证和丰富模型。

如果您想坚持验证强类型模型,那么您需要在验证器之外进行转换。

考虑您的级联用法 (can be set at the validator/global level rather than for each rule) 并使用 custom language manager 以避免为每个验证器指定相同的自定义消息。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2022-01-23
    • 2013-08-22
    相关资源
    最近更新 更多