WPF validation by attributes check if property is valid - c#

I have a ViewModel in my C# WPF application which contains several properties like this one
public class ExecutionsCreateViewModel : ValidationViewModelBase
{
[Required(ErrorMessage = "Execution name is required.")]
[StringLength(60, ErrorMessage = "Execution name is too long.")]
public string ExecutionName { get; set; }
[...]
}
Thats my ValidationViewModelBase class
public abstract class ValidationViewModelBase : IDataErrorInfo
{
string IDataErrorInfo.Error
{
get
{
throw new NotSupportedException("IDataErrorInfo.Error is not supported.");
}
}
string IDataErrorInfo.this[string propertyName]
{
get
{
if (string.IsNullOrEmpty(propertyName))
{
throw new ArgumentException("Invalid property name", propertyName);
}
string error = string.Empty;
var value = GetValue(propertyName);
var results = new List<ValidationResult>(1);
var result = Validator.TryValidateProperty(
value,
new ValidationContext(this, null, null)
{
MemberName = propertyName
},
results);
if (!result)
{
var validationResult = results.First();
error = validationResult.ErrorMessage;
}
return error;
}
}
private object GetValue(string propertyName)
{
PropertyInfo propInfo = GetType().GetProperty(propertyName);
return propInfo.GetValue(this);
}
}
And this is my TextBox in XAML
<TextBox Text="{Binding ExecutionName, UpdateSourceTrigger=PropertyChanged, NotifyOnValidationError=True, ValidatesOnExceptions=True, ValidatesOnDataErrors=True}"/>
Attributes are working, UI is correctly notified when property becomes invalid ("Invalid" VisualState is triggered).
The problem is, I don't know how to check in Create method if certain property is currently valid or not.
private void Create()
{
if(/*check if property is invalid*/)
{
MessageBox.Show(/*property ErrorMessage*/);
return;
}
//Do something with valid properties
}}
I've tried with Validator.ValidateProperty (1, 2, 3) but it's not working and/or it's too messy. I was also doing tricks like
try
{
ExecutionName = ExecutionName;
}
catch(Exception ex)
{
MessageBox.Show(ex.Message);
return;
}
But it's not working in some scenarios and does not look very professional.
Maybe ValidateProperty is the key, but after many "tutorials" I still doesn't know how to fill it to my needs.
Also there is second small thing. Attributes always validate its properties, so when user receive fresh form the ExecutionName will always be null thus Required attribute will mark it as invalid, so that control will automatically turn red. Is there a way to skip validation at initialization?

The problem is, I don't know how to check in Create method if certain property is currently valid or not.
The same way as you do in your ValidationViewModelBase class, e.g.:
private void Create()
{
var value = this.ExecutionName; //the property to validate
var results = new List<ValidationResult>();
var result = Validator.TryValidateProperty(
value,
new ValidationContext(this, null, null)
{
MemberName = "ExecutionName" //the name of the property to validate
},
results);
if (!result)
{
var validationResult = results.First();
MessageBox.Show(validationResult.ErrorMessage);
}
//...
}

Related

Is there any way to add error code to ModelState error

