ASP.NET Core - Custom model validation - c#

In MVC when we post a model to an action we do the following in order to validate the model against the data annotation of that model:
if (ModelState.IsValid)
If we mark a property as [Required], the ModelState.IsValid will validate that property if contains a value or not.
My question: How can I manually build and run custom validator?
P.S. I am talking about backend validator only.

In .NET Core, you can simply create a class that inherits from ValidationAttribute. You can see the full details in the ASP.NET Core MVC Docs.
Here's the example taken straight from the docs:
public class ClassicMovieAttribute : ValidationAttribute
{
private int _year;
public ClassicMovieAttribute(int Year)
{
_year = Year;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
Movie movie = (Movie)validationContext.ObjectInstance;
if (movie.Genre == Genre.Classic && movie.ReleaseDate.Year > _year)
{
return new ValidationResult(GetErrorMessage());
}
return ValidationResult.Success;
}
}
I've adapted the example to exclude client-side validation, as requested in your question.
In order to use this new attribute (again, taken from the docs), you need to add it to the relevant field:
[ClassicMovie(1960)]
[DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }
Here's another, simpler example for ensuring that a value is true:
public class EnforceTrueAttribute : ValidationAttribute
{
public EnforceTrueAttribute()
: base("The {0} field must be true.") { }
public override bool IsValid(object value) =>
value is bool valueAsBool && valueAsBool;
}
This is applied in the same way:
[EnforceTrue]
public bool ThisShouldBeTrue { get; set; }
Edit: Front-End Code as requested:
<div asp-validation-summary="All" class="text-danger"></div>
The options are All, ModelOnly or None.

To create a custom validation attribute in .Net Core, you need to inherit from IModelValidator and implement Validate method.
Custom validator
public class ValidUrlAttribute : Attribute, IModelValidator
{
public string ErrorMessage { get; set; }
public IEnumerable<ModelValidationResult> Validate(ModelValidationContext context)
{
var url = context.Model as string;
if (url != null && Uri.IsWellFormedUriString(url, UriKind.Absolute))
{
return Enumerable.Empty<ModelValidationResult>();
}
return new List<ModelValidationResult>
{
new ModelValidationResult(context.ModelMetadata.PropertyName, ErrorMessage)
};
}
}
The model
public class Product
{
public int ProductId { get; set; }
[Required]
public string ProductName { get; set; }
[Required]
[ValidUrl]
public string ProductThumbnailUrl { get; set; }
}
Will this approach give opportunity to work with "ModelState.IsValid" property in controller action method?
Yes! The ModelState object will correctly reflect the errors.
Can this approach be applied to the model class? Or it can be used with model class properties only?
I don't know if that could be applied onto class level. I know you can get the information about the class from ModelValidationContext though:
context.Model: returns the property value that is to be validated
context.Container: returns the object that contains the property
context.ActionContext: provides context data and describes the action method that processes the request
context.ModelMetadata: describes the model class that is being validated in detail
Notes:
This validation attribute doesn't work with Client Validation, as requested in OP.

Related

Localize DataAnnotations in Asp.net core

In my asp.net core (.net5) application I have a form with a required field. When the field is empty I have
"The XX field is required"... In English... I want to translate it in French. I mean, I don't really want to translate, I want to use the French version of the message. I don't want to add Resource files, because I have any custom strings to translate, I just want to use existing messages, but in French.
I started to read here but did't really get the point if the article really proposes me to translate each message manually by myself.
I added this one in the Configure
var supportedCultures = new[] { "fr-FR" };
var localizationOptions = new RequestLocalizationOptions().SetDefaultCulture(supportedCultures[0])
.AddSupportedCultures(supportedCultures)
.AddSupportedUICultures(supportedCultures);
app.UseRequestLocalization(localizationOptions);
however this didn't change the message...
nor setting the culture params via URL, like this
If you just want one french version, you just need to define the error message in [Required]
attribute, like:
public class MyModel
{
[Required(ErrorMessage = "Le champ Nom est obligatoire")]
public string Nom { get; set; }
}
And if your shared code refers to the documention you shared, you need to add Resource file to
define the translated message, or the message will not changed.
Edit:
If you have other properties, you can define a customRequired attribute:
public class CustomRequiredAttribute : ValidationAttribute
{
public CustomRequiredAttribute()
{
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (value == null)
{
ErrorMessage = $"Le champ {validationContext.DisplayName} est obligatoire";
return new ValidationResult(ErrorMessage);
}
return ValidationResult.Success;
}
}
Model:
public class MyModel
{
[CustomRequired] //custom attribute
public string Nom { get; set; }
[CustomRequired]
public string PreNom { get; set; }
}
To use your custom attribute, you should add ModelState.IsValid in your post action, so if invalid, it will return the input view to show the errorMessages,like:
[HttpPost]
public IActionResult Privacy(MyModel myModel)
{
if (!ModelState.IsValid)
return View("Index");
return View();
}

