How to selectively check data annotation attribute?

There are some properties in my view model that are optional when saved, but are required when submitted. In a word, we allow partial savings, but the whole form appears, we want to make sure that all required fields have values.

The only approaches I can imagine at this point:

Manipulate the ModelState Error Collection.

The view model has all the attributes [Required] . If the request is a partial save, ModelState.IsValid becomes false when the controller action is entered. Then I look through all the errors of ModelState (which is ICollection<KeyValuePair<string, ModelState>> ) and removes all errors caused by the [Required] properties.

But if the request is to represent the entire form, I will not interfere with the ModelState and [Required] attributes.

Use different viewing models to partially save and send

This is even more ugly. One view model will contain all the [Required] attributes used by the action method to submit. But for partial saving, I submit the form data to another action that uses the same presentation model without all the [Required] attributes.

Obviously, I will have many duplicate code / view models.

The perfect solution

I was thinking if I could create my own data annotation attribute [SubmitRequired] for these required properties. And somehow to do validation ignores it when partially saved, but not when sent.

There was still no clear clue. Can anybody help? Thanks.

+5
source share
3 answers

My approach is to add a conditional annotation check attribute, which is learned from foolproof .

Make SaveMode part of the view model.

Mark nullable properties so that values ​​are optional if SaveMode not Finalize .

But add the special annotation attribute [FinalizeRequired] :

 [FinalizeRequired] public int? SomeProperty { get; set; } [FinalizeRequiredCollection] public List<Item> Items { get; set; } 

Here is the code for the attribute:

 [AttributeUsage(AttributeTargets.Property)] public abstract class FinalizeValidationAttribute : ValidationAttribute { public const string DependentProperty = "SaveMode"; protected abstract bool IsNotNull(object value); protected static SaveModeEnum GetSaveMode(ValidationContext validationContext) { var saveModeProperty = validationContext.ObjectType.GetProperty(DependentProperty); if (saveModeProperty == null) return SaveModeEnum.Save; return (SaveModeEnum) saveModeProperty.GetValue(validationContext.ObjectInstance); } protected override ValidationResult IsValid(object value, ValidationContext validationContext) { var saveMode = GetSaveMode(validationContext); if (saveMode != SaveModeEnum.SaveFinalize) return ValidationResult.Success; return (IsNotNull(value)) ? ValidationResult.Success : new ValidationResult(string.Format("{0} is required when finalizing", validationContext.DisplayName)); } } 

For primitive data types, check value!=null :

 [AttributeUsage(AttributeTargets.Property)] public class FinalizeRequiredAttribute : FinalizeValidationAttribute { protected override bool IsNotNull(object value) { return value != null; } } 

For IEnumerable collections,

 [AttributeUsage(AttributeTargets.Property)] public class FinalizeRequiredCollectionAttribute : FinalizeValidationAttribute { protected override bool IsNotNull(object value) { var enumerable = value as IEnumerable; return (enumerable != null && enumerable.GetEnumerator().MoveNext()); } } 

This approach best fixes problems by removing validation logic from the controller. Data Annotation attributes must handle work for which the controller just needs to check !ModelState.IsValid . This is especially useful in my application because I could not reorganize to the base controller if the ModelState check in each controller is different.

0
source

This is one of the approaches that I use in projects.

Create a ValidationService<T> containing business logic that will verify that your model is in a valid state that will be sent using the IsValidForSubmission method.

Add the IsSubmitting property to the IsSubmitting model that you are checking before calling the IsValidForSubmission method.

Use only built-in validation attributes to validate invalid data, i.e. field lengths etc.

Create some custom attributes in a different namespace that will be checked in certain scenarios, i.e. [RequiredIfSubmitting] , and then use reflection inside your service to iterate over the attributes of each property and call their IsValid method manually (skip any that are not within your namespace).

This will populate and return a Dictionary<string, string> , which you can use to populate the ModelState back to the user interface:

 var validationErrors = _validationService.IsValidForSubmission(model); if (validationErrors.Count > 0) { foreach (var error in validationErrors) { ModelState.AddModelError(error.Key, error.Value); } } 
+2
source

I think there is a more accurate solution to your problem. Suppose you send one method, I want to say that you call the same method for Partial and Full submit. Then you should do as below:

  [HttpPost] [ValidateAntiForgeryToken] public ActionResult YourMethod(ModelName model) { if(partialSave) // Check here whether it a partial or full submit { ModelState.Remove("PropertyName"); ModelState.Remove("PropertyName2"); ModelState.Remove("PropertyName3"); } if (ModelState.IsValid) { } } 

This should solve your problem. Let me know if you have any problems.

Edit:

As @SBirthare commented that it is not possible to add or remove properties when updating the model, I found below a solution that should work for the [Required] attribute.

  ModelState.Where(x => x.Value.Errors.Count > 0).Select(d => d.Key).ToList().ForEach(g => ModelState.Remove(g)); 

Above, the code will receive all the keys that will have an error and remove them from the model state. You should put this line inside if the condition is to make sure that it is fulfilled in a partial submit form. I also checked that the error will only occur for the [Required] attribute (somehow the model binding giving this attribute high priority, even if you put it after / below any other attribute). Therefore, you no longer need to worry about model updates.

+1
source

All Articles