I have a model that implement IValidatlableObject, and so custom error checking through Validate method.
When I create an object all is fine, but when I try to edit that object, I wan't to do that custom validation.
How can I know from wich action I'm calling the Validate method in order to no do the validation?
UPDATED:
This is mi model:
public class Ingredient : IValidatableObject
{
public int Id { get; set; }
[Required(ErrorMessage = "Required!!")]
public string Name { get; set; }
public virtual List<Product> Products { get; set; }
public Ingredient()
{
Products = new List<Product>();
}
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
using (var uow = new UnitOfWork())
{
var ingredient = uow.IngredientRepository.Get(i => i.Name ==Name).FirstOrDefault();
if (ingredient != null)
yield return new ValidationResult("Duplicate!!!.", new[] { "Name" });
}
}
}
}
So When I create an Ingredient I want to validate ALL (Attributes + IValidatable)
but when I edit an Ingrendient I only want to validate attributes (so I mean skip IValidatable)
Any method to know, inside the IValidatable method, from where I'm calling Validate ?
Thanks!!!
Check primary key of model - whether it is not null :)
The more "MVCish" correct way here is you actually have two classes, one for the Create method one for the edit. You can call off to a base class for any shared validation, anything then not shared wouldn't be checked here.
If you don't want to validate an object, don't call Model.IsValid (or Validate(), if you're doing it explicitly. Can't answer more than that without knowing more details about your problem.
Related
I'm trying to find the "best" way to check the coherence between data in the request URL and data in the body of the request.
For example, let's say we have this PUT endpoint:
https://host:port/foo/bar/{carId}
where I can pass a Car object as body:
class Car {
string CarId { get; set; }
string Brand { get; set; }
int MaxSpeed { get; set; }
...
}
Now, in my controller I have something like this:
[HttpPut("foo/bar/{carId}")]
public async Task updateCar([FromRoute] string carId, [FromBody] Car car) {
...
}
And I want to be sure that carId in the route matches the CarId property in the body.
What is the "best" way to achieve this? Of course I could simply check with an if in the body controller, but since this is a validation task (or at least I think so), I'd like to have this logic in my validation layer.
Personal ideas so far
Ok, the question is over, here I'll just put some ideas I've tried or I want to try.
I have a custom action filter to check the validation, and I'm trying to play with it in order to see if I can do something there, or add another custom action filter only where I want to check the coherence, but this doesn't look promising.
At the moment I've seen that in an action filter I have access to the controller method parameters through context.ActionArguments property, but I don't know how to check if these arguments were "bound arguments" (namely had some [FromXXX] attribute). If I could do this maybe I could check if there are arguments with the same name (or with a property with the same name), and then compare their values. But this seems very cumbersome and inconsistent.
I've read about custom binders, but I'm still scratching the surface (I hope to learn something more in the next few hours): can they be a possible solution?
You should do it like this:
[AttributeUsage(AttributeTargets.Property)]
public class VerifyWithRouteParams : Attribute
{
public string ParamName
{
get
{
return paramName;
}
}
private readonly string paramName;
public VerifyWithRouteParams(string paramName)
{
this.paramName = paramName;
}
}
Consider you have a car model that is being recieved through body:
public class Car
{
[VerifyWithRouteParams("carId")]
public string CarId { set; get; }
public string AnotherParam {set; get;}
}
You should have a default filter:
public class RouteBodyVerificationActionFilter : IActionFilter
{
public void OnActionExecuted(ActionExecutedContext context)
{
}
public void OnActionExecuting(ActionExecutingContext context)
{
// do something before the action executes
if (context.ActionArguments != null && context.ActionArguments.Count > 0)
{
foreach (var arg in context.ActionArguments)
{
if (arg.Value == null) continue;
bool isThereAnyObjectInArgumentsWithVerificationAttribute = arg.Value
.GetType()
.GetProperties()
.Any(
x => x.GetCustomAttributes(typeof(VerifyWithRouteParams), false).Any()
);
if (isThereAnyObjectInArgumentsWithVerificationAttribute)
{
foreach (var prop in arg.Value.GetType().GetProperties())
{
var verificationAttr = prop.GetCustomAttributes(typeof(VerifyWithRouteParams), false).FirstOrDefault() as VerifyWithRouteParams;
if (null == verificationAttr) continue;
string routeArgumentName = verificationAttr.ParamName;
context.ActionArguments.TryGetValue(routeArgumentName, out var routeArgumentValue);
if (null == routeArgumentValue)
{
context.ModelState.AddModelError("invalid argument value", routeArgumentName);
}
if (routeArgumentValue?.Equals(prop.GetValue(arg.Value)) != true)
{
context.ModelState.AddModelError("invalid argument value", routeArgumentName);
}
}
}
}
}
}
}
Then you should add a default filter in startup:
services.AddControllers(cfg =>
{
cfg.Filters.Add<RouteBodyVerificationActionFilter>();
});
Then you can check it by model validation errors in controller:
if (!ModelState.IsValid)
{
return Content("invalid model");
}
I make a Booking form for restaurant, which asks for the name of the restaurant, the date of the meal and the number of person.
I have a booking class, which has an ID, an ID of the restaurant, a date and a number of people :
public class Booking
{
public int Id { get; set; }
public int IDRestaurant{ get; set; }
[CustomPlaceValidator]
public int Nbpeople { get; set; }
[CustomDateValidator]
public DateTime Date { get; set; }
}
As well as a Resto class, which has an ID, a name, phone number and a number of table :
public class Resto
{
public int Id { get; set; }
[Required(ErrorMessage = "Le nom du restaurant doit être saisi")]
public string Nom { get; set; }
[Display(Name = "Téléphone")]
[RegularExpression(#"^0[0-9]{9}$", ErrorMessage = "Le numéro de téléphone est incorrect")]
public string Telephone { get; set; }
[Range(0, 9999)]
public int Size { get; set; }
}
I would like to make a validation to check with each new reservation, that the restaurant is not full.
To do this, when validating the "Number of persons" field of the Booking, I need the value of the "restaurant name" field and the value of the "date" field, and then retrieve all the bookings on this Restaurant at that date, and check whether the sum of the number of persons is much lower than the capacity of the restaurant.
public class CustomPlaceValidator : ValidationAttribute
{
private IDal dal = new Dal();
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
int nb = 0;
if (dal.GetAllBooking() != null)
{
foreach (var booking in dal.GetAllBooking())
nb += booking.Nbpeople;
if (nb ..... ) return ValidationResult.Success;
return new ValidationResult("The restaurant is full for this date.");
}
return ValidationResult.Success;
}
}
(It's a draft, the tests are not finished obviously)
How can I have the value of the other proprieties for my validation ?
This is not appropriate for a validation attribute. First, a validation attribute should be independent, or at least self-contained. Since the logic here depends on two different properties (the number of people and the date of the booking) a validation attribute would require too much knowledge of the domain in order to perform the necessary validation. In other words, it's not reusable, and if it's not reusable, then there's no point in using an attribute.
Second, a validation attribute should not do something like make a database query. The controller alone should be responsible for working with your DAL. When you start littering database access across your application, you're going to start running into all sorts of issues in very short order. If you use a DI container to inject your DAL where it needs to go, it's less problematic to use it outside of the controller, but importantly, attributes really don't play well with dependency injection. You can make it work with some DI containers, but it's never easy and you're probably going to regret it later. So, again, this really shouldn't be something a validation attribute handles.
The best approach in my opinion is to simply create a private/protected method on your controller to handle this validation. Something like:
public void ValidateCapacity(Booking booking)
{
var restaurant = dal.GetRestaurant(booking.IDRestaurant);
var existingBookings = dal.GetBookings(booking.IDRestaurant, booking.Date);
var available = restaurant.Size - existingBookings.Sum(b => b.Nbpeople);
if (booking.Nbpeople > available)
{
ModelState.AddModelError("Nbpeople", "There is not enough capacity at the restaurant for this many people on the date you've selected");
}
}
Then, in your post action for the booking, simply call this before checking ModelState.IsValid.
I'm looking at this question: Group validation messages for multiple properties together into one message asp.net mvc
My guess is something like:
public class Booking
{
public int Id { get; set; }
public int IDRestaurant{ get; set; }
[CustomPlace("IDRestaurant", "Date", ErrorMessage = "the restaurant is full")]
public int Nbpeople { get; set; }
[CustomDateValidator]
public DateTime Date { get; set; }
}
and the custom validation:
public class CustomPlaceAttribute : ValidationAttribute
{
private readonly string[] _others
public CustomPlaceAttribute(params string[] others)
{
_others= others;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
// TODO: validate the length of _others to ensure you have all required inputs
var property = validationContext.ObjectType.GetProperty(_others[0]);
if (property == null)
{
return new ValidationResult(
string.Format("Unknown property: {0}", _others[0])
);
}
// This is to get one of the other value information.
var otherValue = property.GetValue(validationContext.ObjectInstance, null);
// TODO: get the other value again for the date -- and then apply your business logic of determining the capacity
}
}
However, it feels a bit messy to do a database call for the validationAttribute though
What you are asking for is cross-property validation. If you are not strongly opposed to implementing an interface on your data objects you should take a look at the following:
https://msdn.microsoft.com/en-us/library/system.componentmodel.dataannotations.ivalidatableobject.aspx
A simple example implementation for a small rectangle class where we want its area not to exceed 37 (whatever that unit is).
public class SmallRectangle : IValidatableObject
{
public uint Width { get; set; }
public uint Height { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
var area = Width * Height;
if (area > 37)
{
yield return new ValidationResult($"The rectangle is too large.");
}
}
}
Alternatives
The the second parameter of the IsValid function in your ValidationAttribute provides you with the ValidationContext which has the property ObjectInstance which you can cast to your object type and access its other members. That, however, will make your validation attribute specific to your class. I would generally advise against that.
You could also opt to use a different validation approach altogether such as using a validation library such as FluentValidations, see:
https://github.com/JeremySkinner/FluentValidation
A different perspective
Last but not least I would like to note that usually validation should be used to validate the integrity of the data. A booking request which requests more seats than available is not invalid. It can not be granted, but it is a valid request which will, unfortunately, be answered with a negative result. To give that negative result is, in my opinion not the responsibility of the validation, but the business logic.
I have a requirement where I need to validate the dropdown. On a Button1 click the Model should validate that the dropdown is not selected and on Button2 click the Model should validate that the dropdown is selected to a valid value and also a corresponding field is also marked as Required if the Value is a certain value from the dropdown.
My model is as below:
public class ApprovalInformation
{
[DisplayName("Approval Decision")]
public int? ApproveStatusID { get; set; }
public string ApproveStatus { get; set; }
public SelectList ApproveStatuses { get; set; }
[DisplayName("Additional Information")]
[RequiredIf("ApproveStatus", StringConstants.NotApproved, ErrorMessage = "You must specify the comments if not approved")]
public string AdditionalInformation { get; set; }
}
Currently I have 2 action methods and I call them based on the value of the button name. Here is the controller code:
public ActionResult SaveApproval(ApprovalInformation approveInfo,string submit)
{
if (submit == "Save For Later")
{
Business business = new Business();
int selectedStatusID = approveInfo.ApproveStatusID??0;
if ( selectedStatusID!= 0)
{
ModelState.AddModelError("ApproveStatusID", "You may not set the Approval Decision before saving a service request for later. Please reset the Approval Decision to blank");
}
if (ModelState.IsValid)
{
return RedirectToActionPermanent("EditApproval");
}
return View("EditApproval", approveInfo);
}
else
{
TempData["approverInfo"] = approveInfo;
return RedirectToActionPermanent("FinishApproval");
}
}
I am having a problem plugging the validation depending on the buttons clicked. Since on different button click the same property should be validated in 2 different manner. How can i suppress a Validation or Induce validations at run time on the same model depending on different actions. Any idea around this will be appreciated.
I believe this is a good situation to implement the IValidatableObject Interface on your ApprovalInformation view model. You can pass through the intent to submit or save for later in the ValidationContext dictionary, in order to get the reuse you need.
You can also place the conditional logic that "AdditionalInformation must be set if ApprovalStatus is not set" here, as well.
public class ApprovalInformation : IValidatableObject
{
... // Properties
IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (validationContext.ContainsKey("submit"))
{
if (ApproveStatusID != 0)
{
yield return new ValidationResult("You may not set the Approval Decision before saving a service request for later. Please reset the Approval Decision to blank",
new {"ApproveStatusID"});
}
}
}
}
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...
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.