Why does the Html.HiddenFor generate the data-val- attributes - asp.net-mvc-3

The standard output of #Html.HiddenFor(model => model.Id) is
<input data-val="true" data-val-number="The field Id must be a number." data-val-required="The Id field is required." id="Id" name="Id" type="hidden" value="0" />
Is there a need for the generation of the data-val-* attributes? They seem rather verbose and not needed just to be able to store and return data for the next POST.
Is it a good idea to disable these attributes? Are they usefull for some scenario's?
ps: Currently I have a way to disable them by temporarily setting ViewContext.UnobtrusiveJavaScriptEnabled = false
using these two classes:
public static class Extensions
{
public static NoUnobtrusiveJavaScript NoUnobtrusiveJavaScript(this HtmlHelper htmlHelper)
{
return new NoUnobtrusiveJavaScript(htmlHelper.ViewContext);
}
}
public class NoUnobtrusiveJavaScript: IDisposable
{
private bool _disposed;
private readonly bool _unobtrusiveJavaScript;
private readonly ViewContext _viewContext;
public NoUnobtrusiveJavaScript(ViewContext viewContext)
{
if (viewContext == null) {
throw new ArgumentNullException("viewContext");
}
_viewContext = viewContext;
_unobtrusiveJavaScript = viewContext.UnobtrusiveJavaScriptEnabled;
_viewContext.UnobtrusiveJavaScriptEnabled = false;
}
public void Dispose()
{
Dispose(true /* disposing */);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!_disposed)
{
_disposed = true;
// restore the original UnobtrusiveJavaScriptEnabled state
if (_viewContext != null)
{
_viewContext.UnobtrusiveJavaScriptEnabled = _unobtrusiveJavaScript;
}
}
}
public void EndForm()
{
Dispose(true);
}
}
* pattern following the Html.BeginForm code from FormExtensions.cs and MvcForm.cs

Just because the field is hidden, doesn't neccesarily mean you don't want the validation. You might be manipulating the hidden field via javascript, and want to keep the built in validation so you don't have to do your own.

Related

Blazor EditForm Validation not working when using Child Component

