开发者

ASP.NET MVC 3 - How can I conditionally validate (IValidatableObject) when calling UpdateModel or TryUpdateModel?

开发者 https://www.devze.com 2023-02-07 21:37 出处:网络
I am using EFCodeFirst CTP5 (although this shouldn\'t make a difference) and ASP.NET MVC 3. I have created the following entities:

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" });
    }
}  
0

精彩评论

暂无评论...
验证码 换一张
取 消