Validation does not work if nothing is sent? - c#

I annotated my model as this:
public class Instance
{
[Required]
public string Name { get; set; }
public string Description { get; set; }
[Required]
public string DBServer { get; set; }
[Required]
public string Database { get; set; }
}
In the post method I get a null for the value if nothing was sent but Model.State is true. How can the state be true if nothing was sent? The next problem is that the CreateErrorResponse method throws an exception when I call it (probably because the value was null).
public HttpResponseMessage Post([FromBody]Instance value)
{
if (value != null && ModelState.IsValid)
{
...
}
else
return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState);
}
Edit:
As it seems I didn't explain it right. I try now with some screenshots.
Case 1
I post a correct value using Fiddle and everything works as expected. ModelState.IsValid is true.
Case 2
I post a value with a missing required field (DBServer) and then again everything works as expected. ModelState.IsValid is false.
Case 3
My question. I send a post request with NO information and ModelState.IsValid is true. This seems very strange and I would like to know the reason. Thank you all for your answers.

Try abstracting the ModelState check into a filter. You won't have to check for ModelState every time this way and if there's a problem
This code below comes from a great article on ModelState in WebAPI:
http://www.asp.net/web-api/overview/formats-and-model-binding/model-validation-in-aspnet-web-api
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http.Controllers;
using System.Web.Http.Filters;
using System.Web.Http.ModelBinding;
namespace MyApi.Filters
{
public class ValidateModelAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
if (actionContext.ModelState.IsValid == false)
{
actionContext.Response = actionContext.Request.CreateErrorResponse(
HttpStatusCode.BadRequest, actionContext.ModelState);
}
}
}
}
However, what you need to know is that ModelState only checks internal values, so you need to supply a check to see if the item is null before calling on ModelState.
Check this answer for more details: ModelState.IsValid even when it should not be?

Related

Asp.Net WebApi model binding nullable datetime

Hello,
I have api GET-method /rating (ASP.Net WebApi 2.1), which accepts objects of type ChartPageRequest:
// comments're removed for readability
public sealed class ChartPageRequest
{
public DateTime? From { get; set; }
public DateTime? To { get; set; }
public string Cursor { get; set; }
[Range(-100, 100)]
public int Take { get; set; } = 10;
}
/rating method has following signature:
[HttpGet]
[Route("rating")]
[ResponseType(typeof(ChartPage))]
[ValidateModelState]
public async Task<IHttpActionResult> GetTranslationRatingChartAsync([ModelBinder] ChartPageRequest model)
{
// body here
}
And ValidateModelState attribute is just a custom attribute which returns custom response when ModelState isn't valid. It doesn't check anything by itself except HttpActionContext.ModelState.IsValid property.
This api method works fine except one case - when client explicitly passes null value to DateTime? properties of ChartPageRequest, e.g.:
/rating?from=2016-07-08 12:01:55.604&to=null
In this case ValidateModelState attribute registers invalid ModelState with following message: The value 'null' is not valid for To.
I've found this kind of problem quite popular, but haven't found any good workarounds without creating custom model binder. So here's the questions:
Is there another approach without custom model binder?
In case there isn't, how can I take only "accepting null" job and leave DateTime parsing to default binder in my custom binder?
Do I need to accept those nulls at all? All clients are developing by my colleagues so I can force them to not send it. Is it good practice after all?
Thanks!
I didn't validate this method, but I guess you can try parse it yourself, though I think it's not a convenient way, first use a string to get the To:
public string To { get; set; }
And give another property DateTimeTo and parse To yourself:
public DateTime? DateTimeTo { get; set; }
public void ParseTo()
{
if(To.ToLower() == "null")
DateTimeTo = null;
else
DateTimeTo = Convert.ToDateTime(To);
}
And DateTimeTo is the parsed result property.
I recently ran into some Asp.Net WebApi parsing body issue, the parsing doesn't work very well in some condition.

Data sent through post method results in a null object

