27

I just want to put conditional Required Attribute which is work with WEB API

Example

public sealed class EmployeeModel
{
      [Required]
      public int CategoryId{ get; set; }
      public string Email{ get; set; } // If CategoryId == 1 then it is required
}

I am using Model State validation via (ActionFilterAttribute)

Shubhajyoti Ghosh
  • 1,292
  • 5
  • 27
  • 45

2 Answers2

54

You can implement your own ValidationAttribute. Perhaps something like this:

public class RequireWhenCategoryAttribute : ValidationAttribute
{
    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        var employee = (EmployeeModel) validationContext.ObjectInstance;
        if (employee.CategoryId == 1)
            return ValidationResult.Success;

        var emailStr = value as string;
        return string.IsNullOrWhiteSpace(emailStr)
            ? new ValidationResult("Value is required.")
            : ValidationResult.Success;
    }
}

public sealed class EmployeeModel
{
    [Required]
    public int CategoryId { get; set; }
    [RequireWhenCategory]
    public string Email { get; set; } // If CategoryId == 1 then it is required
}

This is just a sample. It may have casting issues, and I'm not sure this is the best approach to solve this problem.

Ian Kemp
  • 28,293
  • 19
  • 112
  • 138
vcsjones
  • 138,677
  • 31
  • 291
  • 286
  • "*I'm not sure this is the best approach to solve this problem.*" What would be some other approaches to solve the problem? – Scott Chamberlain Dec 17 '13 at 19:08
  • @ScottChamberlain Excellent question! I don't know. I feel like this somehow is a leaky abstraction. The attribute doing the validation knows a lot about the model type, etc. Should this be done in via an attribute? It feels like the model knows too much about behavior and has lost a model's simplicity. Should the controller be doing it then? I'm still not sure. – vcsjones Dec 17 '13 at 19:13
  • 3
    @ vcsjones : It is a good approach, actually I want to avoid validation logic in controller, because that's required lots of changes (as per change requirement), which I don't want to do. – Shubhajyoti Ghosh Dec 18 '13 at 06:48
  • 10
    @ShubhajyotiGhosh, @ScottChamberlain: To save your time and gain some flexibility, instead of creating custom validation attributes for each specific case, take a look at [ExpressiveAnnotations](https://github.com/JaroslawWaliszko/ExpressiveAnnotations). By using it in this case, you can annotate `Email` field with the following attribute: `[RequiredIf("CategoryId == 1")]`. – jwaliszko Sep 03 '15 at 12:52
  • I don't understand whats wrong with this solution. @ScottChamberlain Its what I immediately thought if. I just prefer it to adding Modelstate errors in the controller if something is not set... – Andy Nov 05 '15 at 11:53
  • 2
    Actually this comment is wrong: If CategoryId == 1 then it is NOT required – bombek Mar 05 '20 at 10:30
  • 1
    Fanatastic answer. Saved my life! Thanks. – leighhydes Mar 08 '20 at 15:48
6

Here's my 2 cents. It will give you a nice message like "AssigneeId is required for the current AssigneeType value Salesman" It works for enums too.

[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)]
public class RequiredForAnyAttribute : ValidationAttribute
{
    /// <summary>
    /// Values of the <see cref="PropertyName"/> that will trigger the validation
    /// </summary>
    public string[] Values { get; set; }

    /// <summary>
    /// Independent property name
    /// </summary>
    public string PropertyName { get; set; }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        var model = validationContext.ObjectInstance;
        if (model == null || Values == null)
        {
            return ValidationResult.Success;
        }

        var currentValue = model.GetType().GetProperty(PropertyName)?.GetValue(model, null)?.ToString();
        if (Values.Contains(currentValue) && value == null)
        {
            var propertyInfo = validationContext.ObjectType.GetProperty(validationContext.MemberName);
            return new ValidationResult($"{propertyInfo.Name} is required for the current {PropertyName} value {currentValue}");
        }
        return ValidationResult.Success;
    }
}

Use it like this

public class SaveModel {
    [Required]
    public AssigneeType? AssigneeType { get; set; }

    [RequiredForAny(Values = new[] { nameof(AssigneeType.Salesman) }, PropertyName = nameof(AssigneeType))]
    public Guid? AssigneeId { get; set; }
}
Kautsky Lozano
  • 722
  • 12
  • 21