自定义验证属性:比较同一模型中的两个属性

时间:2017-01-27 18:16:21

标签: c# validation asp.net-core

有没有办法在ASP.NET Core中创建自定义属性,以使用 ValidationAttribute 验证一个日期属性是否小于模型中的其他日期属性。

让我说我有这个:

public class MyViewModel 
{
    [Required]
    [CompareDates]
    public DateTime StartDate { get; set; }

    [Required]
    public DateTime EndDate { get; set; } = DateTime.Parse("3000-01-01");
}

我想尝试使用这样的东西:

    public class CompareDates : ValidationAttribute
{
    public CompareDates()
        : base("") { }

    public override bool IsValid(object value)
    {
        return base.IsValid(value);
    }

}

我发现其他SO帖子建议使用另一个库,但如果可行,我更愿意坚持使用ValidationAttribute。

7 个答案:

答案 0 :(得分:30)

您可以为比较两个属性创建自定义验证属性。它是服务器端验证:

public class MyViewModel
{
    [DateLessThan("End", ErrorMessage = "Not valid")]
    public DateTime Begin { get; set; }

    public DateTime End { get; set; }
}

public class DateLessThanAttribute : ValidationAttribute
{
    private readonly string _comparisonProperty;

    public DateLessThanAttribute(string comparisonProperty)
    {
         _comparisonProperty = comparisonProperty;
    }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        ErrorMessage = ErrorMessageString;
        var currentValue = (DateTime)value;

        var property = validationContext.ObjectType.GetProperty(_comparisonProperty);

        if (property == null)
            throw new ArgumentException("Property with this name not found");

        var comparisonValue = (DateTime)property.GetValue(validationContext.ObjectInstance);

        if (currentValue > comparisonValue)
            return new ValidationResult(ErrorMessage);

        return ValidationResult.Success;
    }
}

<强>更新: 如果您需要对此属性进行客户端验证,则需要实现IClientModelValidator接口:

public class DateLessThanAttribute : ValidationAttribute, IClientModelValidator
{
    ...
    public void AddValidation(ClientModelValidationContext context)
    {
        var error = FormatErrorMessage(context.ModelMetadata.GetDisplayName());
        context.Attributes.Add("data-val", "true");
        context.Attributes.Add("data-val-error", error);
    }
}

AddValidation方法会从context.Attributes为您的输入添加属性。

enter image description here

您可以在此处阅读更多内容IClientModelValidator

答案 1 :(得分:9)

作为一种可能的选择自我验证

您只需要使用方法IValidatableObject实现接口Validate,您可以在其中放置验证码。

public class MyViewModel : IValidatableObject
{
[Required]
public DateTime StartDate { get; set; }

[Required]
public DateTime EndDate { get; set; } = DateTime.Parse("3000-01-01");

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        int result = DateTime.Compare(StartDate , EndDate);
        if (result < 0)
        {
            yield return new ValidationResult("start date must be less than the end date!", new [] { "ConfirmEmail" });
        }
    }
}

答案 2 :(得分:3)

基于Alexander Gore的响应,我建议进行更好的通用验证(它与.Net核心兼容)。当您想使用GreatherThan或LessThan逻辑(无论类型如何)比较属性时,可以验证它们是否实现了IComparable接口。如果两个属性均有效,则可以使用CompareTo实现。此规则也适用于DateTime和数字类型

小于

[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter)]
public class LessThanAttribute : ValidationAttribute
{
    private readonly string _comparisonProperty;

    public LessThanAttribute(string comparisonProperty)
    {
        _comparisonProperty = comparisonProperty;
    }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        ErrorMessage = ErrorMessageString;

        if (value.GetType() == typeof(IComparable))
        {
            throw new ArgumentException("value has not implemented IComparable interface");
        }

        var currentValue = (IComparable)value;

        var property = validationContext.ObjectType.GetProperty(_comparisonProperty);

        if (property == null)
        {
            throw new ArgumentException("Comparison property with this name not found");
        }

        var comparisonValue = property.GetValue(validationContext.ObjectInstance);

        if (comparisonValue.GetType() == typeof(IComparable))
        {
            throw new ArgumentException("Comparison property has not implemented IComparable interface");
        }