I have a Blazor component called EditOffice. It looks as follows:
<EditForm Model="#Office" OnValidSubmit="#HandleValidSubmit">
<DataAnnotationsValidator />
<ValidationSummary />
<InputTextRow Label="Name" #bind-Value="#Office.Name" Placeholder="Enter name" />
<InputTextRow Label="ABN" #bind-Value="#Office.ABN" Placeholder="Enter ABN" />
...
<button type="submit" class="btn btn-primary edit-btn">Save office</button>
</EditForm>
I created child components called InputTextRow in an attempt to Tidy my code. They look as follows:
<div class="form-group row">
<label for="#Id" class="col-sm-3">#Label: </label>
<InputText id="#Id" #oninput="OnValueChanged" #bind-Value="#Value" class="form-control col-sm-8" placeholder="#Placeholder"></InputText>
<ValidationMessage class="offset-sm-3 col-sm-8" For="#(() => Value)" />
</div>
#code {
public string Id => Label.ToLower().Replace(" ", "");
[Parameter]
public string Label { get; set; }
[Parameter]
public string Value { get; set; }
[Parameter]
public string Placeholder { get; set; }
[Parameter] public EventCallback<string> ValueChanged { get; set; }
Task OnValueChanged(ChangeEventArgs e)
{
Value = e.Value.ToString();
return ValueChanged.InvokeAsync(Value);
}
}
The ValidationMessage doesn't work when in my child component. Any idea why?
I know I'm a little late but here is my answer :)
So there is better solution right now.
TL:DR Solution for lazy ones
Be advised - it's experimental, but package is already in release candidate so no worries I guess.
Use Microsoft.AspNetCore.Components.DataAnnotations.Validation package and <ObjectGraphDataAnnotationsValidator /> instead of <DataAnnotationsValidator /> and use this thingy:
using System.ComponentModel.DataAnnotations;
public class YourComplexModel
{
// other properties
[ValidateComplexType] // <--life saver
public ChildModel ChildModel { get; set; } = new ChildModel();
}
Fragment from MS Docs
Link Microsoft Docs:
Blazor provides support for validating form input using data annotations with the built-in DataAnnotationsValidator. However, the DataAnnotationsValidator only validates top-level properties of the model bound to the form that aren't collection- or complex-type properties.
To validate the bound model's entire object graph, including collection- and complex-type properties, use the ObjectGraphDataAnnotationsValidator provided by the experimental Microsoft.AspNetCore.Components.DataAnnotations.Validation package:
<EditForm Model="#model" OnValidSubmit="#HandleValidSubmit">
<ObjectGraphDataAnnotationsValidator />
...
</EditForm>
Annotate model properties with [ValidateComplexType]. In the following model classes, the ShipDescription class contains additional data annotations to validate when the model is bound to the form:
Starship.cs:
using System;
using System.ComponentModel.DataAnnotations;
public class Starship
{
...
[ValidateComplexType]
public ShipDescription ShipDescription { get; set; } =
new ShipDescription();
...
}
ShipDescription.cs:
using System;
using System.ComponentModel.DataAnnotations;
public class ShipDescription
{
[Required]
[StringLength(40, ErrorMessage = "Description too long (40 char).")]
public string ShortDescription { get; set; }
[Required]
[StringLength(240, ErrorMessage = "Description too long (240 char).")]
public string LongDescription { get; set; }
}
I am having exactly the same issue. My code is very similar to yours. My child component do validate, but the validation error message is not displayed.
I do use this extension method:
using System;
using Microsoft.AspNetCore.Components.Forms;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace TimeRecording.Extensions
{
public static class EditContextExtensions
{
static PropertyInfo IsModifiedProperty;
static MethodInfo GetFieldStateMethod;
/// <summary>
/// Validates an entire object tree
/// </summary>
/// <param name="editContext">The EditContext to validate the Model of</param>
/// <returns>True if valid, otherwise false</returns>
public static bool ValidateObjectTree(this EditContext editContext)
{
var validatedObjects = new HashSet<object>();
ValidateObject(editContext, editContext.Model, validatedObjects);
editContext.NotifyValidationStateChanged();
return !editContext.GetValidationMessages().Any();
}
public static void ValidateProperty(this EditContext editContext, FieldIdentifier fieldIdentifier)
{
if (fieldIdentifier.Model == null)
return;
var propertyInfo = fieldIdentifier.Model.GetType().GetProperty(
fieldIdentifier.FieldName,
BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Static);
var validatedObjects = new HashSet<object>();
ValidateProperty(editContext, fieldIdentifier.Model, propertyInfo, validatedObjects);
}
private static void ValidateObject(
EditContext editContext,
object instance,
HashSet<object> validatedObjects)
{
if (instance == null)
return;
if (validatedObjects.Contains(instance))
return;
if (instance is IEnumerable && !(instance is string))
{
foreach (object value in (IEnumerable)instance)
ValidateObject(editContext, value, validatedObjects);
return;
}
if (instance.GetType().Assembly == typeof(string).Assembly)
return;
validatedObjects.Add(instance);
var properties = instance.GetType().GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
foreach (PropertyInfo property in properties)
ValidateProperty(editContext, instance, property, validatedObjects);
}
private static void ValidateProperty(
EditContext editContext,
object instance,
PropertyInfo property,
HashSet<object> validatedObjects)
{
NotifyPropertyChanged(editContext, instance, property.Name);
object value = property.GetValue(instance);
ValidateObject(editContext, value, validatedObjects);
}
private static void NotifyPropertyChanged(
EditContext editContext,
object instance,
string propertyName)
{
if (GetFieldStateMethod == null)
{
GetFieldStateMethod = editContext.GetType().GetMethod(
"GetFieldState",
BindingFlags.NonPublic | BindingFlags.Instance);
}
var fieldIdentifier = new FieldIdentifier(instance, propertyName);
object fieldState = GetFieldStateMethod.Invoke(editContext, new object[] { fieldIdentifier, true });
if (IsModifiedProperty == null)
{
IsModifiedProperty = fieldState.GetType().GetProperty(
"IsModified",
BindingFlags.Public | BindingFlags.Instance);
}
object originalIsModified = IsModifiedProperty.GetValue(fieldState);
editContext.NotifyFieldChanged(fieldIdentifier);
IsModifiedProperty.SetValue(fieldState, originalIsModified);
}
}
}

