Validation of objects in ViewModel not adding CSS validation classes - c#

I have the following view models:
public class Search {
public int Id { get; set; }
[Required(ErrorMessage = "Please choose a name.")]
public string Name { get; set; }
[ValidGroup(ErrorMessage = "Please create a new group or choose an existing one.")]
public Group Group { get; set; }
}
public class Group {
public int Id { get; set; }
public string Name { get; set; }
}
I have defined a custom validation attribute as follows:
public class ValidGroupAttribute : ValidationAttribute {
public override bool IsValid(object value) {
if (value == null)
return false;
Group group = (Group)value;
return !(string.IsNullOrEmpty(group.Name) && group.Id == 0);
}
}
I have the following view (omitted some for brevity):
#Html.ValidationSummary()
<p>
<!-- These are custom HTML helper extensions. -->
#Html.RadioButtonForBool(m => m.NewGroup, true, "New", new { #class = "formRadioSearch", id = "NewGroup" })
#Html.RadioButtonForBool(m => m.NewGroup, false, "Existing", new { #class = "formRadioSearch", id = "ExistingGroup" })
</p>
<p>
<label>Group</label>
#if (Model.Group != null && Model.Group.Id == 0) {
#Html.TextBoxFor(m => m.Group.Name)
}
else {
#Html.DropDownListFor(m => m.Group.Id, Model.Groups)
}
</p>
The issue I'm having is the validation class input-validation-error does not get applied to the Group input. I assume this is because the framework is trying to find a field with id="Group" and the markup that is being generated has either id="Group_Id" or id=Group_Name. Is there a way I can get the class applied?
http://f.cl.ly/items/0Y3R0W3Z193s3d1h3518/Capture.PNG
Update
I've tried implementing IValidatableObject on the Group view model instead of using a validation attribute but I still can't get the CSS class to apply:
public class Group : IValidatableObject
{
public int Id { get; set; }
public string Name { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) {
if (string.IsNullOrEmpty(Name) && Id == 0) {
yield return new ValidationResult("Please create a new group or select an existing one.", new[] { "Group.Name" });
}
}
}
Update 2
Self validation doesn't work. I think this is because the second parameter in the ValidationResult constructor isn't used in the MVC framework.
From: http://www.devtrends.co.uk/blog/the-complete-guide-to-validation-in-asp.net-mvc-3-part-2
In some situations, you might be tempted to use the second constructor overload of ValidationResult that takes in an IEnumerable of member names. For example, you may decide that you want to display the error message on both fields being compared, so you change the code to this:
return new ValidationResult(
FormatErrorMessage(validationContext.DisplayName), new[] { validationContext.MemberName, OtherProperty });
If you run your code, you will find absolutely no difference. This is because although this overload is present and presumably used elsewhere in the .NET framework, the MVC framework completely ignores ValidationResult.MemberNames.

I've come up with a solution that works but is clearly a work around.
I've removed the validation attribute and created a custom model binder instead which manually adds an error to the ModelState dictionary for the property Group.Name.
public class SearchBinder : DefaultModelBinder {
protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext,
PropertyDescriptor propertyDescriptor) {
if (propertyDescriptor.Name == "Group" &&
bindingContext.ValueProvider.GetValue("Group.Name") != null &&
bindingContext.ValueProvider.GetValue("Group.Name").AttemptedValue == "") {
ModelState modelState = new ModelState { Value = bindingContext.ValueProvider.GetValue("Group.Name") };
modelState.Errors.Add("Please create a new group or choose an existing one.");
bindingContext.ModelState.Add("Group.Name", modelState);
}
base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
}
}
// Register custom model binders in Application_Start()
ModelBinders.Binders.Add(typeof(SearchViewModel), new SearchBinder());
With ModelState["Group.Name"] now having an error entry, the CSS class is being rendered in the markup.
I would much prefer if there was a way to do this with idiomatic validation in MVC though.
Solved!
Found a proper way to do this. I was specifying the wrong property name in the self validating class, so the key that was being added to the ModelState dictionary was Group.Group.Name. All I had to do was change the returned ValidationResult.
yield return new ValidationResult("Please create a new group or select an existing one.", new[] { "Name" });

Related

Custom validation for hidden/non-bound field in Blazor