        if (!ReferenceEquals(value.GetType(), comparisonValue.GetType()))
        {
            throw new ArgumentException("The properties types must be the same");
        }

        if (currentValue.CompareTo((IComparable)comparisonValue) >= 0)
        {
            return new ValidationResult(ErrorMessage);
        }

        return ValidationResult.Success;
    }
}

大于

[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter)]
public class GreaterThanAttribute : ValidationAttribute
{
    private readonly string _comparisonProperty;

    public GreaterThanAttribute(string comparisonProperty)
    {
        _comparisonProperty = comparisonProperty;
    }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        ErrorMessage = ErrorMessageString;

        if (value.GetType() == typeof(IComparable))
        {
            throw new ArgumentException("value has not implemented IComparable interface");
        }

        var currentValue = (IComparable)value;

        var property = validationContext.ObjectType.GetProperty(_comparisonProperty);

        if (property == null)
        {
            throw new ArgumentException("Comparison property with this name not found");
        }

        var comparisonValue = property.GetValue(validationContext.ObjectInstance);

        if (comparisonValue.GetType() == typeof(IComparable))
        {
            throw new ArgumentException("Comparison property has not implemented IComparable interface");
        }

        if (!ReferenceEquals(value.GetType(), comparisonValue.GetType()))
        {
            throw new ArgumentException("The properties types must be the same");
        }

        if (currentValue.CompareTo((IComparable)comparisonValue) < 0)
        {
            return new ValidationResult(ErrorMessage);
        }

        return ValidationResult.Success;
    }
}

在预订环境中,示例如下:

public DateTime CheckInDate { get; set; }

[GreaterThan("CheckInDate", ErrorMessage = "CheckOutDate must be greater than CheckInDate")]
public DateTime CheckOutDate { get; set; }

答案 3 :(得分:2)

您可以比较IsValid方法中的两个日期。

public class CompareDates : ValidationAttribute
{
    protected override ValidationResult
            IsValid(object value, ValidationContext validationContext)
    {
        //get your startdate & end date from model and value


        //perform comparison
        if (StartDate < EndDate)
        {
            return new ValidationResult
                ("start date must be less than the end date");
        }
        else
        {
            return ValidationResult.Success;
        }
    }
}

答案 4 :(得分:0)

基于Jaime的回答和Jeffrey的评论,即需要小于,小于或等于,等于,大于,大于或等于的单个属性。

下面的代码将使用单个属性处理所有条件。

public enum ComparisonType
{
    LessThan,
    LessThanOrEqualTo,
    EqualTo,
    GreaterThan,
    GreaterThanOrEqualTo
}

[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter)]
public class ComparisonAttribute : ValidationAttribute
{
    private readonly string _comparisonProperty;
    private readonly ComparisonType _comparisonType;

    public ComparisonAttribute(string comparisonProperty, ComparisonType comparisonType)
    {
        _comparisonProperty = comparisonProperty;
        _comparisonType = comparisonType;
    }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        ErrorMessage = ErrorMessageString;

        if (value.GetType() == typeof(IComparable))
        {
            throw new ArgumentException("value has not implemented IComparable interface");
        }

        var currentValue = (IComparable) value;

        var property = validationContext.ObjectType.GetProperty(_comparisonProperty);

        if (property == null)
        {
            throw new ArgumentException("Comparison property with this name not found");
        }

        var comparisonValue = property.GetValue(validationContext.ObjectInstance);

        if (comparisonValue.GetType() == typeof(IComparable))
        {
            throw new ArgumentException("Comparison property has not implemented IComparable interface");
        }

        if (!ReferenceEquals(value.GetType(), comparisonValue.GetType()))
        {
            throw new ArgumentException("The properties types must be the same");
        }

        bool compareToResult;

        switch (_comparisonType)
        {
            case ComparisonType.LessThan:
                compareToResult = currentValue.CompareTo((IComparable) comparisonValue) >= 0;
                break;
            case ComparisonType.LessThanOrEqualTo:
                compareToResult = currentValue.CompareTo((IComparable) comparisonValue) > 0;
                break;
            case ComparisonType.EqualTo:
                compareToResult = currentValue.CompareTo((IComparable) comparisonValue) != 0;
                break;
            case ComparisonType.GreaterThan:
                compareToResult = currentValue.CompareTo((IComparable) comparisonValue) <= 0;
                break;
            case ComparisonType.GreaterThanOrEqualTo:
                compareToResult = currentValue.CompareTo((IComparable) comparisonValue) < 0;
                break;
            default:
                throw new ArgumentOutOfRangeException();
        }

