I am implementing a RequiredIf validation attribute and the value being passed to the IsValid method is always null.
RequiredIfAttribute Class
public class RequiredIfAttribute : ValidationAttribute
{
private RequiredAttribute innerAttribute = new RequiredAttribute();
public string DependentProperty { get; set; }
public object TargetValue { get; set; }
public RequiredIfAttribute(string dependentProperty, object targetValue)
{
this.DependentProperty = dependentProperty;
this.TargetValue = targetValue;
}
public override bool IsValid(object value)
{
return innerAttribute.IsValid(value);
}
}
ViewModel
[Required]
[Display(Name = "Are You A Student?")]
public bool? IsStudent { get; set; }
[RequiredIf("IsStudent", true, ErrorMessage = "You must upload a photo of your student ID if you wish to register as a student.")]
[Display(Name = "Student ID")]
[FileExtensions("jpg|jpeg|png|pdf", ErrorMessage = "File is not in the correct format.")]
[MaxFileSize(2 * 1024 * 1024, ErrorMessage = "You may not upload files larger than 2 MB.")]
public HttpPostedFileBase StudentId { get; set; }
EditorTemplate
#model bool?
#using System.Web.Mvc;
#{
var options = new List<SelectListItem>
{
new SelectListItem { Text = "Yes", Value = "true", Selected = Model.HasValue && Model.Value },
new SelectListItem { Text = "No", Value = "false", Selected = Model.HasValue && Model.Value }
};
string defaultOption = null;
if (ViewData.ModelMetadata.IsNullableValueType)
{
defaultOption = "(Select)";
}
}
#Html.DropDownListFor(m => m, options, defaultOption)
Every time the form is submitted, the RequiredIf error message is thrown and I have a feeling it has to do with the null value I described initially. What am I doing wrong? Thanks!
NOTE: The HTML appears to be rendering properly, so I don't think that's the problem.
<select data-val="true" data-val-required="The Are You A Student? field is required." id="IsStudent" name="IsStudent"><option value="">(Select)</option>
<option value="true">Yes</option>
<option value="false">No</option>
</select>
Because this is your code -
public class RequiredIfAttribute : ValidationAttribute
{
private RequiredAttribute innerAttribute = new RequiredAttribute();
public string DependentProperty { get; set; }
public object TargetValue { get; set; }
public RequiredIfAttribute(string dependentProperty, object targetValue)
{
this.DependentProperty = dependentProperty;
this.TargetValue = targetValue;
}
public override bool IsValid(object value)
{
return innerAttribute.IsValid(value);
}
}
You are using a RequriedAtrribute. So to simulate that it behaves life a RequiredIf you have to implement some logic that will check whether the target property value is true or false. But you are not doing that and returning just from the innerattribute. So it is just a mere Required not RequiredIf -
public override bool IsValid(object value)
{
return innerAttribute.IsValid(value);
}
modify this function to do some checking like -
public override bool IsValid(object value)
{
//if the referred property is true then
return innerAttribute.IsValid(value);
//else
return True
}
I use the following code:
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true)]
public abstract class StefAttribute : ValidationAttribute
{
public WDCIAttribute()
: base()
{
this.ErrorMessageResourceType = typeof(GlobalResources);
}
}
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true, Inherited = true)]
public class StefRequiredIfAttribute : StefAttribute
{
private RequiredAttribute innerAttribute = new RequiredAttribute();
public string DependentProperty { get; set; }
public object TargetValue { get; set; }
public WDCIRequiredIfAttribute()
{
}
public WDCIRequiredIfAttribute(string dependentProperty, object targetValue)
: base()
{
this.DependentProperty = dependentProperty;
this.TargetValue = targetValue;
}
public override bool IsValid(object value)
{
return innerAttribute.IsValid(value);
}
}
public class RequiredIfValidator : DataAnnotationsModelValidator<StefRequiredIfAttribute>
{
public RequiredIfValidator(ModelMetadata metadata, ControllerContext context, StefRequiredIfAttribute attribute)
: base(metadata, context, attribute)
{
}
public override IEnumerable<ModelValidationResult> Validate(object container)
{
// get a reference to the property this validation depends upon
var field = Metadata.ContainerType.GetProperty(Attribute.DependentProperty);
if (field != null)
{
// get the value of the dependent property
object value = field.GetValue(container, null);
// compare the value against the target value
if (IsEqual(value) || (value == null && Attribute.TargetValue == null))
{
// match => means we should try validating this field
if (!Attribute.IsValid(Metadata.Model))
{
// validation failed - return an error
yield return new ModelValidationResult { Message = ErrorMessage };
}
}
}
}
private bool IsEqual(object dependentPropertyValue)
{
bool isEqual = false;
if (Attribute.TargetValue != null && Attribute.TargetValue.GetType().IsArray)
{
foreach (object o in (Array)Attribute.TargetValue)
{
isEqual = o.Equals(dependentPropertyValue);
if (isEqual)
{
break;
}
}
}
else
{
if (Attribute.TargetValue != null)
{
isEqual = Attribute.TargetValue.Equals(dependentPropertyValue);
}
}
return isEqual;
}
}
Which can be used in the model as follows:
public class PersonnelVM : EntityVM
{
// . . .
[DisplayName("Name")]
[StefRequiredIf("IndividualOrBulk", PersonnelType.Bulk, ErrorMessageResourceName = GlobalResourceLiterals.Name_Required)]
public string Name { get; set; }
[DisplayName("PersonnelType")]
public PersonnelType IndividualOrBulk { get; set; }
// . . .
}
Related
In my app, I fetch two attributes IsLoopCar and LoopStatusSet from a DB and Store them to a model class:
private static ChecklistModel MapChecklistModel(DataRow row)
{
var checklist = new ChecklistModel
{
IsLoopCar = Convert.IsDBNull(row["IsLoopCar"]) ? null : Convert.ToBoolean(row["IsLoopCar"]),
LoopStatusSet = Convert.IsDBNull(row["LoopStatusSet"]) ? null : Convert.ToBoolean(row["LoopStatusSet"])
}
}
In my model class and the viewModel I map my model class to, the data type of these two attributes is bool?
public class ChecklistModel
{
public bool? IsLoopCar { get; set; }
public bool? LoopStatusSet { get; set; }
}
public class GetPaymentInformationViewModel
{
public bool? IsLoopCar { get; set; }
public bool? LoopStatusSet { get; set; }
}
The value of the attribute in my DB is null. When I map the model to the viewmodel using AutoMapper (7.0.1), I get an error message which I don't understand:
GetPaymentInformationViewModel gpivm = _mapper.Map<ChecklistModel, GetPaymentInformationViewModel>(checklist);
The mapping configuration is very basic:
CreateMap<ChecklistModel, GetPaymentInformationViewModel>();
The error message is:
InvalidOperationException: Nullable object must have a value.
I don't understand what's the point in mapping nullable bools, when they have to have a value. I need to have this value to be null when i pass it to the view because it works as kind of a checkpoint where the user must select a yes/no value with no initial default value set.
Based on the recommendation of Lucian, I tried to reproduce the problem with a fresh console app but it didn't fail there. As it turned out, the problem was in my view model where I had written a custom setter that makes no sense:
[Required]
[Display(Name = "IsLoopCar")]
public bool? IsLoopCar
{
get
{
return _IsLoopCar;
}
set
{
if (value.Value == true)
_IsLoopCar = value.Value;
else
{
_IsLoopCar = value.Value;
LoopStatusSet = null;
}
}
}
The field value.Value cannot be read because it does not exists. It's supposed to be like that:
[Required]
[Display(Name = "IsLoopCar")]
public bool? IsLoopCar
{
get
{
return _IsLoopCar;
}
set
{
if (value == true)
_IsLoopCar = value;
else
{
_IsLoopCar = value;
LoopStatusSet = null;
}
}
}
I am trying to make a custom file validation for my project which is based on .net core 2.
I want to validate file size and also file extension in order to prevent users from uploading large files and for example .png files.
I have searched a lot but I could not find anything that works.
Here is my file validation class :
public class FileTypeAttribute : ValidationAttribute, IClientModelValidator
{
private const int MaxSize = 1048576;
private const string _DefaultErrorMessage = "Only the following file types are allowed: {0}";
private IEnumerable<string> _ValidTypes { get; set; }
public string ValidTypes { get; set; }
public string ErrorMessageExtension { get; set; }
public string ErrorMessageSize { get; set; }
public FileTypeAttribute(string errorExtension, string errorSize, string vt)
{
ErrorMessageExtension = errorExtension;
ErrorMessageSize = errorSize;
_ValidTypes = vt.Split(',');
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
IFormFile file = value as IFormFile;
if (file != null)
{
if (!_ValidTypes.Any(e => file.FileName.EndsWith(e)))
{
return new ValidationResult(ErrorMessageExtension);
}
if (file.Length > MaxSize)
{
return new ValidationResult(ErrorMessageSize);
}
}
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-fileextensions", ErrorMessageExtension);
MergeAttribute(context.Attributes, "data-val-maxfilesize", ErrorMessageSize);
}
private bool MergeAttribute(
IDictionary<string, string> attributes, string key, string value)
{
if (attributes.ContainsKey(key))
{
return false;
}
attributes.Add(key, value);
return true;
}
}
and here is the javascript code in my view:
$.validator.addMethod("FileType",
function (value, element, param) {
for (var i = 0; i < element.files.length; i++) {
var extension = getFileExtension(element.files[0].name);
if ($.inArray(extension, param.validtypes) === -1) {
return false;
}
}
return true;
});
$.validator.unobtrusive.adapters.add('FileType', ['validtypes'], function (options) {
console.log("value:");
options.rules.cannotbered = {};
options.messages["FileType"] = options.message;
});
function getFileExtension(fileName) {
if (/[.]/.exec(fileName)) {
return /[^.]+$/.exec(fileName)[0].toLowerCase();
}
return null;
}
and here is the entity class code in my project:
public class MyEntityClass
{
public int MyEntityClassId { get; set; }
[FileType("invalid format", "invalid size", "jpg,png,gif")]
public IFormFile Photo { get; set; }
}
Can anyone help me to know where the problem is?
Thanks in advance.
I would need RequiredIfNull attribute for model validation.
How can I add conditional Required attribute. Condition would depend on another property. If that property value is null, then this shouldn't be.
Something like that:
public class MyModel
{
public int? prop1 { get; set; }
[ConditionalRequired(prop1)] //if prop1 == null, then prop2 is required, otherwise MyModel is invalid
public int? prop2 { get; set; }
}
You need a custom validation attribute. For example:
using System.ComponentModel.DataAnnotations;
using System.Reflection;
[AttributeUsage(AttributeTargets.Property)]
public class RequiredIfNullAttribute : ValidationAttribute
{
private const string DefaultErrorMessageFormat = "The {0} field is required.";
public RequiredIfNullAttribute(string otherProperty)
{
if (otherProperty == null)
{
throw new ArgumentNullException(nameof(otherProperty));
}
OtherProperty = otherProperty;
ErrorMessage = DefaultErrorMessageFormat;
}
public string OtherProperty { get; }
protected override ValidationResult IsValid(object value,
ValidationContext validationContext)
{
if (value == null)
{
var otherProperty = validationContext.ObjectInstance.
GetType().GetProperty(OtherProperty);
object otherPropertyValue = otherProperty.GetValue(
validationContext.ObjectInstance, null);
if (otherPropertyValue == null)
{
return new ValidationResult(
string.Format(ErrorMessageString, validationContext.DisplayName));
}
}
return ValidationResult.Success;
}
}
Then in your model:
public class MyModel
{
public int? prop1 { get; set; }
[RequiredIfNull(nameof(prop1))]
public int? prop2 { get; set; }
}
Things will get more complex if you also need to add client side validation.
I am having model as
public class GroupedIssueData
{
[Range(0, double.MaxValue, ErrorMessage = "Please enter valid number")]
public double IssueQty { get; set; }
public double ReqQty { get; set; }
public bool isSavings { get; set; }
}
This contains two properties as IssueQty and IsSaving, If the IsSaving is checked then IssueQty can be empty, If the IssueQty is not empty then IsSaving can be left blank. How can I validate this
My View is
<td>
#Html.DisplayFor(m => m.MaterialData[i].ReqQty)
#Html.HiddenFor(m => m.MaterialData[i].ReqQty)
</td>
<td>#Html.TextBoxFor(m => m.MaterialData[i].IssueQty, new { style = "width:70px" })#Html.ValidationMessageFor(m => m.MaterialData[i].IssueQty)</td>
<td class="text-center">#Html.CheckBoxFor(m => m.MaterialData[i].isSavings)</td>
And my Controller is
public async Task<ActionResult> GetWorkOrderMaterialDetails(IssueEntryModel m)
{
if (!ModelState.IsValid)
{
// redirect
}
var model = new IssueEntryModel();
}
How can I redirect to the if the Model is not valid. Do I need to redirect to same controller. I want to retain the entered data.
My View is
try this
`
[Required]
[Range(18, 100, ErrorMessage = "Please enter an age between 18 and 50")]
public int Age { get; set; }
[Required]
[StringLength(10)]
public int Mobile { get; set; }
[Range(typeof(decimal), "0.00", "15000.00")]
public decimal Total { get; set; } `
if (ModelState.IsValid)
{
//
}
return View(model);
Validation to the Model
Custom Validation Data Annotation Attribute
You can make a custom validation e.g. RequiredIfOtherFieldIsNullAttribute like described here:
How to validate one field related to another's value in ASP .NET MVC 3
public class RequiredIfOtherFieldIsNullAttribute : ValidationAttribute, IClientValidatable
{
private readonly string _otherProperty;
public RequiredIfOtherFieldIsNullAttribute(string otherProperty)
{
_otherProperty = otherProperty;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var property = validationContext.ObjectType.GetProperty(_otherProperty);
if (property == null)
{
return new ValidationResult(string.Format(
CultureInfo.CurrentCulture,
"Unknown property {0}",
new[] { _otherProperty }
));
}
var otherPropertyValue = property.GetValue(validationContext.ObjectInstance, null);
if (otherPropertyValue == null || otherPropertyValue as string == string.Empty)
{
if (value == null || value as string == string.Empty)
{
return new ValidationResult(string.Format(
CultureInfo.CurrentCulture,
FormatErrorMessage(validationContext.DisplayName),
new[] { _otherProperty }
));
}
}
return null;
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var rule = new ModelClientValidationRule
{
ErrorMessage = FormatErrorMessage(metadata.GetDisplayName()),
ValidationType = "requiredif",
};
rule.ValidationParameters.Add("other", _otherProperty);
yield return rule;
}
}
And use it like that:
[RequiredIfOtherFieldIsNull("IsSavings")]
public double IssueQty { get; set; }
[RequiredIfOtherFieldIsNull("IssueQty")]
public bool IsSavings { get; set; }
Use IValidatableObject
However your condition: If the IsSaving is checked then IssueQty can be empty, If the IssueQty is not empty then IsSaving can be left blank is bit confusing, but this might hint you anyways
public class GroupedIssueData : IValidatableObject
{
[Range(0, double.MaxValue, ErrorMessage = "Please enter valid number")]
public double IssueQty { get; set; }
public double ReqQty { get; set; }
public bool isSavings { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (!isSavings && IssueQty == 0)
{
yield return new ValidationResult("Error Message");
}
}
}
public async Task<ActionResult> GetWorkOrderMaterialDetails(IssueEntryModel m)
{
if (!ModelState.IsValid)
{
return View(m);
// redirect
}
}
I am using custom uniqueemail confirmation attribute like
public class UniqueEmailAttribute : ValidationAttribute
{
public override bool IsValid(object value)
{
WMContext db = new WMContext();
var userWithTheSameEmail = db.Users.SingleOrDefault(
u => u.email == (string)value);
return userWithTheSameEmail == null;
}
}
It is working fine while inserting user. But, when I try to update user it gives an error.
public class User
{
public int userID { get; set; }
[Required]
[Display(Name = "Name")]
public string name { get; set; }
[Required]
[Display(Name = "Email")]
[UniqueEmail(ErrorMessage = "This email is used by another user")]
public string email { get; set; }
}
Solution : I have changed the IsValid method like this :
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var model = (User)validationContext.ObjectInstance;
WMContext db = new WMContext();
var userWithTheSameEmail = db.Users.SingleOrDefault(
u => u.email == (string)value && u.userID!=model.userID);
if (userWithTheSameEmail!=null)
{
return new ValidationResult("Bu eposta adresi kullanılıyor.");
}
return ValidationResult.Success;
}