This is a very similar problem to the one already posted here: ASP.NET MVC: Validation messages set in TryUpdateModel do not appear in ValidationSummary
I'm not sure if this old topic belongs to an earlier version of MVC, but in MVC3 I experience some odd behavior on similar lines.
I have a model of the Trade class. It inherits from IValidatableObject, so it implements the Validate method. As part of this, we have some validation of the model as a whole (as opposed to data annotations that provide validation of properties). Validation is as follows:
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) { var validationResults = new List<ValidationResult>(); if (this.EndDate < this.StartDate) { validationResults.Add(new ValidationResult("End date must be greater than start date")); } return validationResults; }
We have a presentation model that will help with the display of trade. This contains a link to the trading model through the TradeModel property. Thus, basically the presentation model is a trading model, as well as additional information for the population of drop-down lists, such as counterparties, etc.
Our CSHTML class contains ValidationSummary, with "true" as an argument, which means that it will only show model errors.
If I implement my HttpPost controller method to create a new Trade as follows:
[HttpPost] public ActionResult Create(FormCollection collection) { var trade = new Trade(); if (this.TryUpdateModel(trade)) { if (this.SaveChanges(this.ModelState, trade)) { return this.RedirectToAction("Index"); } } return this.View(trade); }
... when I enter a deal with StartDate> EndDate, I find that TryUpdateModel returns false, and the user returns to his deal. It seems logical. Unfortunately, ValidationSummary does not show any error messages.
If I set a breakpoint in the Create method and examine ModelState, I see that there is an error message in the dictionary. This is against the "TradeModel" key, and not against any property. Again, this seems logical.
One theory as to why this is the case is that ValidationSummary assumes that any validation errors on a key that is not String.Empty should be property validation errors, ignoring our validation errors, since we have a view model containing link to the model, whereby the key is "TradeModel".
What removes this theory from the water is this: if I rewrote the Create Control function as follows ...
[HttpPost] public ActionResult Create(Trade trade, FormCollection collection) { if (this.SaveChanges(this.ModelState, trade)) { return this.RedirectToAction("Index"); } return this.View(trade); }
... and therefore rely on MVC to bind “automatically” and re-run the same test script, the user is presented with an alleged error message!
If I add a breakpoint and look at ModelState, I see the same error messages against the same keys as before, but this time ValidationSummary picks them up!
If I change the validation as follows, then it works with the Create Control function, written anyway:
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) { var validationResults = new List<ValidationResult>(); if (this.EndDate < this.StartDate) { validationResults.Add(new ValidationResult("End date must be greater than start date", new[] { "StartDate" })); } return validationResults; }
So it’s clear that this is only a problem with model level verification errors.
Any help with this would be greatly appreciated! There are reasons (which I will not go into now) why we need to manually create an instance of our view model and call the binding using TryUpdateModel.
Thanks in advance!
UPDATE
It seems that the ValidationSummary theory displaying key errors in ModelState from String.Empty when said to rule out property errors is actually true. I looked at the source code and actually uses ViewData.TemplateInfo.HtmlFieldPrefix to find model-level validation errors. By default, this is String.Empty.
Thus, changing this value in "TradeModel" seems logical, but it forces every HTML code or name to be a prefix, so the binding does not work!
If the view model contains a link to the business model, any errors added to the ModelState using the ivalidatableObject file are added with a key that includes a prefix equal to the name of the business model property in the view model (in our case, "TradeModel"), as a result of which keys like "TradeModel.CounterpartyId" etc. Errors at the model level are added with a key equal to the name of the property of the business model of the presentation model ("TradeModel").
Thus, it seems that the business cannot add validation errors at the model level if the presentation model is constructed in this way.
What puzzles me is why it really works in our "real" project when the Create Control function is written so that an object of the Trade view model is required as an argument. I must have missed this yesterday, but looking at it today, when the MVC is connected and the check is activated, it seems that an additional key is added at the end of the dictionary with the value String.Empty. This contains a duplicate of errors added using the TradeModel key. As expected, ValidationSummary picks them up!
So why does MVC do this in our live project, but not in a simple test application?
I saw that the check was activated twice when the controller function is written to take the view model as an argument. Maybe it is something subtle every time?
UPDATE ... AGAIN
The reason that it works in our real project is that in our base controller (from which everyone inherits) there is code that copies all errors found in the model state into a new record with the String.Empty key. This code was called in only one of two scenarios. Thus, there is no real difference between real and test applications.
Now I understand what is happening and why ValidationSummary behaves as follows.