        return compareToResult ? new ValidationResult(ErrorMessage) : ValidationResult.Success;
    }
}

在预订环境中,示例如下:

public DateTime CheckInDate { get; set; }

[Comparison("CheckInDate", ComparisonType.EqualTo, ErrorMessage = "CheckOutDate must be equal to CheckInDate")]
public DateTime CheckOutDate { get; set; }

答案 5 :(得分:0)

这是我的看法。我的版本忽略为空的属性(可选)。当应用于Web API时,它非常合适。

[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter)]
public class ComparisonAttribute : ValidationAttribute
{
    private readonly string _comparisonProperty;
    private readonly ComparisonType _comparisonType;

    public ComparisonAttribute(string comparisonProperty, ComparisonType comparisonType)
    {
        _comparisonProperty = comparisonProperty;
        _comparisonType = comparisonType;
    }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        ErrorMessage = ErrorMessageString;

        var property = validationContext.ObjectType.GetProperty(_comparisonProperty);
        if (property == null)
            throw new ArgumentException($"Property {_comparisonProperty} not found");

        var right = property.GetValue(validationContext.ObjectInstance);
        if (value is null || right is null)
            return ValidationResult.Success;

        if (value.GetType() == typeof(IComparable))
            throw new ArgumentException($"The property {validationContext.MemberName} does not implement {typeof(IComparable).Name} interface");

        if (right.GetType() == typeof(IComparable))
            throw new ArgumentException($"The property {_comparisonProperty} does not implement {typeof(IComparable).Name} interface");

        if (!ReferenceEquals(value.GetType(), right.GetType()))
            throw new ArgumentException("The property types must be the same");

        var left = (IComparable)value;
        bool isValid;

        switch (_comparisonType)
        {
            case ComparisonType.LessThan:
                isValid = left.CompareTo((IComparable)right) < 0;
                break;
            case ComparisonType.LessThanOrEqualTo:
                isValid = left.CompareTo((IComparable)right) <= 0;
                break;
            case ComparisonType.EqualTo:
                isValid = left.CompareTo((IComparable)right) != 0;
                break;
            case ComparisonType.GreaterThan:
                isValid = left.CompareTo((IComparable)right) > 0;
                break;
            case ComparisonType.GreaterThanOrEqualTo:
                isValid = left.CompareTo((IComparable)right) >= 0;
                break;
            default:
                throw new ArgumentOutOfRangeException();
        }

        return isValid
            ? ValidationResult.Success
            : new ValidationResult(ErrorMessage);
    }

    public enum ComparisonType
    {
        LessThan,
        LessThanOrEqualTo,
        EqualTo,
        GreaterThan,
        GreaterThanOrEqualTo
    }
}

答案 6 :(得分:0)

可能我来晚了!

我在ASP.NET Core中创建了一个包含最常见的自定义验证的库。 该库还具有针对所有服务器端自定义验证的客户端验证。该库通过以下单个属性解决了OP的问题:

[CompareTo(nameof(EndDate), ComparisionType.SmallerThan)] // If you want StartDate should be smaller than Endate.
public DateTime StartDate { get; set; }

这是该库的github链接:AspNetCore.CustomValidation

当前,该库包含以下验证属性:

1。 FileAttribute -验证文件类型,文件最大大小,文件最小大小

2。 MaxAgeAttribute -根据DateTime类型的生日值验证最大年龄

3。 MinAgeAttribute -用于根据DateTime类型的生日值验证最低年龄要求。

4。 MaxDateAttribute -为DateTime字段设置最大值验证。

5。 MinDateAttribute -为DateTime字段设置最小值验证。

6。 CompareToAttibute –将一个属性值与另一个属性值进行比较。

7。 TinyMceRequiredAttribute -在TinyMCE,CkEditor等在线文本编辑器上强制执行必需的验证属性。