EmailAddressAttribute in MVC future project missing java-script for client side validation?

I am looking into this New EmailAddressAttribute part of their source code here. Code look like as follows. I am just wondering if there should be any client side script provided with it as this class implements IClientValidatable interface as well.
public sealed class EmailAddressAttribute : DataTypeAttribute, IClientValidatable {
private static Regex _regex = new Regex(#"^([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))#((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
public EmailAddressAttribute()
: base(DataType.EmailAddress) {
ErrorMessage = MvcResources.EmailAddressAttribute_Invalid;
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context) {
yield return new ModelClientValidationRule {
ValidationType = "email",
ErrorMessage = FormatErrorMessage(metadata.GetDisplayName())
};
}
public override bool IsValid(object value) {
if (value == null) {
return true;
}
string valueAsString = value as string;
return valueAsString != null && _regex.Match(valueAsString).Length > 0;
}
}
}
Client validation is done unobtrusively based on html5 attributes that are attached to the element being validated (i.e. <input type="text" data-val-required="The field is required." />).
The java-script is in a static library and is based on jquery validate.

RequiredIf Conditional Validation Attribute

I was looking for some advice on the best way to go about implementing a validation attribute that does the following.
Model
public class MyInputModel
{
[Required]
public int Id {get;set;}
public string MyProperty1 {get;set;}
public string MyProperty2 {get;set;}
public bool MyProperty3 {get;set;}
}
I want to have atleast prop1 prop2 prop3 with a value and if prop3 is the only value filled it it should not equal false.
How would i go about writing a validation attribute(s?) for this?
Thanks for any help!
I had the same problem yesterday, but I did it in a very clean way which works for both client side and server side validation.
Condition: Based on the value of other property in the model, you want to make another property required. Here is the code:
public class RequiredIfAttribute : RequiredAttribute
{
private String PropertyName { get; set; }
private Object DesiredValue { get; set; }
public RequiredIfAttribute(String propertyName, Object desiredvalue)
{
PropertyName = propertyName;
DesiredValue = desiredvalue;
}
protected override ValidationResult IsValid(object value, ValidationContext context)
{
Object instance = context.ObjectInstance;
Type type = instance.GetType();
Object proprtyvalue = type.GetProperty(PropertyName).GetValue(instance, null);
if (proprtyvalue.ToString() == DesiredValue.ToString())
{
ValidationResult result = base.IsValid(value, context);
return result;
}
return ValidationResult.Success;
}
}
PropertyName is the property on which you want to make your condition
DesiredValue is the particular value of the PropertyName (property) for which your other property has to be validated for required
Say you have the following:
public enum UserType
{
Admin,
Regular
}
public class User
{
public UserType UserType {get;set;}
[RequiredIf("UserType",UserType.Admin,
ErrorMessageResourceName="PasswordRequired",
ErrorMessageResourceType = typeof(ResourceString))]
public string Password { get; set; }
}
At last but not the least, register adapter for your attribute so that it can do client side validation (I put it in global.asax, Application_Start)
DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(RequiredIfAttribute),
typeof(RequiredAttributeAdapter));
EDITED
Some people have reported issues that the client side fires no matter what or it does not work. So I modified the above code to do conditional client side validation with Javascript as well. For this case you don't need to register adapter
public class RequiredIfAttribute : ValidationAttribute, IClientValidatable
{
private String PropertyName { get; set; }
private Object DesiredValue { get; set; }
private readonly RequiredAttribute _innerAttribute;
public RequiredIfAttribute(String propertyName, Object desiredvalue)
{
PropertyName = propertyName;
DesiredValue = desiredvalue;
_innerAttribute = new RequiredAttribute();
}
protected override ValidationResult IsValid(object value, ValidationContext context)
{
var dependentValue = context.ObjectInstance.GetType().GetProperty(PropertyName).GetValue(context.ObjectInstance, null);
if (dependentValue.ToString() == DesiredValue.ToString())
{
if (!_innerAttribute.IsValid(value))
{
return new ValidationResult(FormatErrorMessage(context.DisplayName), new[] { context.MemberName });
}
}
return ValidationResult.Success;
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var rule = new ModelClientValidationRule
{
ErrorMessage = ErrorMessageString,
ValidationType = "requiredif",
};
rule.ValidationParameters["dependentproperty"] = (context as ViewContext).ViewData.TemplateInfo.GetFullHtmlFieldId(PropertyName);
rule.ValidationParameters["desiredvalue"] = DesiredValue is bool ? DesiredValue.ToString().ToLower() : DesiredValue;
yield return rule;
}
}
And finally the javascript ( bundle it and renderit...put it in its own script file)
$.validator.unobtrusive.adapters.add('requiredif', ['dependentproperty', 'desiredvalue'], function (options) {
options.rules['requiredif'] = options.params;
options.messages['requiredif'] = options.message;
});
$.validator.addMethod('requiredif', function (value, element, parameters) {
var desiredvalue = parameters.desiredvalue;
desiredvalue = (desiredvalue == null ? '' : desiredvalue).toString();
var controlType = $("input[id$='" + parameters.dependentproperty + "']").attr("type");
var actualvalue = {}
if (controlType == "checkbox" || controlType == "radio") {
var control = $("input[id$='" + parameters.dependentproperty + "']:checked");
actualvalue = control.val();
} else {
actualvalue = $("#" + parameters.dependentproperty).val();
}
if ($.trim(desiredvalue).toLowerCase() === $.trim(actualvalue).toLocaleLowerCase()) {
var isValid = $.validator.methods.required.call(this, value, element, parameters);
return isValid;
}
return true;
});
You need obviously the unobstrusive validate jQuery to be included as requirement
I know the topic was asked some time ago, but recently I had faced similar issue and found yet another, but in my opinion a more complete solution. I decided to implement mechanism which provides conditional attributes to calculate validation results based on other properties values and relations between them, which are defined in logical expressions.
Using it you are able to achieve the result you asked about in the following manner:
[RequiredIf("MyProperty2 == null && MyProperty3 == false")]
public string MyProperty1 { get; set; }
[RequiredIf("MyProperty1 == null && MyProperty3 == false")]
public string MyProperty2 { get; set; }
[AssertThat("MyProperty1 != null || MyProperty2 != null || MyProperty3 == true")]
public bool MyProperty3 { get; set; }
More information about ExpressiveAnnotations library can be found here. It should simplify many declarative validation cases without the necessity of writing additional case-specific attributes or using imperative way of validation inside controllers.
I got it to work on ASP.NET MVC 5
I saw many people interested in and suffering from this code and i know it's really confusing and disrupting for the first time.
Notes
worked on MVC 5 on both server and client side :D
I didn't install "ExpressiveAnnotations" library at all.
I'm taking about the Original code from "#Dan Hunex", Find him above
Tips To Fix This Error
"The type System.Web.Mvc.RequiredAttributeAdapter must have a public constructor which accepts three parameters of types System.Web.Mvc.ModelMetadata, System.Web.Mvc.ControllerContext, and ExpressiveAnnotations.Attributes.RequiredIfAttribute Parameter name: adapterType"
Tip #1: make sure that you're inheriting from 'ValidationAttribute' not from 'RequiredAttribute'
public class RequiredIfAttribute : ValidationAttribute, IClientValidatable { ...}
Tip #2: OR remove this entire line from 'Global.asax', It is not needed at all in the newer version of the code (after edit by #Dan_Hunex), and yes this line was a must in the old version ...
DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(RequiredIfAttribute), typeof(RequiredAttributeAdapter));
Tips To Get The Javascript Code Part Work
1- put the code in a new js file (ex:requiredIfValidator.js)
2- warp the code inside a $(document).ready(function(){........});
3- include our js file after including the JQuery validation libraries, So it look like this now :
#Scripts.Render("~/bundles/jqueryval")
<script src="~/Content/JS/requiredIfValidator.js"></script>
4- Edit the C# code
from
rule.ValidationParameters["dependentproperty"] = (context as ViewContext).ViewData.TemplateInfo.GetFullHtmlFieldId(PropertyName);
to
rule.ValidationParameters["dependentproperty"] = PropertyName;
and from
if (dependentValue.ToString() == DesiredValue.ToString())
to
if (dependentValue != null && dependentValue.ToString() == DesiredValue.ToString())
My Entire Code Up and Running
Global.asax
Nothing to add here, keep it clean
requiredIfValidator.js
create this file in ~/content or in ~/scripts folder
$.validator.unobtrusive.adapters.add('requiredif', ['dependentproperty', 'desiredvalue'], function (options)
{
options.rules['requiredif'] = options.params;
options.messages['requiredif'] = options.message;
});
$(document).ready(function ()
{
$.validator.addMethod('requiredif', function (value, element, parameters) {
var desiredvalue = parameters.desiredvalue;
desiredvalue = (desiredvalue == null ? '' : desiredvalue).toString();
var controlType = $("input[id$='" + parameters.dependentproperty + "']").attr("type");
var actualvalue = {}
if (controlType == "checkbox" || controlType == "radio") {
var control = $("input[id$='" + parameters.dependentproperty + "']:checked");
actualvalue = control.val();
} else {
actualvalue = $("#" + parameters.dependentproperty).val();
}
if ($.trim(desiredvalue).toLowerCase() === $.trim(actualvalue).toLocaleLowerCase()) {
var isValid = $.validator.methods.required.call(this, value, element, parameters);
return isValid;
}
return true;
});
});
_Layout.cshtml or the View
#Scripts.Render("~/bundles/jqueryval")
<script src="~/Content/JS/requiredIfValidator.js"></script>
RequiredIfAttribute.cs Class
create it some where in your project, For example in ~/models/customValidation/
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;
namespace Your_Project_Name.Models.CustomValidation
{
public class RequiredIfAttribute : ValidationAttribute, IClientValidatable
{
private String PropertyName { get; set; }
private Object DesiredValue { get; set; }
private readonly RequiredAttribute _innerAttribute;
public RequiredIfAttribute(String propertyName, Object desiredvalue)
{
PropertyName = propertyName;
DesiredValue = desiredvalue;
_innerAttribute = new RequiredAttribute();
}
protected override ValidationResult IsValid(object value, ValidationContext context)
{
var dependentValue = context.ObjectInstance.GetType().GetProperty(PropertyName).GetValue(context.ObjectInstance, null);
if (dependentValue != null && dependentValue.ToString() == DesiredValue.ToString())
{
if (!_innerAttribute.IsValid(value))
{
return new ValidationResult(FormatErrorMessage(context.DisplayName), new[] { context.MemberName });
}
}
return ValidationResult.Success;
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var rule = new ModelClientValidationRule
{
ErrorMessage = ErrorMessageString,
ValidationType = "requiredif",
};
rule.ValidationParameters["dependentproperty"] = PropertyName;
rule.ValidationParameters["desiredvalue"] = DesiredValue is bool ? DesiredValue.ToString().ToLower() : DesiredValue;
yield return rule;
}
}
}
The Model
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Web;
using Your_Project_Name.Models.CustomValidation;
namespace Your_Project_Name.Models.ViewModels
{
public class CreateOpenActivity
{
public Nullable<int> ORG_BY_CD { get; set; }
[RequiredIf("ORG_BY_CD", "5", ErrorMessage = "Coordinator ID is required")] // This means: IF 'ORG_BY_CD' is equal 5 (for the example) > make 'COR_CI_ID_NUM' required and apply its all validation / data annotations
[RegularExpression("[0-9]+", ErrorMessage = "Enter Numbers Only")]
[MaxLength(9, ErrorMessage = "Enter a valid ID Number")]
[MinLength(9, ErrorMessage = "Enter a valid ID Number")]
public string COR_CI_ID_NUM { get; set; }
}
}
The View
Nothing to note here actually ...
#model Your_Project_Name.Models.ViewModels.CreateOpenActivity
#{
ViewBag.Title = "Testing";
}
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>CreateOpenActivity</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.ORG_BY_CD, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.ORG_BY_CD, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.ORG_BY_CD, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.COR_CI_ID_NUM, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.COR_CI_ID_NUM, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.COR_CI_ID_NUM, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
</div>
}
I may upload a project sample for this later ...
Hope this was helpful
Thank You
If you try to use "ModelState.Remove" or "ModelState["Prop"].Errors.Clear()" the "ModelState.IsValid" stil returns false.
Why not just removing the default "Required" Annotation from Model and make your custom validation before the "ModelState.IsValid" on Controller 'Post' action? Like this:
if (!String.IsNullOrEmpty(yourClass.Property1) && String.IsNullOrEmpty(yourClass.dependantProperty))
ModelState.AddModelError("dependantProperty", "It´s necessary to select some 'dependant'.");
Expanding on the notes from Adel Mourad and Dan Hunex, I amended the code to provide an example that only accepts values that do not match the given value.
I also found that I didn't need the JavaScript.
I added the following class to my Models folder:
public class RequiredIfNotAttribute : ValidationAttribute, IClientValidatable
{
private String PropertyName { get; set; }
private Object InvalidValue { get; set; }
private readonly RequiredAttribute _innerAttribute;
public RequiredIfNotAttribute(String propertyName, Object invalidValue)
{
PropertyName = propertyName;
InvalidValue = invalidValue;
_innerAttribute = new RequiredAttribute();
}
protected override ValidationResult IsValid(object value, ValidationContext context)
{
var dependentValue = context.ObjectInstance.GetType().GetProperty(PropertyName).GetValue(context.ObjectInstance, null);
if (dependentValue.ToString() != InvalidValue.ToString())
{
if (!_innerAttribute.IsValid(value))
{
return new ValidationResult(FormatErrorMessage(context.DisplayName), new[] { context.MemberName });
}
}
return ValidationResult.Success;
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var rule = new ModelClientValidationRule
{
ErrorMessage = ErrorMessageString,
ValidationType = "requiredifnot",
};
rule.ValidationParameters["dependentproperty"] = (context as ViewContext).ViewData.TemplateInfo.GetFullHtmlFieldId(PropertyName);
rule.ValidationParameters["invalidvalue"] = InvalidValue is bool ? InvalidValue.ToString().ToLower() : InvalidValue;
yield return rule;
}
I didn't need to make any changes to my view, but did make a change to the properties of my model:
[RequiredIfNot("Id", 0, ErrorMessage = "Please select a Source")]
public string TemplateGTSource { get; set; }
public string TemplateGTMedium
{
get
{
return "Email";
}
}
[RequiredIfNot("Id", 0, ErrorMessage = "Please enter a Campaign")]
public string TemplateGTCampaign { get; set; }
[RequiredIfNot("Id", 0, ErrorMessage = "Please enter a Term")]
public string TemplateGTTerm { get; set; }
Hope this helps!
The main difference from other solutions here is that this one reuses logic in RequiredAttribute on the server side, and uses required's validation method depends property on the client side:
public class RequiredIf : RequiredAttribute, IClientValidatable
{
public string OtherProperty { get; private set; }
public object OtherPropertyValue { get; private set; }
public RequiredIf(string otherProperty, object otherPropertyValue)
{
OtherProperty = otherProperty;
OtherPropertyValue = otherPropertyValue;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
PropertyInfo otherPropertyInfo = validationContext.ObjectType.GetProperty(OtherProperty);
if (otherPropertyInfo == null)
{
return new ValidationResult($"Unknown property {OtherProperty}");
}
object otherValue = otherPropertyInfo.GetValue(validationContext.ObjectInstance, null);
if (Equals(OtherPropertyValue, otherValue)) // if other property has the configured value
return base.IsValid(value, validationContext);
return null;
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var rule = new ModelClientValidationRule();
rule.ErrorMessage = FormatErrorMessage(metadata.GetDisplayName());
rule.ValidationType = "requiredif"; // data-val-requiredif
rule.ValidationParameters.Add("other", OtherProperty); // data-val-requiredif-other
rule.ValidationParameters.Add("otherval", OtherPropertyValue); // data-val-requiredif-otherval
yield return rule;
}
}
$.validator.unobtrusive.adapters.add("requiredif", ["other", "otherval"], function (options) {
var value = {
depends: function () {
var element = $(options.form).find(":input[name='" + options.params.other + "']")[0];
return element && $(element).val() == options.params.otherval;
}
}
options.rules["required"] = value;
options.messages["required"] = options.message;
});
I think using IValidatableObject is a good choice.
public class MyInputModel : IValidateObject
{
[Required]
public int Id {get;set;}
public string MyProperty1 {get;set;}
public string MyProperty2 {get;set;}
public bool MyProperty3 {get;set;}
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (MyProperty1==null&&MyProperty2==null&&MyPropterty3!=false) //whatever condition
{
yield return new ValidationResult(
"Custom complex error");
}
}
}