I'm working with Blazor for the first time (also the first time I've worked with .NET in a few years so I'm rusty), and an additional component library that my team and I have decided to use with Blazor is MudBlazor. Right now I'm working on a page that has an Autocomplete component.
What I'm trying to do is to use an Autocomplete feature to pull up a list of Books that a user can add to a list. At least one Book must be in the list. My problem is that because of how MudAutocomplete works, I'm unable to bind it to a list, so I have the Book appended to a list on a click event. However, hitting the submit button isn't hitting either of the validations I've implemented and I can't seem to figure out why.
I've got the following code:
<MudForm #ref="form" #bind-isValid="#success" bind-errors="#errors">
<MudAutocomplete T="Book" Label="Select Book(s)" ValueChanged="#(b => AppendBookToList(b))" SearchFunc="#SearchBooks" MinCharacters="4" ToStringFunc="#(b => b == null ? null : $"{b.Name + " by " + b.Author}")" Validation="#(new Func<string, IEnumerable<string>>(ValidateRequiredBooks)">
</MudAutocomplete>
<!-- List books that were selected from the autocomplete -->
#foreach (var b in Books)
{
<MudChip Color="Color.Primary" OnClose="RemoveRequester" Text="b.Id">#b.Name by #b.Author</MudChip>
}
<!-- I guess use this area below to secretly bind the Books field? Not sure how to display the validation error otherwise -->
<MudField #bind-Value="#Books"></MudField>
...
<MudButton Variant="Variant.Filled" Color="Color.Primary" Class="ml-auto" OnClick="#(() => form.Validate())">Submit</MudButton>
</MudForm>
...
#code {
...
public class Book
{
public int Id { get; set; }
public string Name { get; set; }
public string Author { get; set; }
}
[BooksValidation(ErrorMessage = "At least one Book required.")]
public List<Book> Books { get; set; }
...
// This is not firing
private IEnumerable<string> ValidateRequiredBooks(string value)
{
if (Books.Count == 0)
{
yield return "At least one Book must be selected.";
yield break;
}
}
}
I also created the following custom validation attribute for my Books variable:
public class BooksValidationAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
// Breakpoint here isn't getting hit
var listValue = value as List<Book>;
if (listValue != null && listValue.Count != 0)
{
return null;
}
else
{
return new ValidationResult(ErrorMessage, new[] { validationContext.MemberName });
}
}
}
Any ideas on why these aren't working at all? It's driving me up a wall
I use Validation attribute like:
Validation="#(new BooksValidationAttribute())"

How to use an alternative auto-generated identifier upon rendering multiple input elements with the same property name?

