Ruslan
Ruslan

Reputation: 10147

MVC .NET [Required] DataAnnotations on a parent model causing invalid ModelState in a child model

I would like to "turn off" the Required field validation on a certain property of the parent model (VehicleManufacturer), when saving a child model (VehicleModel), i.e.:

public class VehicleManufacturer
{
    public virtual Guid Id { get; set; }

    [Required]
    [StringLength(50, MinimumLength = 1)]
    public virtual string Name { get; set; }
}

public class VehicleModel
{
    public virtual Guid Id { get; set; }

    [Required]
    [StringLength(50, MinimumLength = 1)]
    public virtual string Name { get; set; }

    public virtual VehicleManufacturer Manufacturer { get; set; }
}

So, when I'm saving a new model, all I care about is it's Name and ManufacturerID, which would be selected from a drop-down list, however, because the ManufacturerName is marked [Required] in its entity, it invalidates my ModelState when saving a new VehicleModel, because ManufacturerName is null :(

I would like to know what is the best approach to this and how to do it. I can think of a few solutions but none seem to be the right way:

what do you think?

Upvotes: 0

Views: 2668

Answers (5)

Yablargo
Yablargo

Reputation: 3596

Ok. I looked at this and realized my method was wasteful.

How does this look?

I created an Annotation called ExtendedValidationRequired.

It has a static value that turns on conditional checking for extended values or not.

[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false)]

public class ExtendedValidationRequiredAttribute : RequiredAttribute
{



    // Summary:
    //     Initializes a new instance of the System.ComponentModel.DataAnnotations.RequiredAttribute
    //     class.
    public ExtendedValidationRequiredAttribute()
    {

    }



    // Summary:
    //     Checks that the value of the required data field is not empty.
    //
    // Parameters:
    //   value:
    //     The data field value to validate.
    //
    // Returns:
    //     true if validation is successful; otherwise, false.
    //
    // Exceptions:
    //   System.ComponentModel.DataAnnotations.ValidationException:
    //     The data field value was null.
    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        if (ExtendedValidation.IsExtendedValidationEnabled(validationContext.ObjectType))
        {

            return base.IsValid(value,validationContext);
        }
        else
        {
            return ValidationResult.Success;                
        }


    }




}

I then mark up my Sometimes required (eg when im directly editing that class) fields with [ExtendedValidationRequired(typeof(MyClassName))]

this works for other types of validators as well.

I actually went ahead and created a set of 'Sometimes On' validators and moved my setting to a separate class: public static class ExtendedValidation { private static Dictionary extendedValidationExemptions = new Dictionary();

    /// <summary>
    /// Disable extended validation for a specific type
    /// </summary>
    /// <param name="type"></param>
    public static void DisableExtendedValidation(Type type)
    {
        extendedValidationExemptions[type] = true;
    }
    /// <summary>
    /// Clear any EV exemptions
    /// </summary>
    public static void Reset()
    {
        extendedValidationExemptions.Clear();
    }

    /// <summary>
    /// Check if a class should perform extended validation
    /// </summary>
    /// <param name="type"></param>
    /// <returns></returns>
    public static bool IsExtendedValidationEnabled(Type type)
    {
        if (extendedValidationExemptions.ContainsKey(type))
        {
            return false;
        }
        else
        {
            return true;
        }
    }
}

}

now i just turn ExtendedValidation off when editing children.

E.G: When editing a Child, Can DisableExtendedValidation(typeof(Parent)) and not get in the way.

Edit: Hrm, I realize this doesnt quite work. -- Can I determine what class I am looking at inside of a validationProperty? I guess I could always pass the parent property in but that is a PITA

Upvotes: 0

Yablargo
Yablargo

Reputation: 3596

What I ended up doing is subclassing the parent model with something like ParentWithExtendedValidation : Parent

In ParentWithExtendedValidation all of the various [Required] Fields were tagged.

When I wanted to specifically edit the parent, I used type ParentWithExtendedValidation -- since it is a subclass of Parent, once it passes model validation, you can cast it back to parent and pass it to your DAL with no issues.

When you are using it in a relationship e.g editing a child that references this, you can use the regular Parent class.

Hope this helps

EG I have a class Person -- with all of my normal virtual properties, and only the ID is required (to make validation that selects on person->ID still work right)

And a Class PersonEV

public class PersonWithExtendedValidation : Person
{

    [Required]
    public override string FullName { get; set; }
    [Required]
    public override string FirstName { get; set; }
    [Required]
    public override string LastName { get; set; }        
    [Required]
    public override string DomainName { get; set; }
    [Required]
    public override string WorkPhone { get; set; }
    [Required]
    public override string CellPhone { get; set; }
    [Required]
    public override string Email { get; set; }        
}

Then in your View/Controller work with e.g PersonEV for the model and argument.

Once you check for ModelState.IsValid , cast back to (Person) and pass to whatever repository or etc as normal

Upvotes: 0

Wim
Wim

Reputation: 1985

A possible solution is to add the foreign key column to the VehicleManufacturer (VehicleManufacturerId) to the VehicleModel and use that column in your view.

Upvotes: 1

Andrew
Andrew

Reputation: 5430

The IValidatableObject interface is for custom model validation.

For example:

public class VehicleModel : IValidatableObject
{
    public virtual Guid Id { get; set; }

    [StringLength(50, MinimumLength = 1)]
    public virtual string Name { get; set; }

    public virtual VehicleManufacturer Manufacturer { get; set; }

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        if(string.IsNullOrWhiteSpace(Name))
        {
           yield return new ValidationResult("Name is required")
        } 
    }
}

Then in your controller call ModelState.IsValid

Upvotes: 0

Daniel Hilgarth
Daniel Hilgarth

Reputation: 174299

The simplest way is to have hidden fields for the required properties you don't want to show.

Upvotes: 2

Related Questions