How can I enhance ModelBinder in ASP.NET Core (update property values for a model class)

I would like to enhance final result that ModelBinder returns.
For example:
public class MyModel
{
public int Order {get;set;}
[MyUpperCaseAttribute]
public string Title {get;set;}
}
In API method I expect that all string properties in MyModel which has MyUpperCaseAttribute is in upper case.
For example:
[HttpPost("AddRecord")]
public async Task<ActionResult<int>> AddRecord(MyModel model)
{
model.Title should be upper case, even if send from client in lower case.
}
My idea was to override default ModelBinder and enumerate through all properties and check if property is string and has MyUpperCaseAttribute and correct property value to upper case. I check documentation, but doesn't examples doesn't fill right, since they completely redesign what is returned. I would like to just modify result properties.
What would be the best option to achieve desired behaviour?
Important: (edited):
It would be nice if directive attributes could be stacked:
public class MyModel
{
public int Order {get;set;}
[MyUpperCaseAttribute]
[RemoveSpacesAttribute]
public string Title {get;set;}
}
Edited:
It looks similar to this, but if not other, this is ASP.NET Core, and on link is just ASP.NET. Method, properties, interfaces... are not the same.
I should say, that it would be nice if JSON case rule would work:
public class MyModel
{
public int Order {get;set;}
public string Title {get;set;}
}
should work if {order: 1, title: "test"} (notice lowercase) is send from JavaScript.
This might not be the 'best' option, but I would just use .ToUpper() extension method instead of a custom attribute filter.
public class MyModel
{
private string _title;
public int Order {get;set;}
public string Title { get => _title.ToUpper(); set => _title = value.ToUpper(); }
}
There's a big red herring here, and that's the fact that it appears that this is the sort of thing that could and should be accomplished via model binding. Unfortunately, that's not the case in ASP.Net Core Web API: because the incoming data is JSON, it is in fact handled by input formatters, not model binders. Therefore, in order to achieve the desired effect, you need to create your own custom input formatter that replaces the standard JsonInputFormatter.
First the attribute:
[AttributeUsage(AttributeTargets.Property)]
public class ToUppercaseAttribute : Attribute
{
}
Then we decorate our model class with it:
public class MyModel
{
public int Order { get; set; }
[ToUppercase]
public string Title { get; set; }
}
Now create our custom input formatter that checks for that attribute and transforms the output if necessary. In this case, it simply wraps and delegates to JsonInputFormatter to do the heavy lifting as normal, then modifies the result if it finds our ToUppercaseAttribute attribute on any string property:
public class ToUppercaseJsonInputFormatter : TextInputFormatter
{
private readonly JsonInputFormatter _jsonInputFormatter;
public ToUppercaseJsonInputFormatter(JsonInputFormatter jsonInputFormatter)
{
_jsonInputFormatter = jsonInputFormatter;
foreach (var supportedEncoding in _jsonInputFormatter.SupportedEncodings)
SupportedEncodings.Add(supportedEncoding);
foreach (var supportedMediaType in _jsonInputFormatter.SupportedMediaTypes)
SupportedMediaTypes.Add(supportedMediaType);
}
public override Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context, Encoding encoding)
{
var result = _jsonInputFormatter.ReadRequestBodyAsync(context, encoding);
foreach (var property in context.ModelType.GetProperties().Where(p => p.PropertyType.IsAssignableFrom(typeof(string))
&& p.CustomAttributes.Any(a => a.AttributeType.IsAssignableFrom(typeof(ToUppercaseAttribute)))))
{
var value = (string)property.GetValue(result.Result.Model);
property.SetValue(result.Result.Model, value.ToUpper());
}
return result;
}
}
Next we create an extension method that makes it simple to substitute the default JsonInputFormatter with our custom formatter:
public static class MvcOptionsExtensions
{
public static void UseToUppercaseJsonInputFormatter(this MvcOptions opts)
{
if (opts.InputFormatters.FirstOrDefault(f => f is JsonInputFormatter && !(f is JsonPatchInputFormatter)) is JsonInputFormatter jsonInputFormatter)
{
var jsonInputFormatterIndex = opts.InputFormatters.IndexOf(jsonInputFormatter);
opts.InputFormatters[jsonInputFormatterIndex] = new ToUppercaseJsonInputFormatter(jsonInputFormatter);
}
}
}
And finally, call that method to effect the replacement in Startup.cs:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services
.AddMvc(options => options.UseToUppercaseJsonInputFormatter());
}
}
Et voilĂ !
You can do this thing inside your MyUpperCaseAttribute as follows:
public class MyUpperCaseAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if(value != null)
{
validationContext.ObjectType
.GetProperty(validationContext.MemberName)
.SetValue(validationContext.ObjectInstance, value.ToString().ToUpper(), null);
}
return null;
}
}
Property value will be converted to UpperCase during Model Binding. I have checked it in my side and it works perfectly.

