DataAnnotation to compare two properties - c#

Is there any way of using data annotations to compare two form field (eg. to confirm an email address) are the same, before allowing the form to be posted?
eg. can the regular expression data annotation use the match function to reference another property in a ViewModel?

Use the CompareAttribute
public string EmailAddress {get; set;}
[Compare(nameof(EmailAddress), ErrorMessage = "Emails mismatch")]
public string VerifiedEmailAddress { get; set; }

As one possibe option self-validation:
Implement an interface IValidatableObject with method Validate, where you can put your validation code.
public class TestModel : IValidatableObject
{
public string Email{ get; set; }
public string ConfirmEmail { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (Email != ConfirmEmail)
{
yield return new ValidationResult("Emails mismatch", new [] { "ConfirmEmail" });
}
}
}
Please notice: this is only server-side validation.

Related

Blazor trigger custom validation message

I have the following class which is being used as an input model for an EditForm in a Blazor server side application.
public class KundeInput
{
[ValidateComplexType]
public List<AnsprechpartnerInput> Ansprechpartner { get; } = new List<AnsprechpartnerInput>();
public string? Kundennummer { get; }
[Required]
[MaxLength(60)]
public string Firma { get; set; } = String.Empty;
[MaxLength(60)]
public string? Name2 { get; set; }
[MaxLength(60)]
public string? Name3 { get; set; }
}
As you can see, my model contains a list of another model called AnsprechpartnerInput. Here is this model:
public class AnsprechpartnerInput
{
public string? Kundennummer { get; set; }
public int Nummer { get; } = -1;
[MaxLength(60)]
[Required]
public string Vorname { get; set; } = String.Empty;
[MaxLength(60)]
[Required]
public string Nachname { get; set; } = String.Empty;
[MaxLength(40)]
[Required]
public string? Bereich { get; set; }
/ * More properties */
}
The validation works fine. However, once I have multiple invalid AnsprechpartnerInput models in my list, the ValidationSummary becomes a mess. Because it displays e.g. 5 times field xyz is invalid.
I know I can set a custom message with the ErrorMessage property but I am not able to use other attributes from my model in this message.
What I want to achive is this:
[Required(ErrorMessage = $"Vorname of {Kundennummer} is required")]
public string Vorname { get; set; } = String.Empty;
I already tried to change the message with reflection but accoridng to Microsoft this way is not recommend or supported
https://github.com/dotnet/aspnetcore/issues/25611
Is there any way to get it to work? I thought of string replacement but I am not sure how I can figure out the right model for my ValidationMessage.
Also is there any way to validate the items of the list by one and get a boolean result? Let's say I want to achive this:
#foreach (var ansprechpartner in Input.Ansprechpartner)
{
if (Input.SelectedAnsprechpartner is null)
Input.SelectedAnsprechpartner = ansprechpartner;
<a #onclick="() => Input.SelectedAnsprechpartner = ansprechpartner"
class="#GetNavListClass(Input.SelectedAnsprechpartner == ansprechpartner)"
id="list-ansprechpartner-tab-#(ansprechpartner.Nummer)"
data-toggle="list"
href="#list-ansprechpartner-#(ansprechpartner.Nummer)"
role="tab"
aria-controls="#(ansprechpartner.Nummer)">
#((MarkupString)(ansprechpartner.Nummer < 0 ? "<span class=\"font-weight-bold\">NEU</span>" : $"({ansprechpartner.Nummer})")) #ansprechpartner.Vorname #ansprechpartner.Nachname
</a>
// When the model ansprechpartner is invalid, I want to display an icon
}
Thanks for any help!
PS: Blazor rocks!
You should use a custom validation attribute where you can explicitly add any error message you want
public class KundennummerValidationAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var model = (AnsprechpartnerInput)validationContext.ObjectInstance;
if(string.IsNullOrEmpty((string)value))
{
return new ValidationResult($"Vorname of {model.Kundennummer} is required", new[] { "Kundennummer" });
}
return ValidationResult.Success;
}
}
then use
[KundennummerValidation]
public string Vorname { get; set; } = String.Empty;
result :
Validation summary:

IValidatableObject not validating if child collection is Invalid

