Custom Model Validator for Integer value in ASP.NET Core Web API - data-annotations

I have developed a custom validator Attribute class for checking Integer values in my model classes. But the problem is this class is not working. I have debugged my code but the breakpoint is not hit during debugging the code. Here is my code:
public class ValidateIntegerValueAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (value != null)
{
int output;
var isInteger = int.TryParse(value.ToString(), out output);
if (!isInteger)
{
return new ValidationResult("Must be a Integer number");
}
}
return ValidationResult.Success;
}
}
I have also an Filter class for model validation globally in application request pipeline. Here is my code:
public class MyModelValidatorFilter: IActionFilter
{
public void OnActionExecuting(ActionExecutingContext context)
{
if (context.ModelState.IsValid)
return;
var errors = new Dictionary<string, string[]>();
foreach (var err in actionContext.ModelState)
{
var itemErrors = new List<string>();
foreach (var error in err.Value.Errors){
itemErrors.Add(error.Exception.Message);
}
errors.Add(err.Key, itemErrors.ToArray());
}
actionContext.Result = new OkObjectResult(new MyResponse
{
Errors = errors
});
}
}
The model class with validation is below:
public class MyModelClass
{
[ValidateIntegerValue(ErrorMessage = "{0} must be a Integer Value")]
[Required(ErrorMessage = "{0} is required")]
public int Level { get; set; }
}
Can anyone please let me know why the attribute integer validation class is not working.

Model validation comes into play after the model is deserialized from the request. If the model contains integer field Level and you send value that could not be deserialized as integer (e.g. "abc"), then model will not be even deserialized. As result, validation attribute will also not be called - there is just no model for validation.
Taking this, there is no much sense in implementing such ValidateIntegerValueAttribute. Such validation is already performed by deserializer, JSON.Net in this case. You could verify this by checking model state in controller action. ModelState.IsValid will be set to false and ModelState errors bag will contain following error:
Newtonsoft.Json.JsonReaderException: Could not convert string to
integer: abc. Path 'Level', ...
One more thing to add: for correct work of Required validation attribute, you should make the underlying property nullable. Without this, the property will be left at its default value (0) after model deserializer. Model validation has no ability to distinguish between missed value and value equal to default one. So for correct work of Required attribute make the property nullable:
public class MyModelClass
{
[Required(ErrorMessage = "{0} is required")]
public int? Level { get; set; }
}

Related

How to code a Polymorphic Model Binder and Provider in MVC 6