I'm looking for a way to add error code alongside the error message to ModelState.
for example
ModelState.AddModelError("ErrorKey", new { Code = 4001, Message = "Some error message" });
For some bad requests client should do an action and comparing error message is not an ideal solution for making a decision. ModelState.AddModelError method only accepts two parameters, an error key and a message. Is there a way to achieve this or something similar?
No, there is not a way to achieve what you are looking for, in your code when you're trying to do something like this:
return BadRequest(ModelState);
You’ll receive a 400 bad request response back with message you've already added (as you can see, the error code has been presented already here). So, there is neither a usage nor a way of adding the Error Code in your case.
I found a way to add the error code to ValidationProblemDetails:
public class CustomValidationProblemDetails : ValidationProblemDetails
{
public CustomValidationProblemDetails()
{
}
[JsonPropertyName("errors")]
public new IEnumerable<ValidationError> Errors { get; } = new List<ValidationError>();
}
ValidationProblemDetails has an Error property that is IDictionary<string, string[]> and replace this property with our version to add code error.
public class ValidationError
{
public int Code { get; set; }
public string Message { get; set; }
}
Constructor of ValidationProblemDetails accepts ModelStateDictionary and need to convert it to list of ValidationError:
public CustomValidationProblemDetails(IEnumerable<ValidationError> errors)
{
Errors = errors;
}
public CustomValidationProblemDetails(ModelStateDictionary modelState)
{
Errors = ConvertModelStateErrorsToValidationErrors(modelState);
}
private List<ValidationError> ConvertModelStateErrorsToValidationErrors(ModelStateDictionary modelStateDictionary)
{
List<ValidationError> validationErrors = new();
foreach (var keyModelStatePair in modelStateDictionary)
{
var errors = keyModelStatePair.Value.Errors;
switch (errors.Count)
{
case 0:
continue;
case 1:
validationErrors.Add(new ValidationError { Code = 100, Message = errors[0].ErrorMessage });
break;
default:
var errorMessage = string.Join(Environment.NewLine, errors.Select(e => e.ErrorMessage));
validationErrors.Add(new ValidationError { Message = errorMessage });
break;
}
}
return validationErrors;
}
Create custom ProblemDetailsFactory to create CustomValidationProblemDetails when we want to return bad request response:
public class CustomProblemDetailsFactory : ProblemDetailsFactory
{
public override ProblemDetails CreateProblemDetails(HttpContext httpContext, int? statusCode = null, string title = null,
string type = null, string detail = null, string instance = null)
{
var problemDetails = new ProblemDetails
{
Status = statusCode,
Title = title,
Type = type,
Detail = detail,
Instance = instance,
};
return problemDetails;
}
public override ValidationProblemDetails CreateValidationProblemDetails(HttpContext httpContext,
ModelStateDictionary modelStateDictionary, int? statusCode = null, string title = null, string type = null,
string detail = null, string instance = null)
{
statusCode ??= 400;
type ??= "https://tools.ietf.org/html/rfc7231#section-6.5.1";
instance ??= httpContext.Request.Path;
var problemDetails = new CustomValidationProblemDetails(modelStateDictionary)
{
Status = statusCode,
Type = type,
Instance = instance
};
if (title != null)
{
// For validation problem details, don't overwrite the default title with null.
problemDetails.Title = title;
}
var traceId = Activity.Current?.Id ?? httpContext?.TraceIdentifier;
if (traceId != null)
{
problemDetails.Extensions["traceId"] = traceId;
}
return problemDetails;
}
}
And at the end register the factory:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddTransient<ProblemDetailsFactory, CustomProblemDetailsFactory>();
}
Read the Extending ProblemDetails - Add error code to ValidationProblemDetails for more detail.

CompareAttribute for WPF?

Does anyone know of a CompareAttribute data annotation for WPF, or a way of achieving the same result in WPF?
For those that don't immediately know, CompareAttribute is a property data annotation for validating in WPF, it takes a string for a second property and returns true if the decorated property and the passed property match.
Basically I need to validate a password change form, to ensure the "retyped password" matches the new password, and do this with data annotations so that i can use the xaml validation template.
You can create your own custom validation logic by creating your own CustomValidationAttribute descrided here.
Try Custom Validator like this
public class EqualsValidationAttribute : ValidationAttribute
{
string propertyToCompare;
public EqualsValidationAttribute(string propertyToCompare)
{
this.propertyToCompare = propertyToCompare;
}
public EqualsValidationAttribute(string propertyToCompare,string errorMessage):this(propertyToCompare)
{
this.ErrorMessage = propertyToCompare;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var propInfo=validationContext.ObjectInstance.GetType().GetProperty(propertyToCompare);
if (propInfo != null)
{
var propValue=propInfo.GetValue(validationContext.ObjectInstance);
if(value!=null && propValue!=null && !string.IsNullOrEmpty(value.ToString()) && !string.IsNullOrEmpty(propValue.ToString()) //if either one is empty dont Validate
&& (value.ToString() != propValue.ToString()))
return new ValidationResult(ErrorMessage);
}
else
throw new NullReferenceException("propertyToCompare must be the name of property to compare");
return ValidationResult.Success;
}
}
and use it in Entity like this
[Required(ErrorMessage="Password Required")]
public string Password {
get { return password; }
set { password = value; RaisePropertyChanged("Password"); }
}
[EqualsValidationAttribute("Password", ErrorMessage = "Confirm password must be same as password")]
public string ConfirmPassword {
get { return confirmedpassword; }
set { confirmedpassword = value; RaisePropertyChanged("ConfirmPassword"); }
}
}

