My model ( class A ) has a property ( called b ) of type B with IValidatableObject
implemented.
View has got @Html.ValidationSummary(true)
In the validation summary I want to exclude errors related to properties.
In class B IValidatableObject
implementation is returning ValidationResult
with no memberNames
But class B valiadtion errors from IValidatableObject
are not displayed since class B is a property on class A
How to display class B non-property validation errors?
I think this is pretty much straight forward, Let me explain with an example. First let me create the problem you are facing, then i will explain how to solve.
1) Declare My models.
public class ClassA
{
[Required]
public string Name { get; set; }
public ClassB CBProp { get; set; }
}
public class ClassB:IValidatableObject
{
[Required]
public string MyProperty { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (!string.IsNullOrWhiteSpace(MyProperty) && MyProperty.Length > 10)
yield return new ValidationResult("MaxLength reached");
}
}
2) Declare simple actions.
public class HomeController : Controller
{
[HttpGet]
public ActionResult Test()
{
ClassA ca = new ClassA();
return View(ca);
}
[HttpPost]
public ActionResult Test(ClassA ca)
{
return View(ca);
}
}
3) Let me create a simple view and an editor template for ClassB.
@using (Html.BeginForm()) {
@Html.ValidationSummary(true)
<fieldset>
<legend>ClassA</legend>
<div class="editor-label">
@Html.LabelFor(model => model.Name)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.Name)
@Html.ValidationMessageFor(model => model.Name)
</div>
@Html.EditorFor(m => m.CBProp, "TestB")
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
<div class="editor-label">
@Html.LabelFor(model => model.MyProperty)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.MyProperty)
@Html.ValidationMessageFor(model => model.MyProperty)
</div>
4) Now the view will look like,
5) Now if we will click on Submit. with out entering any data. It will show property validation error as expected.
6) Now if we will enter a string with length > 10 in MyProperty, it should show the error message "MaxLength reached", But the error will not be displayed :) :)
So, If we see the code for the view, we can find the line
@Html.ValidationSummary(true) /// true, will excludePropertyErrors
Since, CBProp is a property in ClassA, ValidationSummary(true)
will exclude any error in CBProp. So you will not find the error message being displayed. How ever there are several options available for this.
1) Set @Html.ValidationSummary()
This will display the error message, But it will also display any other error message(which is redundant),like,
2) Set @Html.ValidationSummary(true)
in the Editor Template. But it will show the error message like,
3) In Validate method of ClassB, specify property name along with error message in ValidationResult.Now it will be treated as a validation error for the property and will be displayed by @Html.ValidationMessageFor(model => model.MyProperty)
in Editor Template.
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (!string.IsNullOrWhiteSpace(MyProperty) && MyProperty.Length > 10)
yield return new ValidationResult("MaxLength reached", new List<string> { "MyProperty" });
}
I think its now clear why the error message was not displayed, and what are the options available. Its up to you to decide the approach best suitable for you.
Cheers
While this behaviour of the validation summary may be inappropriate in your case, it has to be considered "correct" in general. When errors are created in sub-objects contained in the model, the right prefix is added to the errors. That is, if a sub-object is contained in a property MyProp
then the prefix MyProp
is automatically added to all errors. This is necessary to give the right name to all errors that are created - without this neither the Validationsummary
nor the ValidationMessageFor
would work properly - because they refer to the full names (the one including the whole prefixes). This is the only way to avoid ambiguities, because you might have two Name
properties in two different sub-objects.
However, often, this "correct" behaviour is inappropriate when the errors generated in the sub-object are not simple properties level errors but "whole object" level errors. In such cases you might wish they appear in the general validation summary.
You can face this problems in two ways:
foreach
, and then promoting some of them up. Promoting up means deleting the last part of the error prefix. When promoting an error you may keep the original error or not - keeping the original error too is easier and in most of the cases it is the correct thing to do. Note that you can't remove an entry while looping through all entries - you have to put it in a list and then remove it after the loop is terminated.The promoting criteria may depend on your needs. I give you some example:
ViewModel
that contains whole objects instead of simple values!The error processing can be performed within a custom ActionFilter that you may define, and re-use with several action methods.
Below is the code for a simple PromoteAttribute ActionFilter. Its use is:
[Promote("prop1.SubProp1 Prop2")]
public ActionResult MyMethod( ...
That is you pass it a list of expression errors you would like to promote to the Root model, and if it find errors matching them in ModelState it promotes them - obviously it is just a simple example - you can promote just one level up instead of the root, and you can use a complex criterion to locate the errors to promote instead of listing them:
public class PromoteAttribute : ActionFilterAttribute
{
string[] expressions;
public PromoteAttribute(string toPromote)
{
expressions = toPromote.Split(' ');
}
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
ModelStateDictionary modelState=filterContext.Controller.ViewData.ModelState;
foreach(var x in expressions)
{
if (modelState.ContainsKey(x))
{
var entry = modelState[x];
if (entry.Errors.Count == 0) continue;
foreach (var error in entry.Errors) modelState.AddModelError("", error.ErrorMessage);
}
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With