MVC3 custom unobtrusive validation - force validation from checkbox

I have the following class used for custom validation:
[AttributeUsage(AttributeTargets.Property, AllowMultiple=false, Inherited=true)]
public sealed class RequiredIfAnyTrueAttribute : ValidationAttribute, IClientValidatable
{
private const string DefaultErrorMessage = "{0} is required";
public List<string> OtherProperties { get; private set; }
public RequiredIfAnyTrueAttribute(string otherProperties)
: base(DefaultErrorMessage)
{
if (string.IsNullOrEmpty(otherProperties))
throw new ArgumentNullException("otherProperty");
OtherProperties = new List<string>(otherProperties.Split(new char[] { '|', ',' }));
}
public override string FormatErrorMessage(string name)
{
return string.Format(ErrorMessageString, name);
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (value == null)
{
foreach (string s in OtherProperties)
{
var otherProperty = validationContext.ObjectType.GetProperty(s);
var otherPropertyValue = otherProperty.GetValue(validationContext.ObjectInstance, null);
if (otherPropertyValue.Equals(true))
return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
}
}
return ValidationResult.Success;
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var clientValidationRule = new ModelClientValidationRule()
{
ErrorMessage = FormatErrorMessage(metadata.GetDisplayName()),
ValidationType = "requiredifanytrue"
};
clientValidationRule.ValidationParameters.Add("otherproperties", string.Join("|",OtherProperties));
return new[] { clientValidationRule };
}
}
My ViewModel is:
public class SampleViewModel
{
public bool PropABC { get; set; }
public bool PropXYZ { get; set; }
[RequiredIfAnyTrue("PropABC|PropXYZ")]
public int? TestField { get; set; }
}
When my strongly typed view renders, everything sees to work fine. If PropABC or PropXYZ is selected then I am required to enter a value for TestField. Both client and server-side validation is functional.
However, given the following sequence of events:
check PropABC
submit form
client-side validation fires for TestField required
uncheck PropABC
client validation does not re-fire and validation message
remains until form submit
In order to resolve #5 I would typically attach click events to the checkboxes via jquery onready to refire the validation.
Is there a preferred/recommended way to manually force client-side validation given MVC3 + unobstrusive + jquery?
Shawn, attaching to events is the best approach to get validation to refire.
I would suggest creating a class called "validate" (or something along those lines), adding it to each element to be validated, and then use jQuery to attach to the click and blur events (and possibly the change event) of each element with that class, and validate the element like so:
$("form").validate().element(this);
Do you need to write your own attributes? If not I think you may be able to avoid "reinventing the wheel"
FoolProof works great. you get get it as a NuGet package.
NuGet: install-package foolproof
It includes a lot of greate attributes for various combinations of on-the-fly required fields and such.
FoolProof is still in beta and does not work with nested viewmodel, also with arrays