Set RegularExpression Dynamically in Model

I need to set RegularExpression Dynamically in Model.
I mean, I have stored RegularExpression in table and then I will store that value in one variable.
Now I want to supply that variable in Regular Express validation.
i.e
[RegularExpression(VariableValue, ErrorMessage = "Valid Phone is required")]
Something like
i.e
string CustomExpress = "#"^(\+|\d)(?:\+?1[-. ]?)?\(?([0-9]{2})\)?[-. ]?([0-9]{1})[-. ]?([0-9]{9})$" (from Database's table)
[RegularExpression(CustomExpress, ErrorMessage = "Valid Phone is required")]
public string Phone { get; set; }
You have two options, either you create your own validation attribute or you make your whole model "validatable".
Option 1
public class RegexFromDbValidatorAttribute : ValidationAttribute
{
private readonly IRepository _db;
//parameterless ctor that initializes _db
public override ValidationResult IsValid(object value, ValidationContext context)
{
string regexFromDb = _db.GetRegex();
Regex r = new Regex(regexFromDb);
if (value is string && r.IsMatch(value as string)){
return ValidationResult.Success;
}else{
return new ValidationResult(FormatMessage(context.DisplayName));
}
}
}
Then on your model:
[RegexFromDbValidator]
public string Telephone {get; set;}
Option 2
public SomeModel : IValidatableObject
{
private readonly IRepository _db;
//don't forget to initialize _db in ctor
public string Telephone {get; set;}
public IEnumerable<ValidationResult> Validate(ValidationContext context)
{
string regexFromDb = _db.GetRegex();
Regex r = new Regex(regexFromDb);
if (!r.IsMatch(Telephone))
yield return new ValidationResult("Invalid telephone number", new []{"Telephone"});
}
}
Here's a good resource that explains how to create validation attributes
Here's an example of using IValidatableObject
As Murali stated Data Annotation attributes values must be compile time constants.
If you want to perform dynamic validation based on other model values you can try with some kind of third party framework (e.g. Fluent Validation, it even can be integrated in ASP.NET's model validation too).
I believe there might be a way to implement this by inheriting the IValidatableObject interface. Upon doing so whenever the ModelState gets validated on the server side you could perform all the necessary checks that you wish.
It would look something like:
public class SomeClass: IValidatableObject {
private RegEx validation;
public SomeClass(RegEx val) {
this.validation = val;
}
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
var results = new List<ValidationResult>();
// perform validation logic - check regex etc...
// if an error occurs:
results.Add(new ValidationResult('error message'));
}
}

Disable Required validation attribute under certain circumstances