Let's assume that I have a view model like this
public class ExampleVM
{
[Display(Name = "Foo")]
public Nullable<decimal> FooInternal { get; set; }
}
My view looks like this (also a form tag which I omitted in this post)
#model ExampleVM
....
<input asp-for="FooInternal" class="form-control" type="number" />
This results in a rendered text box with FooInternal as id-attribute.
In my scenario, I also have a modal dialog with another form with another view model which shares a property with the same name. I know that the asp-for taghelper renders the id-attribute either a manually from a specified id or infers the id from the property name.
In my backend code, I want to be able to name my properties how I seem fit given the view model context. I don't want to rename my properties to make them globally unique.
I try to avoid two things:
To manually specify the id in the view / the input element. I'd much rather use an autogenerated id that I can set via another attribute in the backend.
Given that I use the view model with [FromBody] in a post, I can't exactly rename the property as it would be with [FromRoute(Name="MyFoo")]. I don't want to map a manually entered id back to my property.
Basically, I'm looking for something like this:
public class ExampleVM
{
[Display(Name = "Foo")]
[HtmlId(Name = "MyUniqueFooName")]
public Nullable<decimal> FooInternal { get; set; }
}
where HtmlId would be an attribute that interacts with the tag-helper for rendering and also for rebinding the view model as parameter from a [HttpPost] method.
Maybe another approach is also valid since avoiding multiple input elements (with the same identifier) in multiple forms on the same page seems like a common situation to me.
According to your description, if you want to achieve your requirement, you should write custom modelbinding and custom input tag helper to achieve your requirement.
Since the asp.net core modelbinding will bind the data according to the post back's form data, you should firstly write the custom input tag helper to render the input name property to use HtmlId value.
Then you should write a custom model binding in your project to bind the model according to the HtmlId attribute.
About how to re-write the custom input tag helper, you could refer to below steps:
Notice: Since the input tag helper has multiple type "file, radio,checkbox and else", you should write all the logic based on the source codes.
According to the input taghelper source codes, you could find the tag helper will call the Generator.GenerateTextBox method to generate the input tag html content.
The Generator.GenerateTextBox has five parameters, the third parameter expression is used to generate the input textbox's for attribute.
Generator.GenerateTextBox(
ViewContext,
modelExplorer,
For.Name,
modelExplorer.Model,
format,
htmlAttributes);
If you want to show the HtmlId value as the name for the for attribute, you should create a custom input taghelper.
You should firstly create a custom attribute:
[System.AttributeUsage(AttributeTargets.All, Inherited = false, AllowMultiple = true)]
public class HtmlId : Attribute
{
public string _Id;
public HtmlId(string Id) {
_Id = Id;
}
public string Id
{
get { return _Id; }
}
}
Then you could use var re = ((Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.DefaultModelMetadata)For.ModelExplorer.Metadata).Attributes.PropertyAttributes.Where(x => x.GetType() == typeof(HtmlId)).FirstOrDefault(); to get the htmlid in the input tag helper's GenerateTextBox method.
Details, you could refer to below custom input tag helper codes:
using Microsoft.AspNetCore.Mvc.TagHelpers;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Razor.TagHelpers;
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Mvc.Rendering;
namespace SecurityRelatedIssue
{
[HtmlTargetElement("input", Attributes = ForAttributeName, TagStructure = TagStructure.WithoutEndTag)]
public class CustomInputTagHelper: InputTagHelper
{
private const string ForAttributeName = "asp-for";
private const string FormatAttributeName = "asp-format";
public override int Order => -10000;
public CustomInputTagHelper(IHtmlGenerator generator)
: base(generator)
{
}
public override void Process(TagHelperContext context, TagHelperOutput output)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (output == null)
{
throw new ArgumentNullException(nameof(output));
}
// Pass through attributes that are also well-known HTML attributes. Must be done prior to any copying
// from a TagBuilder.
if (InputTypeName != null)
{
output.CopyHtmlAttribute("type", context);
}
if (Name != null)
{
output.CopyHtmlAttribute(nameof(Name), context);
}
if (Value != null)
{
output.CopyHtmlAttribute(nameof(Value), context);
}
// Note null or empty For.Name is allowed because TemplateInfo.HtmlFieldPrefix may be sufficient.
// IHtmlGenerator will enforce name requirements.
var metadata = For.Metadata;
var modelExplorer = For.ModelExplorer;
if (metadata == null)
{
throw new InvalidOperationException();
}
string inputType;
string inputTypeHint;
if (string.IsNullOrEmpty(InputTypeName))
{
// Note GetInputType never returns null.
inputType = GetInputType(modelExplorer, out inputTypeHint);
}
else
{
inputType = InputTypeName.ToLowerInvariant();
inputTypeHint = null;
}
// inputType may be more specific than default the generator chooses below.
if (!output.Attributes.ContainsName("type"))
{
output.Attributes.SetAttribute("type", inputType);
}
// Ensure Generator does not throw due to empty "fullName" if user provided a name attribute.
IDictionary<string, object> htmlAttributes = null;
if (string.IsNullOrEmpty(For.Name) &&
string.IsNullOrEmpty(ViewContext.ViewData.TemplateInfo.HtmlFieldPrefix) &&
!string.IsNullOrEmpty(Name))
{
htmlAttributes = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase)
{
{ "name", Name },
};
}
TagBuilder tagBuilder;
switch (inputType)
{
//case "hidden":
// tagBuilder = GenerateHidden(modelExplorer, htmlAttributes);
// break;
//case "checkbox":
// tagBuilder = GenerateCheckBox(modelExplorer, output, htmlAttributes);
// break;
//case "password":
// tagBuilder = Generator.GeneratePassword(
// ViewContext,
// modelExplorer,
// For.Name,
// value: null,
// htmlAttributes: htmlAttributes);
// break;
//case "radio":
// tagBuilder = GenerateRadio(modelExplorer, htmlAttributes);
// break;
default:
tagBuilder = GenerateTextBox(modelExplorer, inputTypeHint, inputType, htmlAttributes);
break;
}
if (tagBuilder != null)
{
// This TagBuilder contains the one <input/> element of interest.
output.MergeAttributes(tagBuilder);
if (tagBuilder.HasInnerHtml)
{
// Since this is not the "checkbox" special-case, no guarantee that output is a self-closing
// element. A later tag helper targeting this element may change output.TagMode.
output.Content.AppendHtml(tagBuilder.InnerHtml);
}
}
}
private TagBuilder GenerateTextBox(
ModelExplorer modelExplorer,
string inputTypeHint,
string inputType,
IDictionary<string, object> htmlAttributes)
{
var format = Format;
if (string.IsNullOrEmpty(format))
{
if (!modelExplorer.Metadata.HasNonDefaultEditFormat &&
string.Equals("week", inputType, StringComparison.OrdinalIgnoreCase) &&
(modelExplorer.Model is DateTime || modelExplorer.Model is DateTimeOffset))
{
// modelExplorer = modelExplorer.GetExplorerForModel(FormatWeekHelper.GetFormattedWeek(modelExplorer));
}
else
{
//format = GetFormat(modelExplorer, inputTypeHint, inputType);
}
}
if (htmlAttributes == null)
{
htmlAttributes = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
}
htmlAttributes["type"] = inputType;
if (string.Equals(inputType, "file"))
{
htmlAttributes["multiple"] = "multiple";
}
var re = ((Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.DefaultModelMetadata)For.ModelExplorer.Metadata).Attributes.PropertyAttributes.Where(x => x.GetType() == typeof(HtmlId)).FirstOrDefault();
return Generator.GenerateTextBox(
ViewContext,
modelExplorer,
((HtmlId)re).Id,
modelExplorer.Model,
format,
htmlAttributes);
}
}
}
Improt this taghelper in _ViewImports.cshtml
#addTagHelper *,[yournamespace]
Model exmaple:
[Display(Name = "Foo")]
[HtmlId("test")]
public string str { get; set; }
Result:
Then you could write a custom model binding for the model to bind the data according to the htmlid. About how to use custom model binding, you could refer to this article.

