In my controller, I have an action that takes in 3 arguments (primary_key, property, and value) and uses reflection to set the value on corresponding model. (Found by its primary key)
I thought I could catch the model if it was invlaid with ModelState.IsValid but it evaluates as true. Now it goes to db.SaveChanges(); which throws exception.
The ModelState is valid. (Apparently it is no the model instance as found by the primary key and actually refers to my three inputs).
I thought I could check my model for errors with the following line...
if (System.Data.Entity.Validation.DbEntityValidationResult.ValidationErrors.Empty)
But I am getting a "missing object reference" error.
I have no idea what that means. (New to C# and everything else here.) Any help?
EDIT 1 - SHOW MORE CODE:
Validations
[Column("pilot_disembarked")]
[IsDateAfter(testedPropertyName: "Undocked",
allowEqualDates: true,
ErrorMessage = "End date needs to be after start date")]
public Nullable<System.DateTime> PilotDisembarked { get; set; }
Custom Validatior
public sealed class IsDateAfter : ValidationAttribute, IClientValidatable
{
private readonly string testedPropertyName;
private readonly bool allowEqualDates;
public IsDateAfter(string testedPropertyName, bool allowEqualDates = false)
{
this.testedPropertyName = testedPropertyName;
this.allowEqualDates = allowEqualDates;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var propertyTestedInfo = validationContext.ObjectType.GetProperty(this.testedPropertyName);
if (propertyTestedInfo == null)
{
return new ValidationResult(string.Format("unknown property {0}", this.testedPropertyName));
}
var propertyTestedValue = propertyTestedInfo.GetValue(validationContext.ObjectInstance, null);
if (value == null || !(value is DateTime))
{
return ValidationResult.Success;
}
if (propertyTestedValue == null || !(propertyTestedValue is DateTime))
{
return ValidationResult.Success;
}
// Compare values
if ((DateTime)value >= (DateTime)propertyTestedValue)
{
if (this.allowEqualDates)
{
return ValidationResult.Success;
}
if ((DateTime)value > (DateTime)propertyTestedValue)
{
return ValidationResult.Success;
}
}
return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
}
}
Controller Action
[HttpPost]
public ActionResult JsonEdit(string name, int pk, string value)
{
Voyage voyage = db.Voyages.Find(pk);
var property = voyage.GetType().GetProperty(name);
if (Regex.Match(property.PropertyType.ToString(), "DateTime").Success)
{
try
{
if (Regex.Match(value, #"^\d{4}$").Success)
{
var newValue = DateTime.ParseExact(value, "HHmm", System.Globalization.CultureInfo.InvariantCulture);
property.SetValue(voyage, newValue, null);
}
else if (value.Length == 0)
{
property.SetValue(voyage, null, null);
}
else
{
var newValue = DateTime.ParseExact(value, "yyyy/MM/dd HHmm", System.Globalization.CultureInfo.InvariantCulture);
property.SetValue(voyage, newValue, null);
}
}
catch
{
Response.StatusCode = 400;
return Json("Incorrect Time Entry.");
}
}
else
{
var newValue = Convert.ChangeType(value, property.PropertyType);
property.SetValue(voyage, newValue, null);
}
if (ModelState.IsValid)
{
db.SaveChanges();
Response.StatusCode = 200;
return Json("Success!");
}
else
{
Response.StatusCode = 400;
return Json(ModelState.Keys.SelectMany(key => this.ModelState[key].Errors));
}
}
When any value of your model is null at that time ModelState.IsValid.So first check your model data.
Related
I like to check the model for nulls before I use the value in the view. Is this the correct way to use the get and set? I am getting an Exception of type 'System.StackOverflowException' was thrown." on the this.MiddleInitial = value;
Model
public string MiddleInitial {
get {
if (string.IsNullOrWhiteSpace(MiddleInitial) == true) {
return MiddleInitial;
}
return $ "{MiddleInitial.Trim()}.";
}
set {
this.MiddleInitial = value;
}
}
Updated Model
public string MiddleInitial {
get {
if (string.IsNullOrWhiteSpace(MiddleInitial) == true) {
return MiddleInitial;
}
return $ "{MiddleInitial.Trim()}.";
}
set {
if (string.IsNullOrWhiteSpace(MiddleInitial) == true) {
return MiddleInitial;
}
return $ "{MiddleInitial.Trim()}.";
}
}
Inside your get method's if condition, you should check false, not true. Like below:
get
{
//if (string.IsNullOrWhiteSpace(MiddleInitial) == true)
if (string.IsNullOrWhiteSpace(MiddleInitial) == false)
{
return MiddleInitial;
}
return $"{MiddleInitial.Trim()}.";
}
Using an internal private backing field for the property allows you a fine control on what goes into the property and what you return back
// The blank value should be the default for the property
private string _middleInitial = "";
public string MiddleInitial
{
get { return _middleInitial; }
// When someone tries to set a new value for the property
// check for Invalid values (in this case a null ) and
// reset the property back to the default
// (or even throw an exception if it is the case)
set
{
_middleInitial = (value == null ? "" : value.Trim());
// if(value == null)
// throw new ArgumentException("Null is not accepted");
// else
// _middleInitial = value.Trim();
}
}
I have create following custom requiredif attribute and added client side script also validation is working on server side. tag is updated with data-val attributes but client side validation is not triggering. Couldn't figure aout what's I am missing here
public class RequiredIfAttribute : ValidationAttribute, IClientModelValidator//ValidationAttribute
{
private string dependentProperty;
private object targetValue;
public RequiredIfAttribute(string _DependentProperty, object _TargetValue)
{
this.dependentProperty=_DependentProperty ;
this.targetValue=_TargetValue ;
}
public void AddValidation(ClientModelValidationContext context)
{
MergeAttribute(context.Attributes, "data-val-requiredif-dependentproperty", dependentProperty);
MergeAttribute(context.Attributes, "data-val-requiredif-targetvalue","true");
MergeAttribute(context.Attributes, "data-val-requiredif",GetErrorMessage(context));
}
bool MergeAttribute(IDictionary<string, string> attributes, string key, string value)
{
if (attributes.ContainsKey(key))
{
return false;
}
attributes.Add(key, value);
return true;
}
private string GetErrorMessage(ClientModelValidationContext context)
{
return string.Format("{0} is not a valid",
context.ModelMetadata.GetDisplayName());
}
protected override ValidationResult IsValid(object value, System.ComponentModel.DataAnnotations.ValidationContext validationContext)
{
var propertyTestedInfo = validationContext.ObjectType.GetProperty(this.dependentProperty);
if (propertyTestedInfo == null)
{
return new ValidationResult(string.Format("{0} needs to be exist in this object.", this.dependentProperty));
}
var dependendValue = propertyTestedInfo.GetValue(validationContext.ObjectInstance, null);
if (dependendValue == null)
{
return new ValidationResult(string.Format("{0} needs to be populated.", this.dependentProperty));
}
if (!dependendValue.Equals(this.targetValue))
{
var fieldValue = validationContext.ObjectType.GetProperty(validationContext.MemberName).GetValue(validationContext.ObjectInstance, null);
if (fieldValue != null)
{
return ValidationResult.Success;
}
else
{
return new ValidationResult(string.Format("{0} cannot be null", validationContext.MemberName));
}
}
else
{
// Must be ignored
return ValidationResult.Success;
}
}
}
Javascript Code
<script>
$.validator.addMethod('requiredif',
function (value, element, parameters) {
var id = '#' + parameters['dependentproperty'];
// get the target value (as a string,
// as that's what actual value will be)
var targetvalue = parameters['targetvalue'];
targetvalue =
(targetvalue == null ? '' : targetvalue).toString();
// get the actual value of the target control
// note - this probably needs to cater for more
// control types, e.g. radios
var control = $(id);
var controltype = control.attr('type');
var actualvalue =
controltype === 'checkbox' ?
control.attr('checked').toString() :
control.val();
// if the condition is true, reuse the existing
// required field validator functionality
if (targetvalue === actualvalue)
return $.validator.methods.required.call(
this, value, element, parameters);
return true;
}
);
$.validator.unobtrusive.adapters.add(
'requiredif',
['dependentproperty', 'targetvalue'],
function (options) {
options.rules['requiredif'] = {
dependentproperty: options.params['dependentproperty'],
targetvalue: options.params['targetvalue']
};
options.messages['requiredif'] = options.message;
});
</script>
Generated Tag and class property are as follows
<select data-val-requiredif="ToPositionID is not a valid" data-val-requiredif-dependentproperty="chkToPosReq" data-val-requiredif-targetvalue="true" id="ToPositionID" name="ToPositionID">
Class
public class VMWorkflow
{
[RequiredIf("chkToPosReq", "1")]
public int? ToPositionID { get; set; }
}
I'm developing an ASP.NET MVC 5.2.3 custom data annotation for validation in Visual Studio 2015. It needs to take any number of fields and ensure that if one has a value, they all must have a value; if they're all null/blank, it should be okay.
A few examples have helped:
ASP.NET MVC implement custom validator use IClientValidatable
MVC Form Validation on Multiple Fields
http://www.macaalay.com/2014/02/24/unobtrusive-client-and-server-side-age-validation-in-mvc-using-custom-data-annotations/
However, I'm not sure how to do the client-side validation where you have an unknown number of fields being validated.
How do you pass that to the client using the implementation of the GetClientValidationRules() method of the IClientValidatable interface?
Also, how do I apply this new data annotation to the properties on my view model? Would it look like this?
[MultipleRequired("AppNumber", "UserId", /* more fields */), ErrorMessage = "Something..."]
[DisplayName("App #")]
public int AppNumber { get; set; }
[DisplayName("User ID")]
public int UserId { get; set; }
Here's as far as I could get with the MultipleRequiredAttribute custom data annotation class:
public class MultipleRequiredAttribute : ValidationAttribute, IClientValidatable
{
private readonly string[] _fields;
public MultipleRequiredAttribute(params string[] fields)
{
_fields = fields;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
// If any field has value, then all must have value
var anyHasValue = _fields.Any(f => !string.IsNullOrEmpty(f));
if (!anyHasValue) return null;
foreach (var field in _fields)
{
var property = validationContext.ObjectType.GetProperty(field);
if (property == null)
return new ValidationResult($"Property '{field}' is undefined.");
var fieldValue = property.GetValue(validationContext.ObjectInstance, null);
if (string.IsNullOrEmpty(fieldValue?.ToString()))
return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
}
return null;
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
yield return new ModelClientValidationRule
{
ErrorMessage = ErrorMessage,
ValidationType = "multiplerequired"
};
}
}
Thank you.
In order to get client side validation, you need to pass the values of the 'other properties' in the ModelClientValidationRule by using the .Add() method of the rules ValidationParameters property, and then write the client side scripts to add the rules to the $.validator.
But first there are a few other issues to address with your attribute. First you should execute your foreach loop only if the value of the property you applied the attribute is null. Second, returning a ValidationResult if one of the 'other properties' does not exist is confusing and meaningless to a user and you should just ignore it.
The attribute code should be (note I changed the name of the attribute)
public class RequiredIfAnyAttribute : ValidationAttribute, IClientValidatable
{
private readonly string[] _otherProperties;
private const string _DefaultErrorMessage = "The {0} field is required";
public RequiredIfAnyAttribute(params string[] otherProperties)
{
if (otherProperties.Length == 0) // would not make sense
{
throw new ArgumentException("At least one other property name must be provided");
}
_otherProperties = otherProperties;
ErrorMessage = _DefaultErrorMessage;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (value == null) // no point checking if it has a value
{
foreach (string property in _otherProperties)
{
var propertyName = validationContext.ObjectType.GetProperty(property);
if (propertyName == null)
{
continue;
}
var propertyValue = propertyName.GetValue(validationContext.ObjectInstance, null);
if (propertyValue != null)
{
return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
}
}
}
return ValidationResult.Success;
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var rule = new ModelClientValidationRule
{
ValidationType = "requiredifany",
ErrorMessage = FormatErrorMessage(metadata.GetDisplayName()),
};
/ pass a comma separated list of the other propeties
rule.ValidationParameters.Add("otherproperties", string.Join(",", _otherProperties));
yield return rule;
}
}
The scripts will then be
sandtrapValidation = {
getDependentElement: function (validationElement, dependentProperty) {
var dependentElement = $('#' + dependentProperty);
if (dependentElement.length === 1) {
return dependentElement;
}
var name = validationElement.name;
var index = name.lastIndexOf(".") + 1;
var id = (name.substr(0, index) + dependentProperty).replace(/[\.\[\]]/g, "_");
dependentElement = $('#' + id);
if (dependentElement.length === 1) {
return dependentElement;
}
// Try using the name attribute
name = (name.substr(0, index) + dependentProperty);
dependentElement = $('[name="' + name + '"]');
if (dependentElement.length > 0) {
return dependentElement.first();
}
return null;
}
}
$.validator.unobtrusive.adapters.add("requiredifany", ["otherproperties"], function (options) {
var element = options.element;
var otherNames = options.params.otherproperties.split(',');
var otherProperties = [];
$.each(otherNames, function (index, item) {
otherProperties.push(sandtrapValidation.getDependentElement(element, item))
});
options.rules['requiredifany'] = {
otherproperties: otherProperties
};
options.messages['requiredifany'] = options.message;
});
$.validator.addMethod("requiredifany", function (value, element, params) {
if ($(element).val() != '') {
// The element has a value so its OK
return true;
}
var isValid = true;
$.each(params.otherproperties, function (index, item) {
if ($(this).val() != '') {
isValid = false;
}
});
return isValid;
});
I am doing client side custom validations using jQuery, I am stuck in getting values from
server side to do operations on client side ....
this is my server side custom validation function
public class SelctedValueCheckAttribute : ValidationAttribute , IClientValidatable
{
public SelctedValueCheckAttribute(string otherProperty): base("{0} is not in correct range")
{
OtherProperty = otherProperty;
}
public string OtherProperty { get; set; }
public SelctedValueCheckAttribute()
{
ErrorMessage = "Values must be in the Given Range";
}
public override string FormatErrorMessage(string name)
{
return "The Entered Value Must be in given range for " + name + "item";
}
protected override ValidationResult IsValid(object firstValue, ValidationContext validationContext)
{
string selecetdItemValue = firstValue as string ;
string userEnteredValue = GetSecondValue(validationContext);
if( string.Equals(selecetdItemValue, "Amount"))
{
int entry = Convert.ToInt32(userEnteredValue);
if (entry < 10 || entry > 20)
{
return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
}
}
else if (string.Equals(selecetdItemValue, "Pound"))
{
int entry = Convert.ToInt32(userEnteredValue);
if (entry < 80 || entry > 90)
{
return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
}
}
else if (string.Equals(selecetdItemValue, "Percent"))
{
int entry = Convert.ToInt32(userEnteredValue);
if (entry < 50 || entry > 60)
{
return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
}
}
return ValidationResult.Success;
}
protected string GetSecondValue(ValidationContext validationContext)
{
var propertyInfo = validationContext.ObjectType.GetProperty(OtherProperty);
if (propertyInfo != null)
{
var secondValue = propertyInfo.GetValue(validationContext.ObjectInstance, null);
return secondValue as string;
}
return null;
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
ModelClientValidationRule mcvr = new ModelClientValidationRule();
mcvr.ValidationType = "enteredvaluescheck";
mcvr.ErrorMessage = "Selected Value must be in given range";
mcvr.ValidationParameters.Add("other", OtherProperty);
mcvr.ValidationType = "selectedvaluewithenteredvaluecheck";
yield return mcvr;
}
}
and this is my client side custom validation
jquery.validator.unobtrusive.adapters.addSingleval("selectedvaluewithenteredvaluecheck", "other");
jQuery.validator.addMethod("selectedvaluewithenteredvaluecheck",
function(val,element,other)
{
if(val & other)
{
// here I am not getting the values..
//do I need to write any function to get the values
//is there any other approach that I need to follow
}
How can I get the values in client side function?
Would any one have any idea about this and any suggestions please suggest me.
You miss-spelled jquery and addSingleval. They should be capitalized Q and V.
jQuery.validator.unobtrusive.adapters.addSingleVal("selectedvaluewithenteredvaluecheck", "other");
I am working with an oracle legacy database.
All the dates are saved in decimal fields with the following format YYYYMMDD.
Is there a way to transform this kind of type in a datetime (c#), maybe in the mapping file?
Just write a custom IEnhancedUserType that converts to and from the string representation.
That way it will be transparent to your application.
Recently I faced with the same problem and ended up with writing the following method:
public static DateTime? ConvertFromOracleDate(object oracleDate)
{
if (oracleDate == null)
throw new ArgumentNullException("oracleDate");
if (!(oracleDate is string))
throw new ArgumentException("oracleDate");
var sDate = (string) oracleDate;
if (sDate.Equals(String.Empty))
return null;
if (sDate.Length != 8)
throw new ArgumentOutOfRangeException("oracleDate");
sDate = sDate.Insert(6, "/");
sDate = sDate.Insert(4, "/");
var ci = new CultureInfo("en-US");
return Convert.ToDateTime(sDate, ci);
}
Hope this helps.
Ended up writing a custom IUserType.
For those who are interested here is the code. I haven't tried the writing process cause my table is read-only at the moment:
public class DecimalToDateUserType : IUserType
{
public object NullSafeGet(IDataReader rs, string[] names, object owner)
{
int? result = (int)NHibernateUtil.Int32.NullSafeGet(rs, names[0]);
if ((result != null)&&(result.Value>0))
{
var sDate = result.Value.ToString();
sDate = sDate.Insert(6, "/").Insert(4, "/");
var ci = new CultureInfo("en-US");
return Convert.ToDateTime(sDate, ci);
}
return (DateTime.MinValue);
}
public void NullSafeSet(IDbCommand cmd, object value, int index)
{
if (value == null)
{
NHibernateUtil.Int32.NullSafeSet(cmd, null, index);
return;
}
if ((DateTime)value == DateTime.MinValue)
{
value = 0;
NHibernateUtil.Int32.NullSafeSet(cmd, value, index);
return;
}
int convertedValue = 0;
int.TryParse(((DateTime)value).ToString("yyyyMMdd"), out convertedValue);
value = convertedValue;
NHibernateUtil.Int32.NullSafeSet(cmd, value, index);
}
public object DeepCopy(object value)
{
if (value == null) return null;
return (value);
}
public object Replace(object original, object target, object owner)
{
return original;
}
public object Assemble(object cached, object owner)
{
return DeepCopy(cached);
}
public object Disassemble(object value)
{
return DeepCopy(value);
}
public SqlType[] SqlTypes
{
get
{
SqlType[] types = new SqlType[1];
types[0] = new SqlType(DbType.Decimal);
return types;
}
}
public Type ReturnedType
{
get { return typeof(DateTime); }
}
public bool IsMutable
{
get { return false; }
}
public new bool Equals(object x, object y)
{
if (x == null || y == null) return false;
return x.Equals(y);
}
public int GetHashCode(object x)
{
return x.GetHashCode();
}
}
You can also do the transformation in your SQL query using
TO_DATE(TO_CHAR(semi_date_field),'yyyymmdd'))
Then you'll select a column with Oracle-datatype DATE. C# will undoubtedly treat those as datetimes.
Regards,
Rob.