I have an View Model that is an IValidatableObject that contains a collection of CustomFields that are also IValidatableObject. Both the view model and the custom fields have custom logic to check if they are valid when posted.
The View Model looks like this:
public class ViewModel : IValidatableObject
{
public bool IsInspected { get; set; }
public DateTime? InspectedDate { get; set; }
public IList<CustomField> CustomFields { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (IsInspected == true && InspectedDate == null)
{
yield return new ValidationResult("Inspection Date is required if Inspected.", new[] { nameof(InspectedDate) });
}
}
}
public class CustomField : IValidatableObject
{
public bool IsRequired { get; set; }
public string Value { get; set; }
public string DisplayName { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (IsRequired && string.IsNullOrWhiteSpace(Value))
{
yield return new ValidationResult($"The {DisplayName} field is required.", new[] { nameof(Value) });
}
}
}
This correctly validates the CustomFields, but it doesn't validate the ViewModel itself unless the CustomFields have no errors. This means if the user posts invalid Custom Fields and invalid View Model fields, they aren't given notification of the View Model fields until they correct the Custom Fields, post again, and then get the View Model fields validated.
I tried removing IValidatableObject from CustomField and instead doing a foreach loop inside the ViewModel.Validate() method, but that didn't correctly assign ModelState Keys that highlight the input fields on the form. For example the method shown above creates a ModelState Key of CustomFields[0].Value while doing a loop inside ViewModel just creates a Key of Value.

Join multiple Model properties into 1 property and get one error message for each of them

