自定义验证属性未在模型上验证 - WebAPI C# 和 JSON

Posted

技术标签:

【中文标题】自定义验证属性未在模型上验证 - WebAPI C# 和 JSON【英文标题】:Custom Validation Attribute not validating on Model - WebAPI C# and JSON 【发布时间】:2020-11-04 00:08:32 【问题描述】:

我创建了一个模型和一个自定义验证属性,以确保该值大于零。我遇到的问题是自定义属性的构造函数被命中,但 IsValid 覆盖从未被命中。

[AttributeUsage(AttributeTargets.Property, AllowMultiple = true)]
public class GreaterThanZeroAttribute : ValidationAttribute

    /// <summary>
    /// Ensures that the value is greater than zero.
    /// </summary>
    public GreaterThanZeroAttribute()
        : base("The value 0 must be greater than 0.")
    
    

    public override bool IsValid(object value)
    
        bool isValueLong = long.TryParse(value?.ToString(), out long longValue);

        if (isValueLong && longValue > 0)
            return true;
        else
            return false;
    

public class ApplicationModel

    [Required]
    public string Name  get; set; 

    [GreaterThanZero]
    public IEnumerable<string> AssignedUserGroupIDs  get; set; 

现在,客户端将在 JSON 中设置 AssignedUserGroupIDs。我想验证该集合中的每个 ID 是否大于零。

这是请求的示例 JSON:

 
    "name": "Test Application",
    "assignedUserGroupIDs": [ "1", "-1001" ] 
 

对于它的价值,我使用 JsonOptions 来使用驼峰式大小写作为属性名称并将字符串转换为枚举。我之所以提到这一点,是因为我不确定 Json 是否会导致任何问题。

public static IServiceCollection AddWebApiServices(this IServiceCollection services)

    _ = services ?? throw new ArgumentNullException(nameof(services));

    Setup.AddServices(services);
    services.AddMvcCore(ConfigureDefaults)
        .AddJsonFormatters()
        .AddJsonOptions(options =>
        
            options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
            options.SerializerSettings.Converters.Add(new StringEnumConverter());
        )
        .AddCors("*")
        .AddControllers();

    return services;

这里是控制器:

        [HttpPost]
        [Route("applications")]
        [EnableCors(PolicyName = CorsPolicies.Default)]
        public IActionResult CreateApplication([FromBody] ApplicationModel postModel)
        
            List<long> assignedUserGroups = new List<long>();

            foreach (string stringUserGroupID in postModel.AssignedUserGroupIDs)
            
                assignedUserGroups.Add(Convert.ToInt64(stringUserGroupID));
            
            
            // business logic here...

            return new JsonResult(applicationID);
        

【问题讨论】:

【参考方案1】:

如果要验证集合中的每个 ID 是否大于零,则需要将 value 强制转换为 IEnumerable,然后循环遍历列表中的每个项目:

/*
 * Changes:
 * 1. Rename to CollectionGreaterThanZero to better reflect this validation is
 *    against a collection.
 * 2. Change AttributeUsage to include Field, as there is a backing field behind
 *    AssignedUserGroupIDs  get; set; , and when the data comes in, the backing
 *    field gets set. That's when you want to run your validation.
 * 3. Set AllowMultiple=false as there is no point to run this validation multiple
 *    times on the same property or field.
*/

[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field,  AllowMultiple = false)]
public class CollectionGreaterThanZeroAttribute : ValidationAttribute

    public override bool IsValid(object value)
    
        // This will case the value to IEnumerable silently.
        var enumerable = value as IEnumerable;
        if (enumerable != null)
        
            foreach (var item in enumerable)
            
                bool isLong = long.TryParse(item?.ToString(), out long longValue);
                if (!isLong)
                
                    return false;
                
            

            // I don't know if you consider an empty collection as a valid 
            // collection or not in your domain.
            return true;
        

        return false;
    

显示它被击中的屏幕截图:

【讨论】:

现在,客户端将在 JSON 中设置 AssignedUserGroupID。我想验证该集合中的每个 ID 是否大于零。这是请求的示例 JSON:``` "name": "Test Application", "assignedUserGroupIDs": [ "1", "-1001" ] ``` 我真的很喜欢这种方法。谢谢你。我添加了这个,但是当我在方法的第一行放置一个断点时,它永远不会被击中。这让我认为验证实际上并没有发生。有趣的是,如果我添加一个构造函数,它就会被命中。只是不覆盖。 @dn238608:所以客户正在将数据发回给您(因为您的 OP 中并不清楚)?如果是这种情况,我建议将属性用法也更改为包含 Field,因为public IEnumerable&lt;string&gt; AssignedUserGroupIDs get; set; 后面有一个支持字段,并且当客户端将数据发回给您时,支持字段被设置。您希望将此验证应用于支持字段。我还更改了AllowMultiple=false,因为没有必要在同一个属性或字段上多次应用此验证。请再次查看我的更新。 抱歉,是的,客户(为了测试我正在使用邮递员)正在发回数据。如果值正确,它会发回我期望的值,但是,我将“-1001”作为值,这应该无法通过验证。即使添加了AttributeTargets.Property | AttributeTargets.Field,我仍然无法在 IsValid 方法中设置断点。 嗯,您可能还想发布您的控制器代码。

以上是关于自定义验证属性未在模型上验证 - WebAPI C# 和 JSON的主要内容,如果未能解决你的问题,请参考以下文章

剃须刀页面上自定义验证属性的 ASP.NET Core 客户端验证

如何使用数据注释为模型属性 WebApi .NET Core 添加布尔验证

使用数据注释的依赖属性的自定义模型验证

为什么未在formarray自定义验证值更改中设置错误

8.1 自定义验证属性

远程属性验证未在 Asp.Net MVC 中触发