Multiple dynamic Submitbuttons in MVC

I am currently developing a Framework to generate dynamic Views in MVC, the idea is based on this tutorial.
The next step is adding the possibility to generate multiple submit buttons but I cant get it to work. I did some research and found this approach. However, since i want to generate those buttons dynamically, this does not work yet.
What I tried is to modify this attribute code here:
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class MultipleButtonAttribute : ActionNameSelectorAttribute
{
public string Name { get; set; }
public string Argument { get; set; }
public override bool IsValidName(ControllerContext controllerContext, string actionName, MethodInfo methodInfo)
{
var isValidName = false;
var keyValue = string.Format("{0}:{1}", Name, Argument);
var value = controllerContext.Controller.ValueProvider.GetValue(keyValue);
if (value != null)
{
controllerContext.Controller.ControllerContext.RouteData.Values[Name] = Argument;
isValidName = true;
}
return isValidName;
}
}
While debugging I digged down the ValueProvider and found out that the JQueryFormValueProvider in the Collection of Valueproviders actually contains the Name of the clicked submitbutton.
The button is generated like this:
<input type="submit" name="buttonClick:#Model.Id" value="#Model.Text" />
Unfortunately the ValueProviders do not let me Iterate through the Keys so I dont know how to get the value I circled red in the above screenshot.
It doesnt need to be this approach, all what counts is, to find out which button was clicked.
Ok i found the solution by modifying the Attribute-code like this:
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class NameToRouteDataAttribute : ActionNameSelectorAttribute
{
/// <Summary>Name of the Value you want to grab from the control.</Summary>
public string Name { get; set; }
public override bool IsValidName(ControllerContext controllerContext, string actionName, MethodInfo methodInfo)
{
ValueProviderCollection providers = controllerContext.Controller.ValueProvider as ValueProviderCollection;
if (providers != null)
{
// We need this value-Provider as it contains the Data from the Form
JQueryFormValueProvider formProvider = providers.OfType<JQueryFormValueProvider>().FirstOrDefault();
if (formProvider != null)
{
// now look for the specified value-prefix.
var kvp = formProvider.GetKeysFromPrefix(Name).FirstOrDefault();
if (kvp.Key != null)
controllerContext.Controller.ControllerContext.RouteData.Values[Name] = kvp.Key;
}
}
return true;
}
}
Contollercode:
[HttpPost]
[NameToRouteDataAttribute(Name = "buttonClick")]
public ActionResult Show(FormViewModel form)
{
string buttonId = RouteData.GetRequiredString("buttonClick");
...
}
Buttoncode
#model MvcForms.Controls.ButtonViewModel
<input type="submit" name="buttonClick.#Model.Id" value="#Model.Text" />
The important thing here is, that the name of the Button contains a dot. This makes the JQueryFormValueProvider.GetKeysFromPrefix work the way I need it to.

