I am trying to conditionally validate the field within the MVC.NET Core. I have two radio buttons. If I select Yes (for the Ownership) I want to make a field below required (Activity dropdown)
However, no matter how hard I try, the value to be validated always comes from the Activity field, not from the Ownership field ("N\A" instead of "Yes")
Can somebody please tell me what I am doing wrong
The View (chtml)
<div class=" form-group">
<div class="bisformdynamiclabel"></div>
<br />
@Html.RadioButtonFor(model => model.BIS232Request.JSONData.OwnershipActivity.Ownership, "Yes", new { id = "OwnershipAnswer_true", onclick = "displayOwnershipFieldsRow(true)" })
<label for="OwnershipAnswer_true">Yes</label>
@Html.RadioButtonFor(model => model.BIS232Request.JSONData.OwnershipActivity.Ownership, "No", new { id = "OwnershipAnswer_false", onclick = "displayOwnershipFieldsRow(false)" })
<label for="OwnershipAnswer_false">No</label>
<span class="alert-danger">
@Html.ValidationMessage("OwnershipAnswer")
</span>
</div>
<div class="row ownershipfieldsrow">
<div class="col-xs-12 col-md-12">
<div class=" form-group">
<div class="bisformdynamiclabel"></div>
<br />
<input style="display:none" class="form-control" type="text" asp-for="BIS232Request.JSONData.OwnershipActivity.Activity" />
<select class="form-control ownershipactivityselect" onchange="$('#BIS232Request_JSONData_OwnershipActivity_Activity').val($(this).val()); ">
<option value="N/A">Please Select</option>
<option value="Manufacturer">Manufacturer</option>
<option value="Distributor">Distributor</option>
<option value="Exporter">Exporter</option>
<option value="Importer">Importer</option>
<option value="Other">Other</option>
</select>
<span asp-validation-for="BIS232Request.JSONData.OwnershipActivity.Activity" class="alert-danger"></span>
<span class="alert-danger">
@Html.ValidationMessage("OwnershipAnswerActivity")
</span>
</div>
</div>
The Model
[Required]
public string Ownership { get; set; }
[RequiredIf("Ownership", "OwnershipAnswer_true", "Activity is required if Ownership is selected")]
public string Activity { get; set; }
public class RequiredIfAttribute : ValidationAttribute
{
private String PropertyName { get; set; }
private String ErrorMessage { get; set; }
private Object DesiredValue { get; set; }
public RequiredIfAttribute(String propertyName, Object desiredvalue, String errormessage)
{
this.PropertyName = propertyName;
this.DesiredValue = desiredvalue;
this.ErrorMessage = errormessage;
}
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() && value == null)
{
return new ValidationResult(ErrorMessage);
}
return ValidationResult.Success;
}
}
To validate user's input we just need to annotate our viewmodel properties with Required attribute. Our ViewModel class will be like this: [Required(ErrorMessage = "Name is required")] public string Name { get; set; }
In ASP.NET MVC, Data Annotation is used for data validation for developing web-based applications. We can quickly apply validation with the help of data annotation attribute classes over model classes.
Based on the original implementation I'd recommend extending RequiredAttribute
rather than ValidationAttribute
- then your default ErrorMessage and other defaults are set as per [Required]. Either way the "errormessage" property is redundant as you already have this as a property of ValidationAttribute
and the original code generates a warning for the ErrorMessage
property - you can also use nameof
for the attribute decoration as well to keep things a lot tighter in your code:
My implementation is slightly more specific so that if a property is a bool I can indicate that a property is required (if say a checkbox is ticked):
[AttributeUsage(AttributeTargets.Property)]
public class RequiredIfTrueAttribute : RequiredAttribute
{
private string PropertyName { get; set; }
public RequiredIfTrueAttribute(string propertyName)
{
PropertyName = propertyName;
}
protected override ValidationResult IsValid(object value, ValidationContext context)
{
object instance = context.ObjectInstance;
Type type = instance.GetType();
bool.TryParse(type.GetProperty(PropertyName).GetValue(instance)?.ToString(), out bool propertyValue);
if (propertyValue && string.IsNullOrWhiteSpace(value?.ToString()))
{
return new ValidationResult(ErrorMessage);
}
return ValidationResult.Success;
}
}
Example Usage:
public bool IsBusinessProfile { get; set; }
[RequiredIfTrue(nameof(IsBusinessProfile), ErrorMessage = "ABN is required for Business Profiles")]
public string Abn { get; set; }
I built on the answer provided by Rob. This one is a generic validator instead of inheriting from Required
, and also provides client-side validation. I am using .Net Core 3.0
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
using System;
using System.Collections.Generic;
using System.Text;
namespace System.ComponentModel.DataAnnotations
{
[AttributeUsage(AttributeTargets.Property)]
public class RequiredIfTrueAttribute : ValidationAttribute, IClientModelValidator
{
private string PropertyName { get; set; }
public RequiredIfTrueAttribute(string propertyName)
{
PropertyName = propertyName;
ErrorMessage = "The {0} field is required."; //used if error message is not set on attribute itself
}
protected override ValidationResult IsValid(object value, ValidationContext context)
{
object instance = context.ObjectInstance;
Type type = instance.GetType();
bool.TryParse(type.GetProperty(PropertyName).GetValue(instance)?.ToString(), out bool propertyValue);
if (propertyValue && (value == null || string.IsNullOrWhiteSpace(value.ToString())))
{
return new ValidationResult(ErrorMessage);
}
return ValidationResult.Success;
}
public void AddValidation(ClientModelValidationContext context)
{
MergeAttribute(context.Attributes, "data-val", "true");
var errorMessage = FormatErrorMessage(context.ModelMetadata.GetDisplayName());
MergeAttribute(context.Attributes, "data-val-requirediftrue", errorMessage);
}
private bool MergeAttribute(IDictionary<string, string> attributes, string key, string value)
{
if (attributes.ContainsKey(key))
{
return false;
}
attributes.Add(key, value);
return true;
}
}
}
Client-Side Javascript
//Custom validation script for the RequiredIfTrue validator
/*
* Note that, jQuery validation registers its rules before the DOM is loaded.
* If you try to register your adapter after the DOM is loaded, your rules will
* not be processed. So wrap it in a self-executing function.
* */
(function ($) {
var $jQval = $.validator;
$jQval.addMethod("requirediftrue",
function (value, element, parameters) {
return value !== "" && value != null;
}
);
var adapters = $jQval.unobtrusive.adapters;
adapters.addBool('requirediftrue');
})(jQuery);
Usage
public bool IsSpecialField { get; set; }
[RequiredIfTrue(nameof(IsSpecialField), ErrorMessage="This is my custom error message")]
[Display(Name = "Address 1")]
public string Address1 { get; set; }
[RequiredIfTrue(nameof(IsSpecialField))]
public string City { get; set; }
Another, cleaner and more versatile, approach would be to implement a more generic attribute, not a specific "requiredIf" attribute, as you would have to make multiple custom attributes for every type of validation you happen to use.
Luckily, since .NET Core 2, Microsoft provides the IPropertyValidationFilter
interface, that you can implement on a custom attribute. This interface defines a function ShouldValidateEntry
, that allows control over whether the current entry should be validated or not; so this runs before any validators are called.
There is one default implementation in the Framework already, the ValidateNeverAttribute
, but it is trivial to implement your own that does a conditional check on another value:
using System;
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
namespace Foo {
// Implementation makes use of the IPropertyValidationFilter interface that allows
// control over whether the attribute (and its children, if relevant) need to be
// validated.
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public class ConditionalValidationAttribute : Attribute, IPropertyValidationFilter {
public string OtherProperty { get; set; }
public object OtherValue { get; set; }
public ConditionalValidationAttribute(string otherProperty, object otherValue) {
OtherProperty = otherProperty;
OtherValue = otherValue;
}
public bool ShouldValidateEntry(ValidationEntry entry, ValidationEntry parentEntry) {
// Default behaviour if no other property is set: continue validation
if (string.IsNullOrWhiteSpace(OtherProperty)) return true;
// Get the property specified by the name. Might not properly work with
// nested properties.
var prop = parentEntry.Metadata.Properties[OtherProperty]?.PropertyGetter?.Invoke(parentEntry.Model);
return prop == OtherValue;
}
}
}
Just annotate the relevant properties with this attribute and any validators, also custom validators you implemented yourself, will only be called when necessary!
Implementation example: here
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