I was wondering if it is possible to disable the Required validation attribute in certain controller actions. I am wondering this because on one of my edit forms I do not require the user to enter values for fields that they have already specified previously. However I then implement logic that when they enter a value it uses some special logic to update the model, such as hashing a value etc.
Any sugestions on how to get around this problem?
EDIT:
And yes client validation is a problem here to, as it will not allow them to submit the form without entering a value.
This problem can be easily solved by using view models. View models are classes that are specifically tailored to the needs of a given view. So for example in your case you could have the following view models:
public UpdateViewView
{
[Required]
public string Id { get; set; }
... some other properties
}
public class InsertViewModel
{
public string Id { get; set; }
... some other properties
}
which will be used in their corresponding controller actions:
[HttpPost]
public ActionResult Update(UpdateViewView model)
{
...
}
[HttpPost]
public ActionResult Insert(InsertViewModel model)
{
...
}
If you just want to disable validation for a single field in client side then you can override the validation attributes as follows:
#Html.TextBoxFor(model => model.SomeValue,
new Dictionary<string, object> { { "data-val", false }})
I know this question has been answered a long time ago and the accepted answer will actually do the work. But there's one thing that bothers me: having to copy 2 models only to disable a validation.
Here's my suggestion:
public class InsertModel
{
[Display(...)]
public virtual string ID { get; set; }
...Other properties
}
public class UpdateModel : InsertModel
{
[Required]
public override string ID
{
get { return base.ID; }
set { base.ID = value; }
}
}
This way, you don't have to bother with client/server side validations, the framework will behave the way it's supposed to. Also, if you define a [Display] attribute on the base class, you don't have to redefine it in your UpdateModel.
And you can still use these classes the same way:
[HttpPost]
public ActionResult Update(UpdateModel model)
{
...
}
[HttpPost]
public ActionResult Insert(InsertModel model)
{
...
}
You can remove all validation off a property with the following in your controller action.
ModelState.Remove<ViewModel>(x => x.SomeProperty);
#Ian's comment regarding MVC5
The following is still possible
ModelState.Remove("PropertyNameInModel");
Bit annoying that you lose the static typing with the updated API. You could achieve something similar to the old way by creating an instance of HTML helper and using NameExtensions Methods.
Client side
For disabling validation for a form, multiple options based on my research is given below. One of them would would hopefully work for you.
Option 1
I prefer this, and this works perfectly for me.
(function ($) {
$.fn.turnOffValidation = function (form) {
var settings = form.validate().settings;
for (var ruleIndex in settings.rules) {
delete settings.rules[ruleIndex];
}
};
})(jQuery);
and invoking it like
$('#btn').click(function () {
$(this).turnOffValidation(jQuery('#myForm'));
});
Option 2
$('your selector here').data('val', false);
$("form").removeData("validator");
$("form").removeData("unobtrusiveValidation");
$.validator.unobtrusive.parse("form");
Option 3
var settings = $.data($('#myForm').get(0), 'validator').settings;
settings.ignore = ".input";
Option 4
$("form").get(0).submit();
jQuery('#createForm').unbind('submit').submit();
Option 5
$('input selector').each(function () {
$(this).rules('remove');
});
Server Side
Create an attribute and mark your action method with that attribute. Customize this to adapt to your specific needs.
[AttributeUsage(AttributeTargets.All)]
public class IgnoreValidationAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var modelState = filterContext.Controller.ViewData.ModelState;
foreach (var modelValue in modelState.Values)
{
modelValue.Errors.Clear();
}
}
}
A better approach has been described here Enable/Disable mvc server side validation dynamically
Personally I would tend to use the approach Darin Dimitrov showed in his solution.
This frees you up to be able to use the data annotation approach with validation AND have separate data attributes on each ViewModel corresponding to the task at hand.
To minimize the amount of work for copying between model and viewmodel you should look at AutoMapper or ValueInjecter. Both have their individual strong points, so check them both.
Another possible approach for you would be to derive your viewmodel or model from IValidatableObject. This gives you the option to implement a function Validate.
In validate you can return either a List of ValidationResult elements or issue a yield return for each problem you detect in validation.
The ValidationResult consists of an error message and a list of strings with the fieldnames. The error messages will be shown at a location near the input field(s).
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if( NumberField < 0 )
{
yield return new ValidationResult(
"Don't input a negative number",
new[] { "NumberField" } );
}
if( NumberField > 100 )
{
yield return new ValidationResult(
"Don't input a number > 100",
new[] { "NumberField" } );
}
yield break;
}
The cleanest way here I believe is going to disable your client side validation and on the server side you will need to:
ModelState["SomeField"].Errors.Clear (in your controller or create an action filter to remove errors before the controller code is executed)
Add ModelState.AddModelError from your controller code when you detect a violation of your detected issues.
Seems even a custom view model here wont solve the problem because the number of those 'pre answered' fields could vary. If they dont then a custom view model may indeed be the easiest way, but using the above technique you can get around your validations issues.
this was someone else's answer in the comments...but it should be a real answer:
$("#SomeValue").removeAttr("data-val-required")
tested on MVC 6 with a field having the [Required] attribute
answer stolen from https://stackoverflow.com/users/73382/rob above
I was having this problem when I creating a Edit View for my Model and I want to update just one field.
My solution for a simplest way is put the two field using :
<%: Html.HiddenFor(model => model.ID) %>
<%: Html.HiddenFor(model => model.Name)%>
<%: Html.HiddenFor(model => model.Content)%>
<%: Html.TextAreaFor(model => model.Comments)%>
Comments is the field that I only update in Edit View, that not have Required Attribute.
ASP.NET MVC 3 Entity
AFAIK you can not remove attribute at runtime, but only change their values (ie: readonly true/false) look here for something similar .
As another way of doing what you want without messing with attributes I will go with a ViewModel for your specific action so you can insert all the logic without breaking the logic needed by other controllers.
If you try to obtain some sort of wizard (a multi steps form) you can instead serialize the already compiled fields and with TempData bring them along your steps. (for help in serialize deserialize you can use MVC futures)
What #Darin said is what I would recommend as well. However I would add to it (and in response to one of the comments) that you can in fact also use this method for primitive types like bit, bool, even structures like Guid by simply making them nullable. Once you do this, the Required attribute functions as expected.
public UpdateViewView
{
[Required]
public Guid? Id { get; set; }
[Required]
public string Name { get; set; }
[Required]
public int? Age { get; set; }
[Required]
public bool? IsApproved { get; set; }
//... some other properties
}
As of MVC 5 this can be easily achieved by adding this in your global.asax.
DataAnnotationsModelValidatorProvider.AddImplicitRequiredAttributeForValueTypes = false;
I was looking for a solution where I can use the same model for an insert and update in web api. In my situation is this always a body content. The [Requiered] attributes must be skipped if it is an update method.
In my solution, you place an attribute [IgnoreRequiredValidations] above the method. This is as follows:
public class WebServiceController : ApiController
{
[HttpPost]
public IHttpActionResult Insert(SameModel model)
{
...
}
[HttpPut]
[IgnoreRequiredValidations]
public IHttpActionResult Update(SameModel model)
{
...
}
...
What else needs to be done?
An own BodyModelValidator must becreated and added at the startup.
This is in the HttpConfiguration and looks like this: config.Services.Replace(typeof(IBodyModelValidator), new IgnoreRequiredOrDefaultBodyModelValidator());
using Owin;
using your_namespace.Web.Http.Validation;
[assembly: OwinStartup(typeof(your_namespace.Startup))]
namespace your_namespace
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
Configuration(app, new HttpConfiguration());
}
public void Configuration(IAppBuilder app, HttpConfiguration config)
{
config.Services.Replace(typeof(IBodyModelValidator), new IgnoreRequiredOrDefaultBodyModelValidator());
}
...
My own BodyModelValidator is derived from the DefaultBodyModelValidator. And i figure out that i had to override the 'ShallowValidate' methode. In this override i filter the requierd model validators.
And now the IgnoreRequiredOrDefaultBodyModelValidator class and the IgnoreRequiredValidations attributte class:
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Web.Http.Controllers;
using System.Web.Http.Metadata;
using System.Web.Http.Validation;
namespace your_namespace.Web.Http.Validation
{
public class IgnoreRequiredOrDefaultBodyModelValidator : DefaultBodyModelValidator
{
private static ConcurrentDictionary<HttpActionBinding, bool> _ignoreRequiredValidationByActionBindingCache;
static IgnoreRequiredOrDefaultBodyModelValidator()
{
_ignoreRequiredValidationByActionBindingCache = new ConcurrentDictionary<HttpActionBinding, bool>();
}
protected override bool ShallowValidate(ModelMetadata metadata, BodyModelValidatorContext validationContext, object container, IEnumerable<ModelValidator> validators)
{
var actionContext = validationContext.ActionContext;
if (RequiredValidationsIsIgnored(actionContext.ActionDescriptor.ActionBinding))
validators = validators.Where(v => !v.IsRequired);
return base.ShallowValidate(metadata, validationContext, container, validators);
}
#region RequiredValidationsIsIgnored
private bool RequiredValidationsIsIgnored(HttpActionBinding actionBinding)
{
bool ignore;
if (!_ignoreRequiredValidationByActionBindingCache.TryGetValue(actionBinding, out ignore))
_ignoreRequiredValidationByActionBindingCache.TryAdd(actionBinding, ignore = RequiredValidationsIsIgnored(actionBinding.ActionDescriptor as ReflectedHttpActionDescriptor));
return ignore;
}
private bool RequiredValidationsIsIgnored(ReflectedHttpActionDescriptor actionDescriptor)
{
if (actionDescriptor == null)
return false;
return actionDescriptor.MethodInfo.GetCustomAttribute<IgnoreRequiredValidationsAttribute>(false) != null;
}
#endregion
}
[AttributeUsage(AttributeTargets.Method, Inherited = true)]
public class IgnoreRequiredValidationsAttribute : Attribute
{
}
}
Sources:
Using string debug = new StackTrace().ToString() to find out who is
handeling the model validation.
https://learn.microsoft.com/en-us/aspnet/web-api/overview/advanced/configuring-aspnet-web-api to know how set my own validator.
https://github.com/ASP-NET-MVC/aspnetwebstack/blob/master/src/System.Web.Http/Validation/DefaultBodyModelValidator.cs to figure out what this validator is doing.
https://github.com/Microsoft/referencesource/blob/master/System.Web/ModelBinding/DataAnnotationsModelValidator.cs to figure out why the IsRequired property is set on true. Here you can also find the original Attribute as a property.
If you don't want to use another ViewModel you can disable client validations on the view and also remove the validations on the server for those properties you want to ignore. Please check this answer for a deeper explanation https://stackoverflow.com/a/15248790/1128216
In my case the same Model was used in many pages for re-usability purposes. So what i did was i have created a custom attribute which checks for exclusions
public class ValidateAttribute : ActionFilterAttribute
{
public string Exclude { get; set; }
public string Base { get; set; }
public override void OnActionExecuting(HttpActionContext actionContext)
{
if (!string.IsNullOrWhiteSpace(this.Exclude))
{
string[] excludes = this.Exclude.Split(',');
foreach (var exclude in excludes)
{
actionContext.ModelState.Remove(Base + "." + exclude);
}
}
if (actionContext.ModelState.IsValid == false)
{
var mediaType = new MediaTypeHeaderValue("application/json");
var error = actionContext.ModelState;
actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.OK, error.Keys, mediaType);
}
}
}
and in your controller
[Validate(Base= "person",Exclude ="Age,Name")]
public async Task<IHttpActionResult> Save(User person)
{
//do something
}
Say the Model is
public class User
{
public int Id { get; set; }
[Required]
public string Name { get; set; }
[Range(18,99)]
public string Age { get; set; }
[MaxLength(250)]
public string Address { get; set; }
}
This one worked for me:
$('#fieldId').rules('remove', 'required');
Yes it is possible to disable Required Attribute. Create your own custom class attribute (sample code called ChangeableRequired) to extent from RequiredAtribute and add a Disabled Property and override the IsValid method to check if it is disbaled. Use reflection to set the disabled poperty, like so:
Custom Attribute:
namespace System.ComponentModel.DataAnnotations
{
public class ChangeableRequired : RequiredAttribute
{
public bool Disabled { get; set; }
public override bool IsValid(object value)
{
if (Disabled)
{
return true;
}
return base.IsValid(value);
}
}
}
Update you property to use your new custom Attribute:
class Forex
{
....
[ChangeableRequired]
public decimal? ExchangeRate {get;set;}
....
}
where you need to disable the property use reflection to set it:
Forex forex = new Forex();
// Get Property Descriptor from instance with the Property name
PropertyDescriptor descriptor = TypeDescriptor.GetProperties(forex.GetType())["ExchangeRate"];
//Search for Attribute
ChangeableRequired attrib = (ChangeableRequired)descriptor.Attributes[typeof(ChangeableRequired)];
// Set Attribute to true to Disable
attrib.Disabled = true;
This feels nice and clean?
NB: The validation above will be disabled while your object instance is alive\active...

