Get error message when using custom validation attribute - c#

I'm using the CustomValidationAttribute like this
[CustomValidation(typeof(MyValidator),"Validate",ErrorMessage = "Foo")]
And my validator contains this code
public class MyValidator {
public static ValidationResult Validate(TestProperty testProperty, ValidationContext validationContext) {
if (string.IsNullOrEmpty(testProperty.Name)) {
return new ValidationResult(""); <-- how can I get the error message from the custom validation attribute?
}
return ValidationResult.Success;
}
}
So how can I get the error message from the custom validation attribute?

I know this is a little of an old post, but I will provide an better answer to the question.
The asker wants to use the CustomValidationAttribute and pass in an error message using the ErrorMessage property.
If you would like your static method to use the error message that you provided when decorating your property, then you return either:
new ValidationResult(string.Empty) or ValidationResult("") or ValidationResult(null).
The CustomValidationAttribute overrides the FormatErrorMessage of its base class and does a conditional check for string.IsNullOrEmpty.

There's no reliable way to get the error message from the attribute. Alternatively you could write a custom validation attribute:
[MyValidator(ErrorMessage = "Foo")]
public TestProperty SomeProperty { get; set; }
like this:
public class MyValidatorAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var testProperty = (TestProperty)value;
if (testProperty == null || string.IsNullOrEmpty(testProperty.Name))
{
return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
}
return null;
}
}
In this case the error message will be inferred from the custom validation attribute.

You can look into the following posting to get some ideas on how to do what you want to do (they use JS):
Custom validator error text through javascript?
Hope this helps.

The only way I have found that works is to validate the model from the post back method using TryValidateObject and if it fails, show the model again - then the error will show up.
[HttpPost]
public ActionResult Standard(Standard model)
{
var valContext = new ValidationContext(model, null, null);
var valResults = new List<ValidationResult>();;
bool b = Validator.TryValidateObject(model, valContext, valResults, true);
if(!b)
return View(model);
...

Related

how to change type validation error messages?

I'm using entity framework code first in an ASP MVC project, and I'd like to change the error message that appears for validation of a numeric type.
I have a property like
public decimal Amount1 { get; set; }
If I enter a non-number in the field, I get the message: The field Amount1 must be a number. How do I change that message?
For other validations, like Required I can just use the ErrorMessage parameter like: [Required(ErrorMessage = "My message...")]
Is there something similar for validating types?
Thank you.
Unfortunately Microsoft didn't expose any interfaces to change the default messages.
But if you are desperate enough to change these non friendly messages, you can do so by creating validation attribute for decimal, creating corresponding validator and finally register it with DataAnnotationsModelValidatorProvider at the application startup. Hope this helps.
UPDATE:
Sample below
Step 1: Create validation attribute
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property, AllowMultiple = false)]
public class ValidDecimalAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext) {
if (value == null || value.ToString().Length == 0) {
return ValidationResult.Success;
}
decimal d;
return !decimal.TryParse(value.ToString(), out d) ? new ValidationResult(ErrorMessage) : ValidationResult.Success;
}
}
Step 2: Create validator
public class ValidDecimalValidator : DataAnnotationsModelValidator<ValidDecimal>
{
public ValidDecimalValidator(ModelMetadata metadata, ControllerContext context, ValidDecimal attribute)
: base(metadata, context, attribute)
{
if (!attribute.IsValid(context.HttpContext.Request.Form[metadata.PropertyName]))
{
var propertyName = metadata.PropertyName;
context.Controller.ViewData.ModelState[propertyName].Errors.Clear();
context.Controller.ViewData.ModelState[propertyName].Errors.Add(attribute.ErrorMessage);
}
}
}
Step 3: Register the adapter in Global.asax under Application_Start() method or Main() method
DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(ValidDecimal), typeof(ValidDecimalValidator));
Step 4: Finally decorate your property in your model with this attribute
[ValidDecimal(ErrorMessage = "Only decimal numbers allowed")]
public decimal CPEHours { get; set; }
Hope it helps.
I couldn't find a clean solution. If there is something like [Required] you could override it in the same way. Only option I find is to remove and add another error into the model state. Again NOT the best option if you have better alternates, but does the job. This example only works if you have something like must be a number at the end. You can create a filter with this kind of loop:
foreach (var m in ModelState)
{
var errors = m.Value.Errors;
foreach (var error in errors)
{
if (error.ErrorMessage.EndsWith("must be a number"))
{
errors.Remove(error);
ModelState.AddModelError(m.Key, $"This is my own validation");
}
}
}
While it's not possible to change the whole message, you can at least change the string used to reference the field. Use the [Display(Name = "amount field"] attribute, like:
[BindProperty]
[Display(Name = "line length")]
public decimal? LineLength { get; set; }
If the user enters a string into a field like this, they will at least see an error message that reads "The value 'sdf' is not valid for line length."
Not a complete solution, but good enough in many scenarios.

