0

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). Ultimately 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?

saggu
  • 73
  • 5
Quesi
  • 746
  • 6
  • 16
  • If I could just skip validation for the Company Subdomain property, I think that would work too. Not sure how to do that though. – Quesi Jan 28 '11 at 23:54
  • Isn't there a dontvalidateattribute or something? Look in system.componentobject.dataannotations or what the namespace is called (sorry I'm not more helpful, on mobile). – Alxandr Jan 29 '11 at 14:46
  • I think removing it from ModelState errors is fine. With regards to your question about EF CTP5 not validating - it might be a bug in this CTP. It should use IValidatableObject and validate it. Maybe this question could help http://stackoverflow.com/questions/4233920/idataerrorinfo-vs-ivalidatableobject – mare Jan 29 '11 at 16:50
  • @Alxandr - yeah, I have searched that namespace with no success. I tried adding a CustomValidationAttribute hoping it would override the other one, but it didn't help. Still ran the Subdomain validation. You can also add [ValidateInput(false)] to the model, but not properties. – Quesi Jan 29 '11 at 23:41
  • @mare - how far down the object graph can one expect it to validate? In this case it is technically validating the activities of subdomains. Not even related to the company. I prefer what I have seen of EFCodeFirst over TryUpdateModel and UpdateModel. If I had a way to set a flag in the validationContext I would be happy. Having to remove the Model error is a pain. Also, not sure how the other question relates to mine. – Quesi Jan 29 '11 at 23:47

2 Answers2

1

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;
    }
}
Quesi
  • 746
  • 6
  • 16
0
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" });
    }
}  
John Cai
  • 16
  • 1