I have 4 string properties, each property for each textbox in my Asp.Net MVC view.
I also have a property that concats the values of each into one:
public string ModelCode {get{return ProjNr+SerialNr+UserNr+ClientNr}}
So I want, if one of the properties doesn't fit the model requirements to get just one error message. Like when the user doesn't input the ClientNr instead of getting an error saying "Client required" it should show an error saying that the ModelCode doesn't meet the requirements.
EDIT:
ProjNr, SerialNr, UserNr and ClientNr are required and they are strings.
You can implement IValidatableObject by your model class
public class YourModel : IValidatableObject
{
public string ProjNr { get; set; }
public string SerialNr { get; set; }
public string UserNr { get; set; }
public string ClientNr { get; set; }
public string ModelCode => $"{ProjNr}{SerialNr}{UserNr}{ClientNr}";
// ...
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (/*check if ProjNr or other fields not valid*/)
yield return new ValidationResult(
"ModelCode doesn't meet the requirements",
new [] {"ModelCode"}); // return only ModelCode member
}
}
Another option (if you want to rely on DataAnnotation attributes for validation of ProjNr, SerailNr, UserNr and ClientNr instead of validating them manually - you can check validation errors of those properties in controller and add new validation error if any errors found:
var hasModelCodeErrors = ModelState["ProjNr"].Errors.Any()
|| ModelState["SerialNr"].Errors.Any()
|| ModelState["SerialNr"].Errors.Any()
|| ModelState["ClientNr"].Errors.Any();
if (hasModelCodeErrors)
ModelState.AddModelError("ModelCode", "ModelCode doesn't meet the requirements");

ASP.NET MVC: Custom Validation by DataAnnotation

I have a Model with 4 properties which are of type string. I know you can validate the length of a single property by using the StringLength annotation. However I want to validate the length of the 4 properties combined.
What is the MVC way to do this with data annotation?
I'm asking this because I'm new to MVC and want to do it the correct way before making my own solution.
You could write a custom validation attribute:
public class CombinedMinLengthAttribute: ValidationAttribute
{
public CombinedMinLengthAttribute(int minLength, params string[] propertyNames)
{
this.PropertyNames = propertyNames;
this.MinLength = minLength;
}
public string[] PropertyNames { get; private set; }
public int MinLength { get; private set; }
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var properties = this.PropertyNames.Select(validationContext.ObjectType.GetProperty);
var values = properties.Select(p => p.GetValue(validationContext.ObjectInstance, null)).OfType<string>();
var totalLength = values.Sum(x => x.Length) + Convert.ToString(value).Length;
if (totalLength < this.MinLength)
{
return new ValidationResult(this.FormatErrorMessage(validationContext.DisplayName));
}
return null;
}
}
and then you might have a view model and decorate one of its properties with it:
public class MyViewModel
{
[CombinedMinLength(20, "Bar", "Baz", ErrorMessage = "The combined minimum length of the Foo, Bar and Baz properties should be longer than 20")]
public string Foo { get; set; }
public string Bar { get; set; }
public string Baz { get; set; }
}
Self validated model
Your model should implement an interface IValidatableObject. Put your validation code in Validate method:
public class MyModel : IValidatableObject
{
public string Title { get; set; }
public string Description { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (Title == null)
yield return new ValidationResult("*", new [] { nameof(Title) });
if (Description == null)
yield return new ValidationResult("*", new [] { nameof(Description) });
}
}
Please notice: this is a server-side validation. It doesn't work on client-side. You validation will be performed only after form submission.
ExpressiveAnnotations gives you such a possibility:
[Required]
[AssertThat("Length(FieldA) + Length(FieldB) + Length(FieldC) + Length(FieldD) > 50")]
public string FieldA { get; set; }
To improve Darin's answer, it can be bit shorter:
public class UniqueFileName : ValidationAttribute
{
private readonly NewsService _newsService = new NewsService();
public override bool IsValid(object value)
{
if (value == null) { return false; }
var file = (HttpPostedFile) value;
return _newsService.IsFileNameUnique(file.FileName);
}
}
Model:
[UniqueFileName(ErrorMessage = "This file name is not unique.")]
Do note that an error message is required, otherwise the error will be empty.
Background:
Model validations are required for ensuring that the received data we receive is valid and correct so that we can do the further processing with this data. We can validate a model in an action method. The built-in validation attributes are Compare, Range, RegularExpression, Required, StringLength. However we may have scenarios wherein we required validation attributes other than the built-in ones.
Custom Validation Attributes
public class EmployeeModel
{
[Required]
[UniqueEmailAddress]
public string EmailAddress {get;set;}
public string FirstName {get;set;}
public string LastName {get;set;}
public int OrganizationId {get;set;}
}
To create a custom validation attribute, you will have to derive this class from ValidationAttribute.
public class UniqueEmailAddress : ValidationAttribute
{
private IEmployeeRepository _employeeRepository;
[Inject]
public IEmployeeRepository EmployeeRepository
{
get { return _employeeRepository; }
set
{
_employeeRepository = value;
}
}
protected override ValidationResult IsValid(object value,
ValidationContext validationContext)
{
var model = (EmployeeModel)validationContext.ObjectInstance;
if(model.Field1 == null){
return new ValidationResult("Field1 is null");
}
if(model.Field2 == null){
return new ValidationResult("Field2 is null");
}
if(model.Field3 == null){
return new ValidationResult("Field3 is null");
}
return ValidationResult.Success;
}
}
Hope this helps. Cheers !
References
Code Project - Custom Validation Attribute in ASP.NET MVC3
Haacked - ASP.NET MVC 2 Custom Validation
A bit late to answer, but for who is searching.
You can easily do this by using an extra property with the data annotation:
public string foo { get; set; }
public string bar { get; set; }
[MinLength(20, ErrorMessage = "too short")]
public string foobar
{
get
{
return foo + bar;
}
}
That's all that is too it really. If you really want to display in a specific place the validation error as well, you can add this in your view:
#Html.ValidationMessage("foobar", "your combined text is too short")
doing this in the view can come in handy if you want to do localization.
Hope this helps!

ASP MVC: Custom Validation Attribute