Unobtrusive validation C# MVC Razor

Is it possible to have unobtrusive validation to make a field required but only if other properties change?
For Example
[Required]
public Decimal Income {get; set;}
[Required]
public Decimal Tax {get; set;}
//Required if tax or income changes
public string ChangeReason {get; set;}
I thought about having multiple backing store fields and writing a Custom Validator to compare these, but wondered if anyone had a better suggestion?
Custom Validator is the way to go. I had to build something similar a while back.
I'd set up a hidden value - "Changed" - set it to true whenever the user mods the fields of interest.
Set a RequiredIf validator on the 2 properties of interest:
[RequiredIf("Changed", true, ErrorMessage = "Required")]
The code for a RequiredIf validator is shown below:
public class RequiredIfAttribute : ValidationAttribute, IClientValidatable
{
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;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
// get a reference to the property this validation depends upon
var containerType = validationContext.ObjectInstance.GetType();
var field = containerType.GetProperty(this.DependentProperty);
if (field != null)
{
// get the value of the dependent property
var dependentvalue = field.GetValue(validationContext.ObjectInstance, null);
// compare the value against the target value
if ((dependentvalue == null && this.TargetValue == null) ||
(dependentvalue != null && dependentvalue.Equals(this.TargetValue)))
{
// match => means we should try validating this field
if (!_innerAttribute.IsValid(value))
// validation failed - return an error
return new ValidationResult(this.ErrorMessage, new[] { validationContext.MemberName });
}
}
return null;
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var rule = new ModelClientValidationRule()
{
ErrorMessage = FormatErrorMessage(metadata.GetDisplayName()),
ValidationType = "requiredif",
};
string depProp = BuildDependentPropertyId(metadata, context as ViewContext);
// find the value on the control we depend on;
// if it's a bool, format it javascript style
// (the default is True or False!)
string targetValue = (this.TargetValue ?? "").ToString();
if (this.TargetValue.GetType() == typeof(bool))
targetValue = targetValue.ToLower();
rule.ValidationParameters.Add("dependentproperty", depProp);
rule.ValidationParameters.Add("targetvalue", targetValue);
yield return rule;
}
private string BuildDependentPropertyId(ModelMetadata metadata, ViewContext viewContext)
{
// build the ID of the property
string depProp = viewContext.ViewData.TemplateInfo.GetFullHtmlFieldId(this.DependentProperty);
// unfortunately this will have the name of the current field appended to the beginning,
// because the TemplateInfo's context has had this fieldname appended to it. Instead, we
// want to get the context as though it was one level higher (i.e. outside the current property,
// which is the containing object (our Person), and hence the same level as the dependent property.
var thisField = metadata.PropertyName + "_";
if (depProp.StartsWith(thisField))
// strip it off again
depProp = depProp.Substring(thisField.Length);
return depProp;
}
}
Javascript:
/// <reference path="jquery-1.4.4-vsdoc.js" />
/// <reference path="jquery.validate.unobtrusive.js" />
$.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;
});
It is possible. You can write your own attribute, to do this exactly.
It basically requires two steps:
Write your own attribute, make it inherit ValidationAttribute amd implement IClientValidatable
Write a Jquery validation adapter to support it
A good working sample is described in this post.
I used a similar approach to create a dependency validation (one field could have values only if another was filled)

TryValidateProperty not work with generic function

