使用 C# 和 Web API 的自定义必需属性,并将私有访问修饰符与验证上下文一起使用

Custom required attribute with C# & Web API and using private access modifier with validation context

我有以下自定义必需属性:

public class RequiredIfAttribute : RequiredAttribute
{
    private string _DependentProperty;
    private object _TargetValue;

    public RequiredIfAttribute(string dependentProperty, object targetValue)
    {
        this._DependentProperty = dependentProperty;
        this._TargetValue = targetValue;
    }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        var propertyTestedInfo = validationContext.ObjectType.GetProperty(this._DependentProperty);

        if (propertyTestedInfo == null)
        {
            return new ValidationResult(string.Format("{0} needs to be exist in this object.", this._DependentProperty));
        }

        var dependendValue = propertyTestedInfo.GetValue(validationContext.ObjectInstance, null);

        if (dependendValue == null)
        {
            return new ValidationResult(string.Format("{0} needs to be populated.", this._DependentProperty));
        }

        if (dependendValue.Equals(this._TargetValue))
        {
            var x = validationContext.ObjectType.GetProperty("_Mappings");

            var objectInstance = (Dictionary<object, string[]>)x.GetValue(validationContext.ObjectInstance, null);

            var isRequiredSatisfied = false;

            foreach (var kvp in objectInstance)
            {
                if (kvp.Key.Equals(this._TargetValue))
                {
                    foreach (var field in kvp.Value)
                    {
                        var fieldValue = validationContext.ObjectType.GetProperty(field).GetValue(validationContext.ObjectInstance, null);

                        if (fieldValue != null && field.Equals(validationContext.MemberName))
                        {
                            isRequiredSatisfied = true;
                            break;
                        }
                    }
                }
            }

            if (isRequiredSatisfied)
            {
                return ValidationResult.Success;
            }
            else
            {
                return new ValidationResult(string.Empty);
            }
        }
        else
        {
            // Must be ignored
            return ValidationResult.Success;
        }
    }
}

我想用它实现的是我想根据模型中的 属性 有条件地验证。它还需要足够通用,以便在多个模型上重复使用。当指定的 属性 具有特定值(我在属性中指定)时,自定义所需的验证需要匹配这些值。例如在这个模型中:

public class PaymentModel
{
    public Dictionary<object, string[]> _Mappings 
    {
        get
        {
            var mappings = new Dictionary<object, string[]>();

            mappings.Add(true, new string[] { "StockID" });
            mappings.Add(false, new string[] { "Amount" });

            return mappings;
        }
    }

    [Required]
    public bool IsBooking { get; set; }

    [RequiredIfAttribute("IsBooking", false)]
    public decimal? Amount { get; set; }

    [RequiredIf("IsBooking", true)]
    public int? StockID { get; set; }

    public PaymentModel()
    {

    }
}

如果IsBooking属性是true,那么我希望StockId是必需的,但是如果是false,那么Amount 应该是必需的。

目前我的解决方案有效,但有两个问题:

  1. 存在对 _Mappings 属性 的依赖,我不希望有。有谁知道我将如何解决我的问题?
  2. 如果我必须按原样使用 _Mappings 属性,有没有办法将它用作 private 访问修饰符?目前我只能在 _Mappingspublic 时使我的解决方案有效,因为 validationContext.ObjectType.GetProperty("_Mappings") 找不到 private 修饰符。 (如果我想在 Web API 响应中将该模型序列化为 JSON,那么我最好不要发送我的验证映射。)

您可以将 _Mappings 设为私有。改变这个:

var propertyTestedInfo = validationContext.ObjectType.GetProperty("_Mappings");

var propertyTestedInfo = validationContext.ObjectType.GetProperty(
    "_Mappings", 
    BindingFlags.NonPublic | BindingFlags.Instance);

要用这个overload获取私人财产

public PropertyInfo Type.GetProperty(string name, BindingFlags bindingAttr)

此外,如果我从您当前的代码中正确地理解了您的意图(并假设您的意思是 "StockID",它说 "ReleasedStockID" 在当前 _Mappings 词典中)。

public class RequiredIfAttribute : RequiredAttribute
{
    private const BindingFlags Flags = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public;
    private string _DependentProperty;
    private object _TargetValue;

    public RequiredIfAttribute(string dependentProperty, object targetValue)
    {
        this._DependentProperty = dependentProperty;
        this._TargetValue = targetValue;
    }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        // Property info for the specified dependent property.
        var propertyTestedInfo = validationContext.ObjectType.GetProperty(this._DependentProperty, Flags);
        if (propertyTestedInfo == null)
            return new ValidationResult(string.Format("{0} needs to be exist in this object.", this._DependentProperty));

        // And its value
        var dependendValue = propertyTestedInfo.GetValue(validationContext.ObjectInstance, null);
        if (dependendValue == null)
            return new ValidationResult(string.Format("{0} needs to be populated.", this._DependentProperty));

        // If it meets the specified "If" predicate value
        if (dependendValue.Equals(this._TargetValue))
        {
            // Get the property being validated.
            var validatedProperty = validationContext.ObjectType.GetProperty(validationContext.MemberName, Flags);
            if (validatedProperty != null)
            {
                // Debug sanity check
                AssertHasThisAttribute(validatedProperty);

                // Get the property's value.
                var validatedPropertyValue = validatedProperty.GetValue(validationContext.ObjectInstance, null);

                // And check that is is not null
                if (validatedPropertyValue != null)
                    return ValidationResult.Success;
            }
            // validation failed.
            return new ValidationResult(string.Empty);
        }

        // Must be ignored
        return ValidationResult.Success;
    }

    // Debug only sanity check.
    [Conditional("DEBUG")]
    private void AssertHasThisAttribute(PropertyInfo prop)
    {
        var attr = prop.GetCustomAttributes<RequiredIfAttribute>().FirstOrDefault();
        Debug.Assert(attr != null);
        Debug.Assert(attr._TargetValue == _TargetValue);
        Debug.Assert(attr._DependentProperty == _DependentProperty);
    }
}

您不必使用 _Mappings 属性,下面的代码会检查相关属性的值是否与您在属性中指定的值匹配,如果匹配则检查以查看如果您正在验证的 属性 有一个值。

protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
    var propertyTestedInfo = validationContext.ObjectType.GetProperty(this._DependentProperty);

    if (propertyTestedInfo == null)
    {
        return new ValidationResult(string.Format("{0} needs to be exist in this object.", this._DependentProperty));
    }

    var dependendValue = propertyTestedInfo.GetValue(validationContext.ObjectInstance, null);

    if (dependendValue == null)
    {
        return new ValidationResult(string.Format("{0} needs to be populated.", this._DependentProperty));
    }

    if (dependendValue.Equals(this._TargetValue))
    {
        var fieldValue = validationContext.ObjectType.GetProperty(validationContext.MemberName).GetValue(validationContext.ObjectInstance, null);


        if (fieldValue != null)
        {
            return ValidationResult.Success;
        }
        else
        {
            return new ValidationResult(string.Format("{0} cannot be null", validationContext.MemberName));
        }
    }
    else
    {
        // Must be ignored
        return ValidationResult.Success;
    }
}