MVC3 Validation with Lightspeed

My ORM (LightSpeed) generates this for Animals table, with Name and Age. Using MVC3 and Razor
[Serializable]
[System.CodeDom.Compiler.GeneratedCode("LightSpeedModelGenerator", "1.0.0.0")]
[System.ComponentModel.DataObject]
[Table(IdColumnName="AnimalID", IdentityMethod=IdentityMethod.IdentityColumn)]
public partial class Animal : Entity<int>
{
[ValidatePresence]
[ValidateLength(0, 50)]
private string _name;
[ValidateComparison(ComparisonOperator.GreaterThan, 0)]
private int _age;
public const string NameField = "Name";
public const string AgeField = "Age";
[System.Diagnostics.DebuggerNonUserCode]
[Required] // ****I put this in manually to get Name required working
public string Name
{
get { return Get(ref _name, "Name"); }
set { Set(ref _name, value, "Name"); }
}
[System.Diagnostics.DebuggerNonUserCode]
public int Age
{
get { return Get(ref _age, "Age"); }
set { Set(ref _age, value, "Age"); }
}
With [Required] attribute added:
With no [Required] attribute added: (notice LightSpeed strange rendering of validation)
With name filled in:
In images above - the validation at the top is LightSpeed (put into ValidationSummary) and at the side is MVC3 (put into ValidationMessageFor)
Am only using Server Side validation currently.
Question: How do I get LightSpeed validation working well in MVC3?
I think it is something in this area http://www.mindscapehq.com/staff/jeremy/index.php/2009/03/aspnet-mvc-part4/
For the server side validation - you will want to use a custom model binder which emits the errors from LightSpeed validation more precisely rather than the leveraging the DefaultModelBinder behavior. Have a look at either directly using or adapting the EntityModelBinder from the community code library for Mvc
http://www.mindscapehq.com/forums/Thread.aspx?PostID=12051
See link http://www.mindscapehq.com/forums/Thread.aspx?ThreadID=4093
Jeremys answer (Mindscape have great support!)
public class EntityModelBinder2 : DefaultModelBinder
{
public static void Register(Assembly assembly)
{
ModelBinders.Binders.Add(typeof(Entity), new EntityModelBinder2());
foreach (Type type in assembly.GetTypes())
{
if (typeof(Entity).IsAssignableFrom(type))
{
ModelBinders.Binders.Add(type, new EntityModelBinder2());
}
}
}
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
object result = base.BindModel(controllerContext, bindingContext);
if (typeof(Entity).IsAssignableFrom(bindingContext.ModelType))
{
Entity entity = (Entity)result;
if (!entity.IsValid)
{
foreach (var state in bindingContext.ModelState.Where(s => s.Value.Errors.Count > 0))
{
state.Value.Errors.Clear();
}
foreach (var error in entity.Errors)
{
if (error.ErrorMessage.EndsWith("is invalid")) continue;
bindingContext.ModelState.AddModelError(error.PropertyName ?? "Custom", error.ErrorMessage);
}
}
}
return result;
}
}
and in Global.asax register using:
EntityModelBinder2.Register(typeof(MyEntity).Assembly);
The Register call sets up the model binder to be used for each entity type in your model assembly so modify as required.
You can get client side validation working with Lightspeed nightly builds from 04/04/2011 onwards.
Create a validator provider as follows:
public class LightspeedModelValidatorProvider : DataAnnotationsModelValidatorProvider
{
private string GetDisplayName(string name)
{
return name; // go whatever processing is required, eg decamelise, replace "_" with " " etc
}
protected override IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context, IEnumerable<Attribute> attributes)
{
if(typeof(Entity).IsAssignableFrom(metadata.ContainerType))
{
List<Attribute> newAttributes = new List<Attribute>(attributes);
var attr = DataAnnotationBuilder.GetDataAnnotations(metadata.ContainerType, metadata.PropertyName, GetDisplayName(metadata.PropertyName));
newAttributes.AddRange(attr);
return base.GetValidators(metadata, context, newAttributes);
}
return base.GetValidators(metadata, context, attributes);
}
}
Then in Application_Start() add
ModelValidatorProviders.Providers.Clear();
ModelValidatorProviders.Providers.Add(new LightspeedModelValidatorProvider());

Resources