How can I use annotations to require a boolean model property to be either false or empty?
Example:
// model
public class MyModel
{
public string StringProperty { get; set; }
public bool BoolProperty { get; set; }
}
OK:
POST /someEndpoint
{
"stringProperty": "foobar"
}
POST /someEndpoint
{
"stringProperty": "foobar",
"boolProperty": false
}
400:
POST /someEndpoint
{
"stringProperty": "foobar",
"boolProperty": true
}
I want to validate the model using ModelState.IsValid. So far, I've tried using [Range(typeof(bool), "false", "false")] but ModelState.IsValid returns true even if boolProperty is set to true. Any help?
I created a custom validation attribute and it works for me
public class AllowedOnlyFalse : ValidationAttribute
{
public override bool IsValid(object value)
{
if (value == null) return true;
bool val = Convert.ToBoolean(value);
return !val;
}
}
// model
public class MyModel
{
public string StringProperty { get; set; }
[AllowedOnlyFalse]
public bool BoolProperty { get; set; }
}
Related
I could use a custom model binder to bind a 0 and 1 to a false and true in this action:
[IntToBoolBinder]
public virtual ActionResult foo(bool someValue) {
}
But now suppose the argument is a strongly-typed model:
public class MyModel {
public int SomeInt { get; set; }
public string SomeString { get; set; }
public bool SomeBool { get; set; } // <-- how to custom bind this?
}
public virtual ActionResult foo(MyModel myModel) {
}
The request will contain an int and my model expects a bool. I could write a custom model binder for the entire MyModel model, but I want something more generic.
Is is possible to custom bind a particular property of a strongly-typed model?
If you want to make a custom binding to it can look like this:
public class BoolModelBinder : DefaultModelBinder
{
protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext,
PropertyDescriptor propertyDescriptor)
{
if (propertyDescriptor.PropertyType == typeof(bool))
{
Stream req = controllerContext.RequestContext.HttpContext.Request.InputStream;
req.Seek(0, SeekOrigin.Begin);
string json = new StreamReader(req).ReadToEnd();
var data = JsonConvert.DeserializeObject<Dictionary<string, string>>(json);
string value = data[propertyDescriptor.Name];
bool #bool = Convert.ToBoolean(int.Parse(value));
propertyDescriptor.SetValue(bindingContext.Model, #bool);
return;
}
else
{
base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
}
}
}
But MVC and WebAPI int conversion to bool (field names must be the same) and do not need anything extra to write, so I do not know if you need the code above.
Try this demo code:
public class JsonDemo
{
public bool Bool { get; set; }
}
public class DemoController : Controller
{
[HttpPost]
public ActionResult Demo(JsonDemo demo)
{
var demoBool = demo.Bool;
return Content(demoBool.ToString());
}
}
And send JSON object :
{
"Bool" : 0
}
I am using MVC4 Application. Below is my model
public class ViewModel
{
public string AssignedUserName { get; set; }
public IList<SelectListItem> PossibleAssignees { get; set; }
[Required]
public string Comment { get; set; }
}
View:
<table style="width: 100%; ">
<tr>
<td>Assigned To:</td>
<td>#Html.DropDownListFor(x => x.AssignedUserName, Model.PossibleAssignees)</td>
<td>#Html.ValidationMessageFor(m => m.AssignedUserName, "Assigned To Required")</td>
<tr></table>
PossibleAssignes is a dropdown, the values should be -> EmptyString,"Mr.Barr".
So if the user selected EmptyString means i need to throw validation like " it is Required" field.
i tried with adding [Required] field validator. it is a collection which having some empty string values as drop down value,
so i am not sure how to use the [Required] field for collection which having empty strings.
i don't want to allow empty strings for dropdown.
how can i validate this ?
don't assign an emtpty string to the values in your list, use it as default value instead and the [Required] will work just fine.
In your view use it as:
#Html.DropDownListFor(x => x.AssignedUserName, Model.PossibleAssignees, String.Empty)
and in your viewmodel:
public class ViewModel
{
[Required]
public string AssignedUserName { get; set; }
public IList<SelectListItem> PossibleAssignees { get; set; }
[Required]
public string Comment { get; set; }
}
EDIT (i'll leave my first answer because it might work for someone else)
You can make a custom validation:
public class ViewModel
{
[Required]
[ValidateAssignedUserName()]
public string AssignedUserName { get; set; }
public IList<SelectListItem> PossibleAssignees { get; set; }
[Required]
public string Comment { get; set; }
}
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true, Inherited = true)]
public sealed class ValidateAssignedUserName : ValidationAttribute
{
private const string _defaultErrorMessage = "Select a user.";
public ValidateAssignedUserName()
: base(_defaultErrorMessage)
{ }
public override bool IsValid(object value)
{
string user = value as string;
if (user != null && user.Length > 0)
{
return true;
}
return false;
}
}
Alternatively, you could also use a custom model binder .e.g
public class ViewModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var model = base.BindModel(controllerContext, bindingContext) as ViewModel;
if (model.AssignedUserName == "-1")
bindingContext.ModelState.AddModelError("AssignedUserName", "No assignee selected");
return model;
}
}
Global.asax.cs
protected void Application_Start()
{
// .....
ModelBinders.Binders.Add(typeof(ViewModel), new ViewModelBinder());
// ....
}
Change your view slightly :
<td>#Html.ValidationMessageFor(m => m.AssignedUserName)</td>
Remove the text from the 2nd param for the above line.
Doing the above will result in ModelState.IsValid being set to false. When you render the view, the text "No assignee selected" will appear where the ValidationMessageFor() call is made.
So there are options to achieve what you need.
The viewmodel has many string properties like Sample as below. My requirement is to show different validation messages depending on a bool flag in my viewmodel. That flag is IsProposer property as mentioned below:
[SampleAttribute(true, "bla prop", "foo add driver")]
public string Sample { get; set; }
public bool IsProposer { get; set; }
I thought to create a validation attribute so that I can just place it on all my string properties (required validation). And then depending on the value of that boolean flag, I will pass the msg accordingly. My custom validation attribute is as follows:
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true, Inherited = false)]
public class SampleAttribute : RequiredAttribute
{
protected string ProposerErrorMessage { get; set; }
protected string AdditionalDriverErrorMessage { get; set; }
protected bool IsProposer { get; set; }
public SampleAttribute(bool isProposer, string propmsg, string adddrivermsg)
{
ProposerErrorMessage = propmsg;
IsProposer = isProposer;
AdditionalDriverErrorMessage = adddrivermsg;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (IsValid(value))
{
return ValidationResult.Success;
}
else
{
return new ValidationResult(IsProposer ? ProposerErrorMessage : AdditionalDriverErrorMessage);
}
}
}
Now the issue is, as you can see I am just passing true as first parameter for the attribute. Here, I need to pass the Isproposer property's value from the viewmodel instance so that I can then act accordingly. How can I access it?
I solved my problem by creating a attribute like this:
/// <summary>
/// This validation attribute is an extension to RequiredAttribute that can be used to choose either of the two
/// validation messages depending on a property in the context of same model.
/// </summary>
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true, Inherited = false)]
public class RequiredExtensionAttribute : RequiredAttribute
{
private string _errorMessageIfTruthy;
private string _errorMessageIfFalsy;
private string _dependentProperty;
public RequiredExtensionAttribute(string dependentproperty, string errorMessageIfTruthy, string errorMessageIfFalsy)
{
_errorMessageIfTruthy = errorMessageIfTruthy;
_dependentProperty = dependentproperty;
_errorMessageIfFalsy = errorMessageIfFalsy;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var propertyTestedInfo = validationContext.ObjectType.GetProperty(this._dependentProperty);
if (propertyTestedInfo == null)
{
return new ValidationResult(string.Format("unknown property {0}", this._dependentProperty));
}
var propertyTestedValue = propertyTestedInfo.GetValue(validationContext.ObjectInstance, null);
if (IsValid(value))
{
return ValidationResult.Success;
}
else
{
return new ValidationResult((bool)propertyTestedValue ? _errorMessageIfTruthy : _errorMessageIfFalsy);
}
}
}
This can now be used in models like:
[RequiredExtensionAttribute("IsProposerViewModel", "Please select your employment status.", "Please select this driver's employment status")]
public string EmploymentStatus { get; set; }
public bool IsProposerViewModel { get; set; }
-where the first parameter for attribute is the IsProposerViewModel, the dependent value.
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!
I have made custom attribute in my asp.net mvc2 project:
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
public class IsUsernameValidAttribute : ValidationAttribute
{
public override bool IsValid(object value)
{
if (value == null)
{
return true;
}
var username = value.ToString();
return UserBusiness.IsUsernameValid(username)
// && value of OtherProperty == true;
}
}
for the model:
public class MyClass
{
[IsUsernameValid]
public string UserName { get; set; }
public bool OtherProperty { get; set; }
}
I can get value of UserName, but can I get value of OtherProperty inside custom attribute and use it in return clause and how. Thanks in advance.
The only way to do this is with a class level attribute. This is often used for validating the Password and PasswordConfirmation fields during registration.
Grab some code from there as a starting point.
[AttributeUsage(AttributeTargets.Class)]
public class MatchAttribute : ValidationAttribute
{
public override Boolean IsValid(Object value)
{
Type objectType = value.GetType();
PropertyInfo[] properties = objectType.GetProperties();
...
}
}