简体   繁体   中英

Custom Validation Attribute not validating on Model - WebAPI C# and JSON

I have created a Model and a custom validation attribute to ensure that the value is greater than zero. The issue that I'm having is that the constructor of the custom attribute is hit, but the IsValid override is never hit.

[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; }
}

Right now, the client will set the AssignedUserGroupIDs in JSON. I'd like to validate that each of the IDs in that collection are greater than zero.

Here is sample JSON for the request:

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

For what it's worth, I'm using JsonOptions to use camel casing for the property names and to covert string to enums. I'm only mentioning this because I'm not sure if the Json is causing any issues or not.

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;
}

Here is the Controller:

        [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);
        }

If you would like to validate each of the IDs in the collection is greater than zero, then you need to cast the value as IEnumerable , and then loop through each item in the list:

/*
 * 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;
    }
}

Screenshot to show it's being hit:

在此处输入图像描述

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM