1

I'm doing a ASP.NET MVC course. I'm building a REST Web API using ASP.NET WebAPI 2. The application also contains standard MVC 5 views. I'm using DTOs (Data Transfer Objects) to decouple the API from the data model. I've made a custom ValidationAttribute that I have applied to a property in my data model, and I'd like to use the same Validation attribute for a property on my DTO as well as a property ViewModel used in an MVC view.

This requires casting the ValidationContext.ObjectInstance to the right type. I have found a simple solution, but I don't find it very elegant, and I'd like to know if there is a better way to do this.

The specific ValidationAttribute and property I'm talking about:

[Min18YearsIfAMember]
public DateTime? DateOfBirth { get; set; }

In the context of the solution (some details removed for brevity including CustomerViewModel):

public class Customer
{
    public int Id { get; set; }
    public string Name { get; set; }
    public MembershipType MembershipType { get; set; }
    public byte MembershipTypeId { get; set; }

    [Min18YearsIfAMember]
    public DateTime? DateOfBirth { get; set; }
}    


public class CustomerDto
{
    public int Id { get; set; }
    public string Name { get; set; }
    public byte MembershipTypeId { get; set; }

    [Min18YearsIfAMember]
    public DateTime? DateOfBirth { get; set; }  
}

public class Min18YearsIfAMemberAttribute : ValidationAttribute
{
    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        // Check it here
        var customer = validationContext.ObjectInstance as Customer;
        if (customer != null)
            return DoValidation(customer.MembershipTypeId, customer.DateOfBirth);

        // Check it here
        var customerVm = validationContext.ObjectInstance as CustomerViewModel;
        if (customerVm  != null)
            return DoValidation(customerVm.MembershipTypeId, customerVm.DateOfBirth);

        // Yes I should probably check it here too
        var customerDto = validationContext.ObjectInstance as CustomerDto;
            return DoValidation(customerDto.MembershipTypeId, customerDto.DateOfBirth);
    }

    private ValidationResult DoValidation( int membershipTypeId, DateTime? DateOfBirth)
    { 
        // Do the validation....
    }
}

It's readable, but I find it ugly having to check each possible case like so ValidationContext.ObjectInstance as Customer.

Is there a better way?

Phil G
  • 69
  • 1
  • 9

1 Answers1

2

In the data annotation attribute, you can specify the dependent property while attaching the attribute and using that you can validate the property for object types:

public class Min18YearsIfAMemberAttribute : ValidationAttribute
{
    private string _dependentProperty { get; set; }

    public Min18YearsIfAMemberAttribute(string dependentProperty)
    {
        this._dependentProperty = dependentProperty;
    }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        var field = validationContext.ObjectType.GetProperty(_dependentProperty);
        if (field != null)
        {
            var dependentValue = (byte)field.GetValue(validationContext.ObjectInstance, null);
            
            return DoValidation(dependentValue, (DateTime?)value);
        }
        else
        {
            return new ValidationResult("<Your message here>");
        }
    }

    private ValidationResult DoValidation( int membershipTypeId, DateTime? DateOfBirth)
    { 
        // Do the validation....
    }

Now while attaching the attribute we specify the dependent property name [Min18YearsIfAMember("MembershipTypeId").

public class Customer
{
    public int Id { get; set; }
    public string Name { get; set; }
    public MembershipType MembershipType { get; set; }
    public byte MembershipTypeId { get; set; }

    [Min18YearsIfAMember(nameof(MembershipTypeId))]
    public DateTime? DateOfBirth { get; set; }
}  
user1672994
  • 10,509
  • 1
  • 19
  • 32
  • That's great! I like the way this solution would allow us to use dependent properties with different names. However, I know I didn't state performance being a requirement, and correct me if I'm wrong, but I think calling `validationContext.ObjectType.GetProperty` would probably be slower than using the `as` operator to attempt to perform type conversion. – Phil G Jan 07 '21 at 10:48
  • 1
    You can catch the output of validationContext.ObjectType.GetProperty value in static dictionary based on type of `validationContext.ObjectInstance`. However `field.GetValue` is also being used to retrieve the value via reflection which can't be cached... You can run the benchmarking test to figure out if it would really be concern, – user1672994 Jan 07 '21 at 10:54
  • Am I missing something here? You have `var dependentValue = (byte)...` then check `dependentValue` for null - but it can't be null because it's a value type. – Wai Ha Lee Jan 07 '21 at 12:27
  • @WaiHaLee you are right. I have suggested a few changes but they are not yet approved. – Phil G Jan 07 '21 at 13:57
  • @WaiHaLee - Yes, my mistake, I've updated the code – user1672994 Jan 07 '21 at 14:12