I'm trying to write my own Custom Validation attribute but I'm having some problems.
The attribute I'm trying to write is that when a user logs in, the password will be compared against the confirmation password.
namespace Data.Attributes
{
public class ComparePassword : ValidationAttribute
{
public string PasswordToCompareWith { get; set; }
public override bool IsValid(object value)
{
if (PasswordToCompareWith == (string)value)
{
return true;
}
return false;
}
}
Now my problem is when i'm trying to set the attribute like this in the model file:
[Required]
[ComparePassword(PasswordToCompareWith=ConfirmPassword)]
public string Password { get; set; }
[Required]
public string ConfirmPassword { get; set; }
}
I get the following error:
Error 1 An object reference is required for the non-static field, method,
or property 'Project.Data.Models.GebruikerRegistreerModel.ConfirmPassword.get'
It seems that VS is not accepting the confirmpassword in the PasswordToCompareWith=ConfirmPassword part.
What am I doing wrong?
According to this link http://devtrends.co.uk/blog/the-complete-guide-to-validation-in-asp.net-mvc-3-part-1 there is an special validation attribute now in MVC3:
public class RegisterModel
{
// skipped
[Required]
[ValidatePasswordLength]
[DataType(DataType.Password)]
[Display(Name = "Password")]
public string Password { get; set; }
[DataType(DataType.Password)]
[Display(Name = "Confirm password")]
[Compare("Password", ErrorMessage = "The password and confirmation do not match.")]
public string ConfirmPassword { get; set; }
}
CompareAttribute is a new, very useful validator that is not actually
part of
System.ComponentModel.DataAnnotations,
but has been added to the
System.Web.Mvc DLL by the team. Whilst
not particularly well named (the only
comparison it makes is to check for
equality, so perhaps EqualTo would be
more obvious), it is easy to see from
the usage that this validator checks
that the value of one property equals
the value of another property. You can
see from the code, that the attribute
takes in a string property which is
the name of the other property that
you are comparing. The classic usage
of this type of validator is what we
are using it for here: password
confirmation.
Sorry to disappoint you but handling such a simple case like yours using Data Annotations could be a pain. You may take a look at this post.
FoolProof http://foolproof.codeplex.com/ seems to be the best solution.
public class SignUpViewModel
{
[Required]
public string Password { get; set; }
[EqualTo("Password", ErrorMessage="Passwords do not match.")]
public string RetypePassword { get; set; }
}
It is better than suggested PropertiesMustMatchAttribute as it adds the validation error for the "RetypePassword' instead of the global model level as PropertiesMustMatchAttribute does.
I don't know why this is made out to be such a big deal, just do this:
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true, Inherited = false)]
public class ComparePassword: ValidationAttribute
{
public ComparePassword()
: base("Passwords must match.") { }
protected override ValidationResult IsValid (object value, ValidationContext validationContext)
{
if (value == null) return new ValidationResult("A password is required.");
// Make sure you change YourRegistrationModel to whatever the actual name is
if ((validationContext.ObjectType.Name != "YourRegistrationModel")
return new ValidationResult("This attribute is being used incorrectly.");
if (((YourRegistrationModel)validationContext.ObjectInstance).ConfirmPassword != value.ToString())
return new ValidationResult("Passwords must match.");
return ValidationResult.Success;
}
}
Now all you need to do is add [ComparePassword] to your password property, nothing to pass... simple and fairly clean
You can't pass a reference type to an attribute unless you do some rather lame reflection code.
In this situation, I would think creating a custom model binder would be a better idea and then checking the Password and ComparePassword at that point.
you need a STATIC method in your case:
EXAMPLE:
public static ValidationResult ValidateFrequency( double frequency, ValidationContext context )
{
if( context == null )
{
return ( ValidationResult.Success );
}
}
just as an example:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Globalization;
using System.Web.Mvc;
using System.Web.Security;
namespace GDNET.Web.Mvc.Validation
{
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public sealed class ValidatePasswordLengthAttribute : ValidationAttribute, IClientValidatable
{
private const string defaultErrorMessage = "'{0}' must be at least {1} characters long.";
private readonly int minRequiredPasswordLength = Membership.Provider.MinRequiredPasswordLength;
public ValidatePasswordLengthAttribute()
: base(defaultErrorMessage)
{
}
public override string FormatErrorMessage(string name)
{
return String.Format(CultureInfo.CurrentCulture, ErrorMessageString, name, minRequiredPasswordLength);
}
public override bool IsValid(object value)
{
string valueAsString = value as string;
return (valueAsString != null && valueAsString.Length >= minRequiredPasswordLength);
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
return new[]
{
new ModelClientValidationStringLengthRule(FormatErrorMessage(metadata.GetDisplayName()), minRequiredPasswordLength, int.MaxValue)
};
}
}
}
source: https://code.google.com/p/gdnetprojects/source/browse/trunk/Experiments/Common/GDNET.Web.Mvc/Validation/ValidatePasswordLengthAttribute.cs?r=69

Categories

Resources