UPDATED:
when i executing Unit Test project and then it will return Was unhandled This test result also contained an inner exception instead of "Assert.IsTrue failed. The Description field is required." Result like (0 Pass, 1 FAIL, 1 Total) but we are not getting any exception at all if i debug with F11
[TestMethod]
[Asynchronous]
[Description("Determines whether the selected or single property is valide using the validation context or validate single properties.")]
public void ValidateSigleWithDataAnnotation()
{
LookupsServices lookupsservices = new LookupsServices();
Lookups lookups = new Lookups() { Description = "", LookupReference = 2, DisplayOrder = 50};
lookupsservices.Lookups.Add(lookups);
//THIS IS NOT WORKING
string message = ValidateProperties.ValidateSingle(lookups, "Description");
Assert.IsTrue(message.Equals(""), message);
//THIS IS WORKING
var results = new List<ValidationResult>();
Validator.TryValidateProperty(lookups.Description , new ValidationContext(lookups, null, null) { MemberName = "Description" }, results);
Assert.IsTrue(results.Count == 0, results[0].ToString());
}
Following is the Generic function to validate individual property
public static string ValidateSingle<T>(T t, string PeropertyName) where T : class
{
string errorMessage = "";
var ValidationMessages = new List<ValidationResult>();
bool ValidationResult = Validator.TryValidateProperty(typeof(T).GetProperty(PeropertyName).Name, new ValidationContext(t, null, null) { MemberName = PeropertyName} , ValidationMessages);
if (!ValidationResult) errorMessage += string.Format("\n{0}", ValidationMessages[0]);
return errorMessage;
}
Following is the Model where Description field id Required
public class Lookups
{
public Lookups() { }
[Key]
public virtual int LookupReference { get; set; }
[Required]
public virtual string Description { get; set; }
public virtual int? DisplayOrder { get; set; }
}
I am getting error "The Description field is required" if i am validating without Generic method, but why am not getting same error using Generic method?
Please Help me.....
Compare these two calls:
// In the generic method
Validator.TryValidateProperty(
typeof(T).GetProperty(PeropertyName).Name,
new ValidationContext(t, null, null) { MemberName = PeropertyName},
ValidationMessages);
// The working call
Validator.TryValidateProperty(
lookups.Description,
new ValidationContext(lookups, null, null) { MemberName = "Description" },
results);
In the first form, you're passing in the name of the property, i.e. "Description". In the second form, you're passing in the value of the property, i.e. "". To make the first call look like the second, you'd need:
typeof(T).GetProperty(PeropertyName).GetValue(t, null),
It's not entirely clear to me whether that's what you want (I haven't used Validator myself) but it may be the answer.

In ASP.NET MVC 2, can you use data annotations to compare two fields in a form? What are the alternatives if not Data Annotations

I have two fields in my form
AccountNumber
ReverseAccountNumber
Can i use data annotations to validate that the value of "ReverseAccountNumber" textbox is equal to the reversed value of "AccountNumber".
i.e.
AccountNumber = 12345
ReverseAccountNumber = 54321
i expect the validation to occur on the lostFocus event of the ReverseAccountNumber textbox.
I think i can do this using IDataErrorInfo, However I believe this would require a POST first before validation occurs, and i consider it a last resort.
Simply add a validation attribute to the class (not the properties) and evaluate the class object to compare the two properties. As for the client side, ASP.NET MVC 3 should be able to generate proper client-side validation for this (although I have not tried it myself since Iam still using xVal).
CustomAttribute
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true, Inherited = true)]
public sealed class ReversStringMatchAttribute : ValidationAttribute
{
public string Property { get; set; }
public ReversStringMatchAttribute()
{ }
public override bool IsValid(object value)
{
return true;
}
}
CustomValidator
public class ReversStringValidator : DataAnnotationsModelValidator<ReversStringMatchAttribute>
{
string property;
public ReversStringValidator(ModelMetadata metadata, ControllerContext context, ReversStringMatchAttribute attribute)
: base(metadata, context, attribute)
{
property = attribute.Property;
}
public override IEnumerable<ModelClientValidationRule> GetClientValidationRules()
{
var rule = new ModelClientValidationRule
{
ErrorMessage = Attribute.ErrorMessage,
ValidationType = "reversStringValidator"
};
rule.ValidationParameters.Add("propertyname", property);
return new[] { rule };
}
}
Java Script
Sys.Mvc.ValidatorRegistry.validators["reversStringValidator"] = function (rule) {
//initialization
//return validator function
return function (value, context) {
var field = $get(rule.ValidationParameters['propertyname']);
if (field == null)
return "Property name is invalid!";
var s1 = field.value;
if (s1) {
if (value) {
var reverse = value.split("").reverse().join("");
if (s1 != reverse.toString()) {
return rule.ErrorMessage;
}
} else {
return rule.ErrorMessage;
}
}
return true;
}
};
then use it on your property
public class AccountViewModel
{
[Required(ErrorMessage="Account Number is Required")]
public string AccountNumber { get; set; }
[ReversStringMatch(ErrorMessage = "The value doesn't match the Account Number", Property="AccountNumber")]
public string ReverseAccountNumber { get; set; }
}
i have some doubts on the $get validation method in javascript but it works, for now.

Categories