Manual Validation against variables

Is there a way, for a given variable, to call existing validation functions against it?
I'm doing some additional manual validations in model binder, it would be nice, rather than doing:
if (Session["abc"] != "blah")
{
ModelState.AddModelError("", "whoops");
}
If I could somehow wire up to test said variable against a validator, so it would be more like:
var X = new Validatable(Session["abc"]);
X.addValidation(new RequiredAttribute());
X.addValidation(new RegexAttribute(some regex));
X.Validate()
which would add to ModelState error dictionary.
You could implement IValidatableObject on your ViewModel, this will set the errors in the ModelState dictionary on Model Binding. Then reuse the existing System.ComponentModel.DataAnnotations attributes for your implementation.
using System.ComponentModel.DataAnnotations;
using System.Web;
public class MyViewModel : IValidatableObject {
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) {
var sessionValue = HttpContext.Current.Session["abc"];
yield return new RequiredAttribute {
ErrorMessage = "error message: required"
}.GetValidationResult(sessionValue, validationContext);
const string pattern = "^([a-zA-Z0-9]+)$";
yield return new RegularExpressionAttribute(pattern) {
ErrorMessage = "error message: regex"
}.GetValidationResult(sessionValue, validationContext);
}
}

Shared validation error message for more than one field

In an ASP.NET MVC project I'm trying to validate several fields against a regular expression. However I would like to have only one validation message displayed if any of them fails (and highlight the ones failing).
I can make a custom validation for that, and annotating one of them with the id's of the rest kind of work although only highlights the one decorated with the attribute. But it looks to me as an overkill as I just want to reduce the message to one.
In the same form I will try to do the same for two checkboxes, both must be checked.
So as far as I understand if I use the summary to put a generic message won't be able to tell if it's failing for the checkboxes or for the fields.
Is there a simple way of achieving this?
You could write a custom validation that targets your entire view model class. I wrote the following when I wanted to check that at least one property was set. You can see that this targets the class itself and should therefore give you one message.
/// <summary>
/// A configurable class wide attribute that is used to determine if at least one property of a class has received a value.
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public class SingleValueConfigurableAttribute : ValidationAttribute, IClientValidatable
{
public SingleValueConfigurableAttribute(string errorKey)
{ ErrorMessage = Properties.Settings.Default[errorKey].ToString(); }
public override bool IsValid(object value)
{
var typeInfo = value.GetType();
var propertyInfo = typeInfo.GetProperties();
return propertyInfo.Any(property => null != property.GetValue(value, null));
}
public override string FormatErrorMessage(string name)
{
return ErrorMessage;
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
yield return new ModelClientValidationRule
{
ErrorMessage = String.IsNullOrEmpty(ErrorMessage) ? FormatErrorMessage(metadata.DisplayName) : ErrorMessage,
ValidationType = "enforcetrue"
};
}
}

Custom Dataannotations, Client side Validation, ViewModels, & Model State

This is an extension of an older question from this post:
Enforcing a model's boolean value to be true using data annotations
This gave me a lot of insight on how to get the client side custom validation working with jquery-unubtrusive-ajax.js.
Here is the answer code:
namespace Checked.Entitites
{
public class BooleanRequiredAttribute : ValidationAttribute, IClientValidatable
{
public override bool IsValid(object value)
{
return value != null && (bool)value == true;
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
//return new ModelClientValidationRule[] { new ModelClientValidationRule() { ValidationType = "booleanrequired", ErrorMessage = this.ErrorMessage } };
yield return new ModelClientValidationRule()
{
ValidationType = "booleanrequired",
ErrorMessage = this.ErrorMessageString
};
}
}
}
When i use this custom attribute in my models it works fine.
ex:
[BooleanRequired(ErrorMessage = "Please accept the terms")]
public bool Terms { get; set; }
When i use textboxfor in my views the error messages populate in the html validation summary.
#Html.CheckBoxFor(model => model.Terms, new { #class = "legalbox", #placeholder = "Terms" })
I also had to add this line to my jquery.unubtrusive-ajax.js
$.validator.unobtrusive.adapters.addBool("BooleanRequired", "required");
The issue i am having with this fix arises when i reference this model in the controller and check the modelstate. The modelstate is always false no matter what i have as the value for this custom validated field. It looks like the override isvalid is working in the client side validation but not on the server side. I need it to work on both and have been scouring trying to find a solution that isn't "ditch dataannotations and use something else".
The specific line i am using in my controller that will never pass with this field is as follows:
if (ModelState.IsValid) {
//Logic here
}
The rest of the model using the standard data-annotations validate correctly, i just cannot get this custom one to work the same way as the built in attributes. Its possible that the right solution would prevent me from having to add that line to the unobtrusive ajax file.
Any help would be appreciated.
Thanks

