ASP.NET MVC 3 - How can I conditionally validate (IValidatableObject) when calling UpdateModel or TryUpdateModel?
I am using EFCodeFirst CTP5 (although this shouldn't make a difference) and ASP.NET MVC 3. I have created the following entities:
public class Company
{
public int CompanyID { get; set; }
public int SubdomainID { get; set; }
public virtual Subdomain Subdomain { get; set; }
}
public class Subdomain : IValidatableObject
{
public int SubdomainID { get; set; }
public virtual ICollection<SubdomainActivity> Activities { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
int activitySum = Activities.Sum(x => x.Value);
if (activitySum != 100)
{
yield return new ValidationResult(string.Format("The Activity percentages must add up to 100. Current sum is {0}", activitySum), new[] { "Activities" });
}
}
}
public class SubdomainActivity
{
public int ActivityID { get; set; }
public int Value { get; set; }
}
Notice that a Company
has a Subdomain
and a Subdomain
has an ICollection<SubdomainActivity>
.
I have controllers for Company
and Subdomain
. In the SubdomainController
when I call UpdateModel()
(or TryUpdateModel()
) on an instance of a Subdomain
I get the result I want. The Subdomain
activities are validated that the values sum up to 100.
In the CompanyController
when I call UpdateModel()
on an instance of a Company
it validates that the Subdomain.Activities
add up to 100 as well, which I don't want. The user can't even edit Subdomain.Activites
from the Company
edit view anyway, so it makes no sense to tell them that there was an error with Subdomain.Activities
.
I would like to be able to add an item to the validationContext.Items IDictionary so that I can conditionally validate based on context or something like that, but I haven't been able to figure out how to do that without writing my own IModelBinder (which I could do I suppose, but seems like overkill). Ulti开发者_StackOverflow中文版mately I have come up with this workaround in the CompanyController
, but I am sure there is a correct way to handle this (this is wrapped in a try
catch
)
var d = this.companyRepository.GetById(id);
bool good = TryUpdateModel(d);
if (!good)
{
if (ModelState.ContainsKey("Subdomain.Activities"))
ModelState.Remove("Subdomain.Activities");
if (!ModelState.IsValid)
throw new InvalidOperationException(string.Format("The model of type '{0}' could not be updated.", typeof(Company).FullName));
}
this.companyRepository.Save();
As an interesting side note, if I don't check if the model is valid and throw an error then the companyRepository.Save() works fine. In other words EFCodeFirst doesn't seem to call Validate on the Subdomain, but MVC UpdateModel does.
Can anyone help me out with a better/correct/recommended way to handle this?
I found a way to handle this a bit more gracefully. Instead of implementing IValidatableObject
on the Subdomain
I just use a [CustomValidation]
attribute on the Activities property of the Subdomain. This is NOT called when validating the Company
instance in my question and I still get my specialized validation on the activities. Hopefully this helps someone somewhere someday.
public class Subdomain
{
public int SubdomainID { get; set; }
[CustomValidation(typeof(Subdomain), "ValidateActivities")]
public virtual ICollection<SubdomainActivity> Activities { get; set; }
public static ValidationResult ValidateActivities(ICollection<SubdomainActivity> activities)
{
if (activities.Count > 0)
{
int activitySum = activities.Sum(x => x.Value);
if (activitySum != 100)
{
return new ValidationResult(string.Format("The Activity percentages must add up to 100. Current sum is {0}", activitySum), new[] { "Activities" });
}
}
return ValidationResult.Success;
}
}
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
int activitySum = Activities.Sum(x => x.Value);
if (activitySum != 100)
{
yield return new ValidationResult(string.Format("The Activity percentages must add up to 100. Current sum is {0}", activitySum), new[] { "Activities" });
}
}
what about modifying your Validate() method as below?
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
int activitySum = Activities.Sum(x => x.Value);
if (Activities.Count != 0 && activitySum != 100)
{
yield return new ValidationResult(string.Format("The Activity percentages must add up to 100. Current sum is {0}", activitySum), new[] { "Activities" });
}
}
精彩评论