Required attribute not appearing in error list when using IValidatableObject

I've got a class that has [Required] decorations on some attributes, but also needs some custom validation, so it uses IValidatableObject.
Class
public class ModelCourse : IValidatableObject
{
//some other code...
[DisplayName("Course Name")]
[Required]
[StringLength(100, MinimumLength=1)]
public String name { get; set; }
//more code...
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (courseGradeLevels == null || courseGradeLevels.Count < 1)
{
yield return new ValidationResult("You must select at least one grade level.");
}
if ((courseLength == CourseLength.Other) && string.IsNullOrEmpty(courseLengthDescription))
{
yield return new ValidationResult("Course Length Description is Required if Length is Other.");
}
}
}
Validation
//course is one of a few child objects in this class
bool dbDetailsValid = TryValidateModel(dbDetails);
ViewData["courseValid"] = !ModelState.ContainsKey("course");
foreach(KeyValuePair<string, ModelState> pair in ModelState.Where(x => x.Value.Errors.Count > 0))
{
ViewData[pair.Key + "ErrorList"] = pair.Value.Errors.ToList();
}
When I run this code, a blank string for name doesn't result in an error in ModelState. The custom validation logic works as expected, but I don't get why TryValidateModel isn't picking up on the decorations... is my only option to manually check each required field?
Thanks to #nick nieslanik to pointing me to Understanding ValidationContext in DataAnnotations, I have my answer.
Inside my custom validation method, I use reflection to loop through each property, and call TryValidateProperty on each one, adding it to the results if it doesn't validate:
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
List<ValidationResult> results = new List<ValidationResult>();
foreach (PropertyInfo property in this.GetType().GetProperties())
{
Validator.TryValidateProperty(property.GetValue(this), new ValidationContext(this, null, null) { MemberName = property.Name }, results);
if (results.Count > 0)
{
foreach (ValidationResult err in results)
{
yield return new ValidationResult(err.ErrorMessage);
}
results.Clear();
}
}
//the rest of the validation happens here...
}

Dynamic validation in ASP MVC