Validation of Guid

I have a strongly-typed view which has a DropDownListFor attribute on it.
Each item in the dropdown list is represented by a GUID.
What I'm after is a way to validate if a user selects an item from the dropdown list. At present i don't see anyway of doing this using Data Annotations.
Is there anyway of achieving this using Data Annotations so client and server side validation would work.
I'm guessing i need to make a custom method to do this but was wondering if anything already existed.
Actually, you can't use Required attribute with GUIDs (without the method I mention below) because they inherit from struct, and as such their default value is actually an instance of Guid.Empty, which will satisfy the requirements of the Required attribute. Now that being said, it is possible to get what you want you just need to make your property nullable, take this for example...
public class Person
{
[Required] //Only works because the Guid is nullable
public Guid? PersonId { get; set;}
public string FirstName { get; set;}
public string LastName { get; set;}
}
By marking the GUID nullable (using the ?, or Nullable if you prefer the long way) you let it stay as null when binding against what the browser sent. In your case, just make sure the value of the default option of the dropdown uses an empty string as it's value.
EDIT: The only caveat to this method is you end up having to use something like Person.GetValueOfDefault() everywhere and potentially testing for Guid.Empty. I got tired of doing this and ended up creating my own validation attribute to help simplify validating Guids (and any other types that have default values I want to treat as invalid such as int, DateTime, etc). However I don't have client side validation to go along with this yet, so validation only happens on the server. This can be combined with [Required] (designed to not duplicate functionality of [Required]) if you're ok with using nullable types. This would mean you still have to use GetValueOrDefault(), but at least then you don't have to test for Guid.Empty anymore. The Gist link has some XMLDocs with examples, I left them out here for brevity. I'm currently using it with ASP.NET Core.
EDIT: Updated to fix a bug with Nullable<>, and a bug with treating null as invalid. Added supporting classes to handle client side validation. See Gist for full code.
Gist: RequireNonDefaultAttribute
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false)]
public class RequireNonDefaultAttribute : ValidationAttribute
{
public RequireNonDefaultAttribute()
: base("The {0} field requires a non-default value.")
{
}
public override bool IsValid(object value)
{
if (value is null)
return true; //You can flip this if you want. I wanted leave the responsability of null to RequiredAttribute
var type = value.GetType();
return !Equals(value, Activator.CreateInstance(Nullable.GetUnderlyingType(type) ?? type));
}
}
Edited Answer
Upon re-reading your question, it sounds like you just want to know if a value is selected. If that's the case then just apply the RequiredAttribute to the Guid property and make it nullable on the model
public class GuidModel
{
[Required]
public Guid? Guid { get; set; }
public IEnumerable<Guid> Guids { get; set; }
}
then in the strongly typed View (with #model GuidModel)
#Html.ValidationMessageFor(m => m.Guid)
#Html.DropDownListFor(
m => m.Guid,
Model.Guids.Select(g => new SelectListItem {Text = g.ToString(), Value = g.ToString()}),
"-- Select Guid --")
Add the client validation JavaScript script references for client-side validation.
The controller looks like
public class GuidsController : Controller
{
public GuidRepository GuidRepo { get; private set; }
public GuidsController(GuidRepository guidRepo)
{
GuidRepo = guidRepo;
}
[HttpGet]
public ActionResult Edit(int id)
{
var guid = GuidRepo.GetForId(id);
var guids - GuidRepo.All();
return View(new GuidModel { Guid = guid, Guids = guids });
}
[HttpPost]
public ActionResult Edit(GuidModel model)
{
if (!ModelState.IsValid)
{
model.Guids = GuidRepo.All();
return View(model);
}
/* update db */
return RedirectToAction("Edit");
}
}
This will ensure that the Guid property is required for a model-bound GuidModel.
Original Answer
I don't believe that there is a ready made Data Annotation Validation attribute that is capable of doing this. I wrote a blog post about one way to achieve this; the post is using an IoC container but you could take the hard coded dependency if you're wanting to get something working.
Something like
public class ValidGuidAttribute : ValidationAttribute
{
private const string DefaultErrorMessage = "'{0}' does not contain a valid guid";
public ValidGuidAttribute() : base(DefaultErrorMessage)
{
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var input = Convert.ToString(value, CultureInfo.CurrentCulture);
// let the Required attribute take care of this validation
if (string.IsNullOrWhiteSpace(input))
{
return null;
}
// get all of your guids (assume a repo is being used)
var guids = new GuidRepository().AllGuids();
Guid guid;
if (!Guid.TryParse(input, out guid))
{
// not a validstring representation of a guid
return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
}
// is the passed guid one we know about?
return guids.Any(g => g == guid) ?
new ValidationResult(FormatErrorMessage(validationContext.DisplayName)) : null;
}
}
and then on the model you send into the controller action
public class GuidModel
{
[ValidGuid]
public Guid guid { get; set; }
}
This gives you server side validation. You could write client side validation to do this as well, perhaps using RemoteAttribute but I don't see a lot of value in this case as the only people that are going to see this client side validation are people that are messing with values in the DOM; it would be of no benefit to your normal user.
I know this is an old question now, but if anyone else is interested I managed to get around this by creating an [IsNotEmpty] annotation (making the Guid nullable wasn't an option in my case).
This uses reflection to work out whether there's an implementation of Empty on the property, and if so compares it.
public class IsNotEmptyAttribute : ValidationAttribute
{
public override bool IsValid(object value)
{
if (value == null) return false;
var valueType = value.GetType();
var emptyField = valueType.GetField("Empty");
if (emptyField == null) return true;
var emptyValue = emptyField.GetValue(null);
return !value.Equals(emptyValue);
}
}
Regex actually does work (if you use the right one!)
[Required]
[RegularExpression("^((?!00000000-0000-0000-0000-000000000000).)*$", ErrorMessage = "Cannot use default Guid")]
public Guid Id { get; set; }
Non Empty Guid Validator
prevents 00000000-0000-0000-0000-000000000000
Attribute:
using System.ComponentModel.DataAnnotations;
[AttributeUsage(AttributeTargets.Property)]
internal class NonEmptyGuidAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if ((value is Guid) && Guid.Empty == (Guid)value)
{
return new ValidationResult("Guid cannot be empty.");
}
return null;
}
}
Model:
using System.ComponentModel.DataAnnotations;
public class Material
{
[Required]
[NonEmptyGuid]
public Guid Guid { get; set; }
}
If the custom validation doesn't require a high reuse in your system (i.e. without the need for a custom validation attribute), there's another way to add custom validation to a ViewModel / Posted data model, viz by using IValidatableObject.
Each error can be bound to one or more model properties, so this approach still works with e.g. Unobtrusive validation in MVC Razor.
Here's how to check a Guid for default (C# 7.1):
public class MyModel : IValidatableObject // Implement IValidatableObject
{
[Required]
public string Name {get; set;}
public Guid SomeGuid {get; set;}
... other properties here
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (SomeGuid == default)
{
yield return new ValidationResult(
"SomeGuid must be provided",
new[] { nameof(SomeGuid) });
}
}
}
More on IValidatableObject here
You can validate the Guid if it contains default values - "00000000-0000-0000-0000-000000000000".
if (model.Id == Guid.Empty)
{
// TODO: handle the error or do something else
}
You can create a custom validator for that.
using System;
using System.ComponentModel.DataAnnotations;
namespace {{Your_App_Name}}.Pages
{
public class NotEmptyGuidAttribute: ValidationAttribute
{
protected override ValidationResult IsValid(object guidValue, ValidationContext validationContext)
{
var emptyGuid = new Guid();
var guid = new Guid(guidValue.ToString());
if (guid != emptyGuid){
return null;
}
return new ValidationResult(ErrorMessage, new[] {validationContext.MemberName});
}
}
}
You can use it like this
[EmptyGuidValidator(ErrorMessage = "Role is required.")]
public Guid MyGuid{ get; set; }
This worked for me.

Categories

Resources