this is my first time ever asking a question on stackoverflow. I have searched through this site many times to look for answers, but this is my first time asking my own! Hopefully I will give enough information so you guys can understand my question.
So basically I have created a new table in our database on sqlserver. I have created a new entity in our entity framework to map to this table as well. The problem is my post method in my odata controller for this database table. Here is the method:
[HttpPost]
[ODataRoute("TerminalPersonnelEmails/LoadEmail()")]
[ResponseType(typeof(TerminalPersonnelEmail))]
public async Task<IHttpActionResult> PostAsync(TerminalPersonnelEmail email)
{
if (email == null)
{
throw new ArgumentException("The data entry given is invalid");
}
portalDatabaseContext.TerminalPersonnelEmails.Add(email);
await portalDatabaseContext.SaveChangesAsync();
//return Created(email);
return Ok("test"); //will be replaced once post method is fixed
}
Here is what I am sending through postman:
{"TerminalEmail" :{
"Id" : 1,
"Name" : "NewEmail",
"Email" : "email#email.com",
"TerminalNumber" : 23084093284 } }
(Sorry for weird indentations/placement of brackets. When i pasted in the code, 1 or 2 indentations were off and my postman code was messed up so I fixed it as best as I could to get it into the code block box).
I have tried several ways to send this data. I have tried just sending the data without clumping it into "TerminalEmail", not sending Id because it is autonum in the database, and I have messed with the content type (json, text, etc.). None of this has worked.
I have made sure that the data matches up with the object so that it should be passing in a valid object to the c# method, but it still is null. I have tired out all of my options and need your help. Thanks!
edit: Here is declaration of terminalpersonnelemail class in the entityframework as requested:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Marketing.EntityFramework.Portal
{
[Table("TerminalPersonnelEmails", Schema = "Portal")]
public class TerminalPersonnelEmail
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[Required, MaxLength(50)]
public string Name { get; set; }
[Required, MaxLength(50)]
public string Email { get; set; }
[Required]
public int TerminalNumber { get; set; }
}
}
Ok, so I found out how to fix it, but I couldn't tell you why this works. I took out the odata route, and in my odata config took out everything except the entityset that I had created. Here is the working code:
[HttpPost]
[ResponseType(typeof(TerminalPersonnelEmail))]
public async Task<IHttpActionResult> Post(TerminalPersonnelEmail email)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
if (email == null)
{
throw new ArgumentException("The data entry given is invalid");
}
portalDatabaseContext.TerminalPersonnelEmails.Add(email);
await portalDatabaseContext.SaveChangesAsync();
return Created(email);

AllowHtml not working

I'm building a Content Management System to allow people other than me to update stuff on the site.
I have a front-facing HTML form that sends data, via AJAX, to a controller:
// CONTROLLER
[ValidateInput(false)]
public void CarAJAX()
{
CarAdmin CA = new CarAdmin();
CA.UpdateCar(System.Web.HttpContext.Current.Request);
}
This data will have HTML, so I keep getting an error in my Model:
// MODEL
using System;
using System.Web;
using System.Web.Mvc;
namespace Site.Models
{
public class CarAdmin
{
public String id { get; set; }
[AllowHtml]
public String HTML_Stuff { get; set; }
public CarAdmin(){}
public void UpdateCar(HttpRequest Request)
{
HTML_Stuff = Request.Form["HTML_Stuff"]; // <-- ERROR HAPPENS HERE!!!!!!
// sanitation and validation
String Select = String.Format("UPDATE Car Set HTML_Stuff = {0} WHERE id = {1}", HTML_Stuff, id);
// Execute DB Command
}
}
}
As shown in the code, I'm getting an error when I try to set a member equal to a request variable that has HTML.
Edit: The error is 'A potentially dangerous Request.Form value was detected'
Here's what I've tried:
Change the validation mode in web.config, but I don't want to change the validation for my entire site, when only one variable will have HTML.
[AllowHtml] in the Model, however I'm still getting the same error - as if [AllowHtml] did nothing at all.
[ValidateInput(false)] in the Controller, similar to AllowHtml, it seems to have no affect whatsoever.
Am I missing something here?
I had the same problem. "requestValidationMode="2.0"" was set in web.config, [AllowHtml] was also set on proper property and I still got the error "A potentially dangerous Request.Form value detected...".
But I observed that the controller method actually was called (I was able to debug the method) so this had to meant that validation is in fact turned off. In Call Stack I noticed repeatedly occurring of classes around cache like "System.Web.Caching.OutputCacheModule" and this led me to an idea that this has something to do with cache I had turned off on the whole controller like this "[OutputCache(NoStore = true, Duration = 0)]".
Based on this I tried to also set Location of the cache to OutputCacheLocation.None and this did the trick. So I ended up with [OutputCache(NoStore = true, Duration = 0, Location = OutputCacheLocation.None)] working and finally not validating and not failing my requests.
Try with this:
// CONTROLLER
[HttpPost]
public ActionResult CarAJAX(CarAdmin model)
{
model.UpdateCar();
}
// MODEL
using System;
using System.Web;
using System.Web.Mvc;
namespace Site.Models
{
public class CarAdmin
{
private string html;
public String id { get; set; }
[AllowHtml]
public String HTML_Stuff {
get
{
return html;
}
set
{
// sanitation and validation on "value"
html = value;
}
}
public CarAdmin(){}
public void UpdateCar()
{
String Select = String.Format("UPDATE Car Set HTML_Stuff = {0} WHERE id = {1}", HTML_Stuff, id);
// Execute DB Command
}
}
}
I also noticed that you are validating inside a method. It would probably be better, if you do that when setting the property.
EDIT:
I researched quite a bit on the topic. You actually need to bind model to the controller using AJAX. Please look at this example. I'm not sure of extents of your code, but I think you also need ActionResult to return within controller. There are nice examples of what to return from ActionResult.
just put [ValidateInput(false)] on controller
You should do it as-
Create a separate class with entities those are required-
public class EntityDto {
public String id { get; set; }
[AllowHtml]
public String HTML_Stuff { get; set; }
}
And then use it in your controller method-
[ValidateInput(false)]
public void UpdateCar(EntityDto model)
{
var html_stuff = model.HTML_Stuff;
// sanitation and validation
String Select = String.Format("UPDATE Car Set HTML_Stuff = {0} WHERE id = {1}", html_stuff , id);
// Execute DB Command
}
Let me know if it helps.

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