ASP.NET MVC Conditional validation

How to use data annotations to do a conditional validation on model?
For example, lets say we have the following model (Person and Senior):
public class Person
{
[Required(ErrorMessage = "*")]
public string Name
{
get;
set;
}
public bool IsSenior
{
get;
set;
}
public Senior Senior
{
get;
set;
}
}
public class Senior
{
[Required(ErrorMessage = "*")]//this should be conditional validation, based on the "IsSenior" value
public string Description
{
get;
set;
}
}
And the following view:
<%= Html.EditorFor(m => m.Name)%>
<%= Html.ValidationMessageFor(m => m.Name)%>
<%= Html.CheckBoxFor(m => m.IsSenior)%>
<%= Html.ValidationMessageFor(m => m.IsSenior)%>
<%= Html.CheckBoxFor(m => m.Senior.Description)%>
<%= Html.ValidationMessageFor(m => m.Senior.Description)%>
I would like to be the "Senior.Description" property conditional required field based on the selection of the "IsSenior" propery (true -> required). How to implement conditional validation in ASP.NET MVC 2 with data annotations?
There's a much better way to add conditional validation rules in MVC3; have your model inherit IValidatableObject and implement the Validate method:
public class Person : IValidatableObject
{
public string Name { get; set; }
public bool IsSenior { get; set; }
public Senior Senior { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (IsSenior && string.IsNullOrEmpty(Senior.Description))
yield return new ValidationResult("Description must be supplied.");
}
}
Read more at Introducing ASP.NET MVC 3 (Preview 1).
I have solved this by handling the "ModelState" dictionary, which is contained by the controller. The ModelState dictionary includes all the members that have to be validated.
Here is the solution:
If you need to implement a conditional validation based on some field (e.g. if A=true, then B is required), while maintaining property level error messaging (this is not true for the custom validators that are on object level) you can achieve this by handling "ModelState", by simply removing unwanted validations from it.
...In some class...
public bool PropertyThatRequiredAnotherFieldToBeFilled
{
get;
set;
}
[Required(ErrorMessage = "*")]
public string DepentedProperty
{
get;
set;
}
...class continues...
...In some controller action ...
if (!PropertyThatRequiredAnotherFieldToBeFilled)
{
this.ModelState.Remove("DepentedProperty");
}
...
With this we achieve conditional validation, while leaving everything else the same.
UPDATE:
This is my final implementation: I have used an interface on the model and the action attribute that validates the model which implements the said interface. Interface prescribes the Validate(ModelStateDictionary modelState) method. The attribute on action just calls the Validate(modelState) on IValidatorSomething.
I did not want to complicate this answer, so I did not mention the final implementation details (which, at the end, matter in production code).
I had the same problem yesterday but I did it in a very clean way which works for both client side and server side validation.
Condition: Based on the value of other property in the model, you want to make another property required. Here is the code
public class RequiredIfAttribute : RequiredAttribute
{
private String PropertyName { get; set; }
private Object DesiredValue { get; set; }
public RequiredIfAttribute(String propertyName, Object desiredvalue)
{
PropertyName = propertyName;
DesiredValue = desiredvalue;
}
protected override ValidationResult IsValid(object value, ValidationContext context)
{
Object instance = context.ObjectInstance;
Type type = instance.GetType();
Object proprtyvalue = type.GetProperty(PropertyName).GetValue(instance, null);
if (proprtyvalue.ToString() == DesiredValue.ToString())
{
ValidationResult result = base.IsValid(value, context);
return result;
}
return ValidationResult.Success;
}
}
Here PropertyName is the property on which you want to make your condition
DesiredValue is the particular value of the PropertyName (property) for which your other property has to be validated for required
Say you have the following
public class User
{
public UserType UserType { get; set; }
[RequiredIf("UserType", UserType.Admin, ErrorMessageResourceName = "PasswordRequired", ErrorMessageResourceType = typeof(ResourceString))]
public string Password
{
get;
set;
}
}
At last but not the least , register adapter for your attribute so that it can do client side validation (I put it in global.asax, Application_Start)
DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(RequiredIfAttribute),typeof(RequiredAttributeAdapter));
I've been using this amazing nuget that does dynamic annotations ExpressiveAnnotations
You could validate any logic you can dream of:
public string Email { get; set; }
public string Phone { get; set; }
[RequiredIf("Email != null")]
[RequiredIf("Phone != null")]
[AssertThat("AgreeToContact == true")]
public bool? AgreeToContact { get; set; }
You can disable validators conditionally by removing errors from ModelState:
ModelState["DependentProperty"].Errors.Clear();
Thanks Merritt :)
I've just updated this to MVC 3 in case anyone finds it useful: Conditional Validation in ASP.NET MVC 3.
There is now a framework that does this conditional validation (among other handy data annotation validations) out of the box:
http://foolproof.codeplex.com/
Specifically, take a look at the [RequiredIfTrue("IsSenior")] validator. You put that directly on the property you want to validate, so you get the desired behavior of the validation error being associated to the "Senior" property.
It is available as a NuGet package.
You need to validate at Person level, not on Senior level, or Senior must have a reference to its parent Person. It seems to me that you need a self validation mechanism that defines the validation on the Person and not on one of its properties. I'm not sure, but I don't think DataAnnotations supports this out of the box. What you can do create your own Attribute that derives from ValidationAttribute that can be decorated on class level and next create a custom validator that also allows those class-level validators to run.
I know Validation Application Block supports self-validation out-of the box, but VAB has a pretty steep learning curve. Nevertheless, here's an example using VAB:
[HasSelfValidation]
public class Person
{
public string Name { get; set; }
public bool IsSenior { get; set; }
public Senior Senior { get; set; }
[SelfValidation]
public void ValidateRange(ValidationResults results)
{
if (this.IsSenior && this.Senior != null &&
string.IsNullOrEmpty(this.Senior.Description))
{
results.AddResult(new ValidationResult(
"A senior description is required",
this, "", "", null));
}
}
}
I had the same problem, needed a modification of [Required] attribute - make field required in dependence of http request.The solution was similar to Dan Hunex answer, but his solution didn't work correctly (see comments). I don't use unobtrusive validation, just MicrosoftMvcValidation.js out of the box.
Here it is. Implement your custom attribute:
public class RequiredIfAttribute : RequiredAttribute
{
public RequiredIfAttribute(/*You can put here pararmeters if You need, as seen in other answers of this topic*/)
{
}
protected override ValidationResult IsValid(object value, ValidationContext context)
{
//You can put your logic here
return ValidationResult.Success;//I don't need its server-side so it always valid on server but you can do what you need
}
}
Then you need to implement your custom provider to use it as an adapter in your global.asax
public class RequreIfValidator : DataAnnotationsModelValidator <RequiredIfAttribute>
{
ControllerContext ccontext;
public RequreIfValidator(ModelMetadata metadata, ControllerContext context, RequiredIfAttribute attribute)
: base(metadata, context, attribute)
{
ccontext = context;// I need only http request
}
//override it for custom client-side validation
public override IEnumerable<ModelClientValidationRule> GetClientValidationRules()
{
//here you can customize it as you want
ModelClientValidationRule rule = new ModelClientValidationRule()
{
ErrorMessage = ErrorMessage,
//and here is what i need on client side - if you want to make field required on client side just make ValidationType "required"
ValidationType =(ccontext.HttpContext.Request["extOperation"] == "2") ? "required" : "none";
};
return new ModelClientValidationRule[] { rule };
}
}
And modify your global.asax with a line
DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(RequiredIfAttribute), typeof(RequreIfValidator));
and here it is
[RequiredIf]
public string NomenclatureId { get; set; }
The main advantage for me is that I don't have to code custom client validator as in case of unobtrusive validation. it works just as [Required], but only in cases that you want.
Check out Simon Ince's Conditional Validation in MVC.
I am working through his example project right now.
Typical usage for conditional removal of error from Model State:
Make conditional first part of controller action
Perform logic to remove error from ModelState
Do the rest of the existing logic (typically Model State validation, then everything else)
Example:
public ActionResult MyAction(MyViewModel vm)
{
// perform conditional test
// if true, then remove from ModelState (e.g. ModelState.Remove("MyKey")
// Do typical model state validation, inside following if:
// if (!ModelState.IsValid)
// Do rest of logic (e.g. fetching, saving
In your example, keep everything as is and add the logic suggested to your Controller's Action. I'm assuming your ViewModel passed to the controller action has the Person and Senior Person objects with data populated in them from the UI.
I'm using MVC 5 but you could try something like this:
public DateTime JobStart { get; set; }
[AssertThat("StartDate >= JobStart", ErrorMessage = "Time Manager may not begin before job start date")]
[DisplayName("Start Date")]
[Required]
public DateTime? StartDate { get; set; }
In your case you would say something like "IsSenior == true".
Then you just need to check the validation on your post action.

Categories