Scenario: Address.City is required when Address.Country's value is "USA".
We can achieve this by following below steps, and the illustration is developed using MVC 5, Web API 2, EF 6 and Mock
As a first step, Create a new class with name "RequiredIfValidator" (preferably in a common location of the solution ie. either in Model or a Common project, as applied) and copy below code:
public class RequiredIfValidator : ValidationAttributeWe need to create or make changes to Model(s) and Controller(s) as below to implement the created custom validation.
{
private RequiredAttribute _innerAttribute = new RequiredAttribute();public string DependentProperty { get; set; }public object TargetValue { get; set; }
public RequiredIfValidator(string dependentProperty, object targetValue){this.DependentProperty = dependentProperty;
this.TargetValue = targetValue; }
protected override ValidationResult IsValid(object value, ValidationContext validationContext){
// get a reference to the property this validation depends upon var containerType = validationContext.ObjectInstance.GetType(); var field = containerType.GetProperty(this.DependentProperty);
if (field != null)
{
// get the value of the dependent property
object dependentValue = field.GetValue(validationContext.ObjectInstance, null);
// trim spaces and convert dependent value to uppercase to support case senstive comparison
if (dependentValue != null && dependentValue is string)
{
dependentValue = (dependentValue as string).Trim();
dependentValue as string).Length == 0 ? null : (dependentValue as string).ToUpper();
}
// trim spaces and convert TargetValue to uppercase to support case senstive comparison
if (TargetValue != null && TargetValue is string)
{
TargetValue = (TargetValue as string).Trim();
TargetValue = (TargetValue as string).Length == 0 ? null : (TargetValue as string).ToUpper();
}
// compare the value against the target value
if ((dependentValue == null && TargetValue.Equals("") ||
(dependentValue == null && !TargetValue.Equals("") ||
(dependentValue != null && dependentValue.Equals(this.TargetValue)))))
{
// try validating this field
if (!_innerAttribute.IsValid(value))
// validation failed - return an error
return new ValidationResult(FormatErrorMessage(validationContext.DisplayName), new[] { validationContext.MemberName });
}
}
// validation success - return success
return ValidatioResult.Success;
}}
POCO / Model: Create or Modify Address class to apply the custom validation on City property as below to define City is required when selected Country is USA.
public class Address {Controller: The controller for Address entity with a POST method will look like below.public int AddressId {get; set; }
public string Line1 { get; set; }
public string Line2 { get; set; }
[RequiredIfValidator("Country", "USA", ErrorMessage = "City is Required")]public string City { get; set; }
public string State { get; set; }
public string Zip { get; set; }
public IEnumerable}Country { get; set; }
Here, before persisting the changes in database, I am forcing process to re-validate Modal State by calling "this.Validate()", which will help the process to identify and throw an error upfront instead of making a call to DB.
public class AddressController : ApiControllerTesting: Below TestMethods will help testing the post method to check for the expected validation error:
{
[ResponseType(typeof(Address))]public IHttpActionResult PostAddress(Address address){// use below to force validation before doing anything
this.Validate(address);
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
db.Address.Add(address);
db.SaveChanges();
return CreatedAtRoute("DefaultApi", new { id = address.AddressId }, address);
}}
Here, The first test method will represent a case forcing this.Validate() method and second without forcing this.Validate() in controller's POST method.
// Forcing this.Validate() - this case Controller will thow the error
[TestMethod]
public void RequiredCityWhenCountryIsUSATest()
{
Address address = new Address { Line1 = "200 E Main St", Line2 = "Apt B", State = "VA", Country = "USA" };AddressController addressController = new AddressController();AddressController.Request = new HttpRequestMessage();AddressController.Request.Properties["MS_HttpConfiguration"] = new HttpConfiguration();
addressController.PostAddress(address);
Assert.IsFalse(addressController.ModelState.IsValid);Assert.IsTrue(addressController.ModelState.Count == 1, "City is Required");}
// Without forcing this.Validate() - this case DB will throw the errorWith this I am concluding the illustration. Feel free to share your feedback.
[TestMethod]
public void RequiredCityWhenCountryIsUSATest()
{
Address address = new Address { Line1 = "200 E Main St", Line2 = "Apt B", State = "VA", Country = "USA" };try{AddressController addressController = new AddressController();
AddressController.Request = new HttpRequestMessage();
AddressController.Request.Properties["MS_HttpConfiguration"] = new HttpConfiguration();
addressController.PostAddress(address);
}catch (System.Data.Entity.Validation.DbEntityValidationException dbEx){foreach (var validationErrors in dbEx.EntityValidationErrors)
{
foreach (var validationError in validationErrors.ValidationErrors)
{
Assert.AreEqual("City is Required", validationError.ErrorMessage);
}
}
}}
Happy Programming !!!
No comments:
Post a Comment