This question has been asked before on SO and elsewhere in the context of MVC3 and there are bits and bobs about it related to ASP.NET Core RC1 and RC2 but niot a single example that actually shows how to do it the right way in MVC 6.
There are the following classes
public abstract class BankAccountTransactionModel {
public long Id { get; set; }
public DateTime Date { get; set; }
public decimal Amount { get; set; }
public readonly string ModelType;
public BankAccountTransactionModel(string modelType) {
this.ModelType = modelType;
}
}
public class BankAccountTransactionModel1 : BankAccountTransactionModel{
public bool IsPending { get; set; }
public BankAccountTransactionModel1():
base(nameof(BankAccountTransactionModel1)) {}
}
public class BankAccountTransactionModel2 : BankAccountTransactionModel{
public bool IsPending { get; set; }
public BankAccountTransactionModel2():
base(nameof(BankAccountTransactionModel2)) {}
}
In my controller I have something like this
[Route(".../api/[controller]")]
public class BankAccountTransactionsController : ApiBaseController
{
[HttpPost]
public IActionResult Post(BankAccountTransactionModel model) {
try {
if (model == null || !ModelState.IsValid) {
// failed to bind the model
return BadRequest(ModelState);
}
this.bankAccountTransactionRepository.SaveTransaction(model);
return this.CreatedAtRoute(ROUTE_NAME_GET_ITEM, new { id = model.Id }, model);
} catch (Exception e) {
this.logger.LogError(LoggingEvents.POST_ITEM, e, string.Empty, null);
return StatusCode(500);
}
}
}
My client may post either BankAccountTransactionModel1 or BankAccountTransactionModel2 and I would like to use a custom model binder to determine which concrete model to bind based on the value in the property ModelType which is defined on the abstract base class BankAccountTransactionModel.
Thus I have done the following
1) Coded up a simple Model Binder Provider that checks that the type is BankAccountTransactionModel. If this is the case then an instance of BankAccountTransactionModelBinder is returned.
public class BankAccountTransactionModelBinderProvider : IModelBinderProvider {
public IModelBinder GetBinder(ModelBinderProviderContext context) {
if (context == null) throw new ArgumentNullException(nameof(context));
if (context.Metadata.IsComplexType && !context.Metadata.IsCollectionType) {
var type1 = context.Metadata.ModelType;
var type2 = typeof(BankAccountTransactionModel);
// some other code here?
// tried this but not sure what to do with it!
foreach (var property in context.Metadata.Properties) {
propertyBinders.Add(property, context.CreateBinder(property));
}
if (type1 == type2) {
return new BankAccountTransactionModelBinder(propertyBinders);
}
}
return null;
}
}
2) Coded up the BankAccountTransactionModel
public class BankAccountTransactionModelBinder : IModelBinder {
private readonly IDictionary<ModelMetadata, IModelBinder> _propertyBinders;
public BankAccountTransactionModelBinder(IDictionary<ModelMetadata, IModelBinder> propertyBinders){
this._propertyBinders = propertyBinders;
}
public Task BindModelAsync(ModelBindingContext bindingContext) {
if (bindingContext == null) throw new ArgumentNullException(nameof(bindingContext));
// I would like to be able to read the value of the property
// ModelType like this or in some way...
// This does not work and typeValue is...
var typeValue = bindingContext.ValueProvider.GetValue("ModelType");
// then once I know whether it is a Model1 or Model2 I would like to
// instantiate one and get the values from the body of the Http
// request into the properties of the instance
var model = Activator.CreateInstance(type);
// read the body of the request in some way and set the
// properties of model
var key = some key?
var result = ModelBindingResult.Success(key, model);
// Job done
return Task.FromResult(result);
}
}
3) Lastly I register the provider in Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(options => {
options.ModelBinderProviders.Insert(0, new BankAccountTransactionModelBinderProvider());
options.Filters.Add(typeof (SetUserContextAttribute));
});
The whole thing seems OK in that the provider is actually invoked and the same is the case for the model builder. However, I cannot seem to get anywhere with coding the logic in BindModelAsync of the model binder.
As already stated by the comments in the code, all that I'd like to do in my model binder is to read from the body of the http request and in particular the value of ModelType in my JSON. Then on the bases of that I'd like to instantiate either BankAccountTransactionModel1 or BankAccountTransactionModel and finally assign values to the property of this instance by reading them of the JSON in the body.
I know that this is a only a gross approximation of how it should be done but I would greatly appreciate some help and perhaps example of how this could or has been done.
I have come across examples where the line of code below in the ModelBinder
var typeValue = bindingContext.ValueProvider.GetValue("ModelType");
is supposed to read the value. However, it does not work in my model binder and typeValue is always something like below
typeValue
{}
Culture: {}
FirstValue: null
Length: 0
Values: {}
Results View: Expanding the Results View will enumerate the IEnumerable
I have also noticed that
bindingContext.ValueProvider
Count = 2
[0]: {Microsoft.AspNetCore.Mvc.ModelBinding.RouteValueProvider}
[1]: {Microsoft.AspNetCore.Mvc.ModelBinding.QueryStringValueProvider}
Which probably means that as it is I do not stand a chance to read anything from the body.
Do I perhaps need a "formatter" in the mix in order to get desired result?
Does a reference implementation for a similar custom model binder already exist somewhere so that I can simply use it, perhaps with some simple mods?
Thank you.

ASP.NET MVC 4 Custom Validation

