是否可以将模型属性用作范围验证属性的一部分

本文关键字:属性 范围 验证 一部分 模型 是否 | 更新日期: 2023-09-27 18:36:01

在我的模型中,我有一个具有以下属性的对象。

[Range(typeof(int), "2014", "2024", ErrorMessage = "{0} can only be beteween {1} and {2}")]
public int FiscalYear { get; set; }

下限和上限值分别为 2014 年和 2024 年。但是,我希望它们基于模型中的另一个属性,而不是使用这些固定值。

因此,例如,如果我有一个属性,CurrentFiscalYear,我假设的Range属性将如下所示。

[Range(typeof(int), CurrentFiscalYear, CurrentFiscalYear + 10, ErrorMessage = "{0} can only be beteween {1} and {2}")]
public int FiscalYear { get; set; }

这样的事情可能吗?还是必须在编译时提供下限值和上限值?

是否可以将模型属性用作范围验证属性的一部分

不,这是不可能的。属性参数值只是"编译时常量"值。换句话说,编译程序时必须知道参数的实际值。

从 MSDN - 属性教程:

属性参数限制为以下类型的常量值:

  • 简单类型(布尔值、字节、字符、短整型、整型、长整型、浮点型和双精度型)
  • 字符串
  • 系统类型。
  • 枚举
  • 对象
  • (对象类型的属性参数的参数必须是上述类型之一的常量值。
  • 上述任何类型的一维数组

这是 .NET 1.1 的文档,但未更改。

解决方法

这根本不经过测试,但您可以创建一个自定义ValidationAttribute,该该采用范围,并对属性名称进行建模,这些属性名称在测试有效性时要添加到范围值中。您可以创建一个内部标准RangeAttribute为您完成工作,甚至可以通过实现IClientValidatable来保持客户端验证工作:

public sealed class ShiftedRangeAttribute : ValidationAttribute
{
    public string MinShiftProperty { get; private set; }
    public string MaxShiftProperty { get; private set; }
    public double Minimum { get; private set; }
    public double Maximum { get; private set; }
    public ShiftedRangeAttribute(double minimum, double maximum, string minShiftProperty, string maxShiftProperty)
    {
        this.Minimum = minimum;
        this.Maximum = maximum;
        this.MinShiftProperty = minShiftProperty;
        this.MaxShiftProperty = maxShiftProperty;
    }
    public ShiftedRangeAttribute(int minimum, int maximum, string minShiftProperty, string maxShiftProperty)
    {
        this.Minimum = minimum;
        this.Maximum = maximum;
        this.MinShiftProperty = minShiftProperty;
        this.MaxShiftProperty = maxShiftProperty;
    }
    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        RangeAttribute attr = this.CreateRangeAttribute(validationContext.ObjectInstance);
        return attr.GetValidationResult(value, validationContext);
    }
    internal RangeAttribute CreateRangeAttribute(object model)
    {
        double min = this.Minimum;
        if (this.MinShiftProperty != null)
        {
            min += Convert.ToDouble(model.GetType().GetProperty(this.MinShiftProperty).GetValue(model));
        }
        double max = this.Maximum;
        if (this.MaxShiftProperty != null)
        {
            max += Convert.ToDouble(model.GetType().GetProperty(this.MaxShiftProperty).GetValue(model));
        }
        return new RangeAttribute(min, max);
    }
}

如果希望它与客户端验证一起使用,则还需要创建一个DataAnnotationsModelValidator并将其注册到 global.asax Application_Start()中,以确保输出客户端验证 HTML 属性。同样,你可以作弊并使用内置RangeAttributeAdapter来帮助你,因为在Javascript中,它最终只是一个范围验证器:

public class ShiftedRangeAttributeAdapter : DataAnnotationsModelValidator<ShiftedRangeAttribute>
{
    public ShiftedRangeAttributeAdapter(ModelMetadata metadata, ControllerContext context, ShiftedRangeAttribute attribute)
        : base(metadata, context, attribute)
    {
    }
    public override IEnumerable<ModelClientValidationRule> GetClientValidationRules()
    {
        RangeAttribute attr = this.Attribute.CreateRangeAttribute(this.Metadata.Container);
        return new RangeAttributeAdapter(this.Metadata, this.ControllerContext, attr).GetClientValidationRules();
    }
}
...
DataAnnotationsModelValidatorProvider.RegisterAdapter(
    typeof(ShiftedRangeAttribute), typeof(ShiftedRangeAttributeAdapter));

请注意,仅当包含属性的类是存储在 Metadata.Container 中的顶级模型类时,客户端验证代码才有效。您无法访问当前属性的"父级"。您需要做更多的工作来创建自定义 jQuery 验证器才能正确处理此问题。

然后,您可以按如下方式使用它:

[ShiftedRange(0, 10, "CurrentFiscalYear", "CurrentFiscalYear", ErrorMessage = "{0} can only be beteween {1} and {2}")]
public int FiscalYear { get; set; }

编辑:测试后修复了一些错误

这可以通过编写自定义 ValidationAttribute 来完成,实现可以像这样完成:

public sealed class FiscalYearAttribute : ValidationAttribute
{
    public string CurrentFiscalYear { get; set; }
    public override bool IsValid(object value)
    {
        var currentFiscalYearString = HttpContext.Current.Request[CurrentFiscalYear];
        var currentFiscalYear = int.Parse(currentFiscalYearString);
        var fiscalYear = (int) value;
        return fiscalYear >=  currentFiscalYear && fiscalYear <= currentFiscalYear + 10;
    }
    public override string FormatErrorMessage(string name)
    {
        return name + " error description here.";
    }
}

用法:

[Required]
[Display(Name = "CurrentFiscalYear")]
public int CurrentFiscalYear { get; set; }
[Display(Name = "FiscalYear")]
[FiscalYear(CurrentFiscalYear = "CurrentFiscalYear")]
public int FiscalYear { get; set; }