MVC ValidationSummary ignores model level validation errors when binding using TryUpdateModel

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.

+6
source share
1 answer

Ok I am now at a point where I can answer it myself.

If ValidationSummary is used with the ExcludePropertyErrors argument set to True, it will look for errors in the model state using a key equal to ViewData.TemplateInfo.HtmlFieldPrefix. By default, this is String.Empty.

If you have a presentation model that exposes your business model, something like this:

 namespace ValidationSummary.Models { using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; public class TradeModel : IValidatableObject { public DateTime StartDate { get; set; } public DateTime EndDate { get; set; } public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) { List<ValidationResult> validationResults = new List<ValidationResult>(); if (EndDate < StartDate) { validationResults.Add(new ValidationResult("End date must not be before start date")); } return validationResults; } } } namespace ValidationSummary.ViewModels { public class Trade { public Trade() { this.TradeModel = new Models.TradeModel(); } public Models.TradeModel TradeModel { get; private set; } } } 

When validation is performed, errors (ValidationResults) are added to ModelState, which are added at the model level (where there is no additional argument to the ValidationResult constructor, which accepts property names (names), prefixed with the name of the view model property - in this example, "TradeModel".

In this regard, we will consider several ways.

  • Do not expose the business model outside of the presentation model. This is essentially related to binding to the presentation model, and not to the business model of the presentation model. There are two ways to do this: make the presentation model a subclass of the business model; or duplicate the properties of a business model on a presentation model. I prefer the first.
  • Enter the code to copy the model level errors into the new ModelState dictionary key in String.Empty. Unfortunately, this may not be possible to do gracefully, since the property name of the view model that displays the business model is used as the key. This may be different for each controller / view model.
  • In the view, use the following. Error messages that are related to the business model appear. In essence, this is a pretense that model-level errors for a business model are actually property errors. The display of this data is not the same as for ValidationSummary, but perhaps it could be cured with CSS:

    @ Html.ValidationMessageFor (m => m.TradeModel)

  • Subclass confirmation This would have to change it so that he knows which keys in ModelState relate to the properties of the business model of the view model.

+5
source

Source: https://habr.com/ru/post/919682/


All Articles