How I can develop a custom validation rule in MVC? I.e. I have many decimal properties in my model and I would like to make a range validation from 1 to 100 to all of them without use data annotation in each.
You can add validation to your whole model by making it implement IValidatableObject, for example:
public class MyModel: IValidatableObject
{
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (MyProperty > 100 || MyProperty < 1)
yield return new ValidationResult("MyProperty is out of range (1..100)", new [] { "MyProperty" });
...
}
}
Here's a resource that has a more elaborate example.
In case you want to cover all decimal properties automatically you can do this:
public abstract class MyValidatableBaseModel: IValidatableObject
{
protected abstract virtual Type GetSubClassType();
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
var decimalProperties = GetSubClassType().GetProperties().Where(p => p.PropertyType == typeof(decimal));
foreach (var decimalProperty in decimalProperties)
{
var decimalValue = (decimal)decimalProperty.GetValue(this, null);
var propertyName = decimalProperty.Name;
//do validation here and use yield return new ValidationResult
}
}
}
public class MyModel : MyValidatableBaseModel
{
protected override Type GetSubClassType()
{
return GetType();
}
}
Any model that inherits from MyValidatableBaseModel (in the example, MyModel) and overrides GetSubClassType to returns it's own type will have that automatic validation for decimal properties

How to make a property required based on multiple condition?

I have a list of Pair of radio buttons (Yes/No):
Q1.(Y)(N)
Q2.(Y)(N)
Q3.(Y)(N)
Q4.(Y)(N)
and I have one property in my model
public string MedicalExplanation { get; set; }
My goal is to make Explanation required if any of the radio button has been set to true.
My first try was to use [Required] but it does not handle conditions.
Then I decided to use third party tool like MVC Foolproof Validation
I used it like this:
[RequiredIf("Q1", true, ErrorMessage = "You must explain any \"Yes\" answers!")]
Now the problem is I don't know how to make it required if any of the other Q2, Q3, Q4 is checked.
Please advice
In your ViewModel, create a bool property like this:
public bool IsMedicalExplanationRequired
{
get
{
return Q1 || Q2 || Q3 || Q4;
}
}
Then, use your RequiredIf attribute like this:
[RequiredIf("IsMedicalExplanationRequired", true, ErrorMessage = "You must explain any \"Yes\" answers!")]
UPDATE:
If your Q1 - Q4 properties are of type bool?, just change the IsMedicalExplanationRequired property like below:
public bool IsMedicalExplanationRequired
{
get
{
return Q1.GetValueOrDefault() || Q2.GetValueOrDefault() || Q3.GetValueOrDefault() || Q4.GetValueOrDefault();
}
}
This is how I did it:
First I created a custom validation attribute which gets a string array of fields to check passed in:
public class ValidateAtLeastOneChecked : ValidationAttribute {
public string[] CheckBoxFields {get; set;}
public ValidateAtLeastOneChecked(string[] checkBoxFields) {
CheckBoxFields = checkBoxFields;
}
protected override ValidationResult IsValid(Object value, ValidationContext context) {
Object instance = context.ObjectInstance;
Type type = instance.GetType();
foreach(string s in CheckBoxFields) {
Object propertyValue = type.GetProperty(s).GetValue(instance, null);
if (bool.Parse(propertyValue.ToString())) {
return ValidationResult.Success;
}
}
return new ValidationResult(base.ErrorMessageString);
}
}
Then I use it like this (I am using resource files to localize my error messages):
[ValidateAtLeastOneChecked(new string[] { "Checkbox1", "Checkbox2", "Checkbox3", "Checkbox4" }, ErrorMessageResourceType=typeof(ErrorMessageResources),ErrorMessageResourceName="SelectAtLeastOneTopic")]
public bool Checkbox1{ get; set; }
public bool Checkbox2{ get; set; }
public bool Checkbox3{ get; set; }
public bool Checkbox4{ get; set; }
It is only actually setting the error on the first checkbox. If you are using the built in css highlighting to highlight fields in error you will need to modify this slightly to make it look right, but I felt this was a clean solution which was reusable and allowed me to take advantage of the support for resource files in validation attributes.

MVC3 Object with IValidatableObject - Validate fires multiple times per TryUpdateModel()