For a project at work I'm trying to create a process that lets a user dynamically create a form that other users could then fill out the values for. I'm having trouble figuring out how to go about getting this to play nice with the built in model binding and validation with ASP MVC 3, though.
Our view model is set up something like this. Please note that I've over simplified the example code:
public class Form
{
public FieldValue[] FieldValues { get; set; }
}
public class Field
{
public bool IsRequired { get; set; }
}
public class FieldValue
{
public Field Field { get; set; }
public string Value { get; set; }
}
And our view looks something like:
#model Form
#using (Html.BeginForm("Create", "Form", FormMethod.Post))
{
#for(var i = 0; i < Model.Fields.Count(); i++)
{
#Html.TextBoxFor(_ => #Model.Fields[i].Value)
}
<input type="submit" value="Save" name="Submit" />
}
I was hoping that we'd be able to create a custom ModelValidatorProvider or ModelMetadataProvider class that would be able to analyze a FieldValue instance, determine if its Field.IsRequired property is true, and then add a RequiredFieldValidator to that specific instance's validators. I'm having no luck with this, though. It seems that with ModelValidatorProvider(and ModelMetadataProvider) you can't access the parent container's value(ie: GetValidators() will be called for FieldValue.Value, but there's no way from there to get the FieldValue object).
Things I've tried:
In the ModelValidatorProvider, I've tried using
ControllerContext.Controller.ViewData.Model, but that doesn't work if
you have nested types. If I'm trying to figure out the validators
Form.FieldValues[3], I have no idea which FieldValue to use.
I tried using a custom ModelMetadata that tries to use the internal
modelAccessor's Target property to get the parent, but this also
doesn't work if you have a nested type. Somewhere internal to MVC, an
expression like the one in my example will result in the Target being
the Model's type(Form), not FieldValue. So I get the same problem as
above where I have no idea what instance of FieldValue to compare
against.
A class-level validation attribute that I could put on the FieldValue
class itself, but this only gets called during server validation. I
need client-side validation, too.
Is what I'm trying to do even possible in MVC? Or is there something I'm missing entirely?
One possibility is to use a custom validation attribute.
But before getting into the implementation I would like to point out a potential flaw in your scenario. The IsRequired property is part of your model. This means that when the form is submitted its value must be known so that we conditionally apply the required rule to the corresponding property. But for this value to be known when the form is submitted this means that it must be either part of the form (as a hidden or standard input field) or must be retrieved from somewhere (datastore, ...). The problem with the first approach is obvious => hidden field means that the user can set whatever value he likes, so it's no longer a real validation because it is the user that decides which field is required.
This warning being said, let's suppose that you trust your users and decide to take the hidden field approach for storing the IsRequired value. Let's see how a sample implementation:
Model:
public class Form
{
public FieldValue[] Fields { get; set; }
}
public class FieldValue
{
public Field Field { get; set; }
[ConditionalRequired("Field")]
public string Value { get; set; }
}
public class Field
{
public bool IsRequired { get; set; }
}
Controller:
public class HomeController : Controller
{
public ActionResult Index()
{
var model = new Form
{
Fields = new[]
{
new FieldValue { Field = new Field { IsRequired = true }, Value = "" },
new FieldValue { Field = new Field { IsRequired = true }, Value = "" },
new FieldValue { Field = new Field { IsRequired = false }, Value = "value 3" },
}
};
return View(model);
}
[HttpPost]
public ActionResult Index(Form model)
{
return View(model);
}
}
View:
#model Form
#using (Html.BeginForm())
{
#Html.EditorFor(x => x.Fields)
<input type="submit" value="Save" name="Submit" />
}
ConditionalRequiredAttribute:
public class ConditionalRequiredAttribute : ValidationAttribute, IClientValidatable
{
private RequiredAttribute _innerAttribute = new RequiredAttribute();
private readonly string _fieldProperty;
public ConditionalRequiredAttribute(string fieldProperty)
{
_fieldProperty = fieldProperty;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var containerType = validationContext.ObjectInstance.GetType();
var field = containerType.GetProperty(_fieldProperty);
if (field == null)
{
return new ValidationResult(string.Format("Unknown property {0}", _fieldProperty));
}
var fieldValue = (Field)field.GetValue(validationContext.ObjectInstance, null);
if (fieldValue == null)
{
return new ValidationResult(string.Format("The property {0} was null", _fieldProperty));
}
if (fieldValue.IsRequired && !_innerAttribute.IsValid(value))
{
return new ValidationResult(this.ErrorMessage, new[] { validationContext.MemberName });
}
return ValidationResult.Success;
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var rule = new ModelClientValidationRule()
{
ErrorMessage = FormatErrorMessage(metadata.GetDisplayName()),
ValidationType = "conditionalrequired",
};
rule.ValidationParameters.Add("iserquiredproperty", _fieldProperty + ".IsRequired");
yield return rule;
}
}
Associated unobtrusive adapter:
(function ($) {
$.validator.unobtrusive.adapters.add('conditionalrequired', ['iserquiredproperty'], function (options) {
options.rules['conditionalrequired'] = options.params;
if (options.message) {
options.messages['conditionalrequired'] = options.message;
}
});
$.validator.addMethod('conditionalrequired', function (value, element, parameters) {
var name = $(element).attr('name'),
prefix = name.substr(0, name.lastIndexOf('.') + 1),
isRequiredFiledName = prefix + parameters.iserquiredproperty,
requiredElement = $(':hidden[name="' + isRequiredFiledName + '"]'),
isRequired = requiredElement.val().toLowerCase() === 'true';
if (!isRequired) {
return true;
}
return value && value !== '';
});
})(jQuery);

Categories

Resources