I have an object class generated from a T4, with a partial SafeClass to do validation, which looks like this:
public partial class Address : IValidatableObject
This class has a Validate method like so:
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
//ValidationResponse is a custom struct that holds a few properties.
ValidationResponse r = this.IsValidAddress(); //a method that does some checks
if (!r.IsValid)
{
yield return new ValidationResult(r.Message, new string[] { "Address1" });
}
}
In my Controller's HttpPost event, I have the line:
if (!TryUpdateModel(_policy))
return View(_policy);
Note that the Policy object contains a Person object, which contains an Address object (pieces of all 3 are rendered in the same view; may be relevant, I don't know).
When TryUpdateModel() executes, the Address's Validate method gets called 3 times. I verified it's not triggering for other addresses on the policy. I have also verified that the Controller's HttpPost method is only being called once. It's the single execution of TryUpdateModel() that fires off 3 Validates.
Has anybody else run into this sort of thing? Any ides what's going on?
I had encoutered similar issue running this code
if (isPoorSpecimen)
{
errors.Add(new ValidationResult("Your are reporting poor specimen condition, please specify what is the reason"
, new string[] { "SpecimenCondition", "QtyOk", "DocumentedOk", "ColdChainOk", "PackagingOK", "IsProcessable" }));
}
It will show the error message 6 times in Html.ValidationSummary() .
The solution is to highligt a single control per error.
if (isPoorSpecimen)
{
errors.Add(new ValidationResult("Your are reporting poor specimen condition, please specify what is the reason"
, new string[] { "SpecimenCondition" }));
}
It is called 3 times, because the Address instance is validated first as a standalone entity, then as a member of a standalone Person entity, and finally as a member of the Person entity being a member of the Policy entity.
I would suggest the following solutions:
1) Remove IValidatableObject from all the classes but Policy and validate its members manually:
public class Policy : IValidatableObject
{
public Person PersonInstance { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
// validate Policy...
// then validate members explicitly
var results = PersonInstance.Validate(validationContext);
}
}
public class Person
{
public Address AddressInstance { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
// validate Person...
// then validate members explicitly
var results = AddressInstance.Validate(validationContext);
}
}
public class Address
{
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
// validate Address...
}
}
2) Or add a flag to each class to validate only once, since the instance across the calls is the same:
private bool validated = false;
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (!validated)
{
validated = true;
// do validation
}
}
Hope this helps.

ASP.NET MVC 2.0 Validation and ErrorMessages

I need to set the ErrorMessage property of the DataAnnotation's validation attribute in MVC 2.0. For example I should be able to pass an ID instead of the actual error message for the Model property, for example...
[StringLength(2, ErrorMessage = "EmailContentID")]
[DataType(DataType.EmailAddress)]
public string Email { get; set; }
Then use this ID ("EmailContentID") to retrieve some content(error message) from a another service e.g database. Then the error error message is displayed to the user instead of the ID. In order to do this I need to set the DataAnnotation validation attribute’s ErrorMessage property.
It seems like a stright forward task by just overriding the DataAnnotationsModelValidatorProvider‘s protected override IEnumerable GetValidators(ModelMetadata metadata, ControllerContext context, IEnumerable attributes)
However it is complicated now....
A. MVC DatannotationsModelValidator’s ErrorMessage property is readonly. So I cannot set anything here
B. System.ComponentModel.DataAnnotationErrorMessage property(get and set) which is already set in MVC DatannotationsModelValidator so I cannot set it again. If I try to set it I get “The property cannot set more than once…” error message.
public class CustomDataAnnotationProvider : DataAnnotationsModelValidatorProvider
{
protected override IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context, IEnumerable<Attribute> attributes)
{
IEnumerable<ModelValidator> validators = base.GetValidators(metadata, context, attributes);
foreach (ValidationAttribute validator in validators.OfType<ValidationAttribute>())
{
messageId = validator.ErrorMessage;
validator.ErrorMessage = "Error string from DB And" + messageId ;
}
//......
}
}
Can anyone please give me the right direction on this?
Thanks in advance.
I personally wouldnt create a new provider inheriting the original provider but I would create a new Attribute inheriting either the ValidationAttribute or the StringLengthAttribute.
Something Like this
public class MyNewValidationAttribute : StringLengthAttribute
{
public int ErrorMessageId
{
set {
var myErrorMessageFromDB = ""; //query database and get message.
this.ErrorMessage = myErrorMessageFromDB;
}
}
}
Now when you assign the attribute
[StringLength(2, ErrorMessageId = 1 //equals id from database
)]

Resources