IValidatableObject Validate method firing when DataAnnotations fails - asp.net

I've a ViewModel which has some DataAnnotations validations and then for more complex validations implements IValidatableObject and uses Validate method.
The behavior I was expecting was this one: first all the DataAnnotations and then, only if there were no errors, the Validate method. How ever I find out that this isn't always true. My ViewModel (a demo one) has three fileds one string, one decimal and one decimal?. All the three properties have only Required attribute. For the string and the decimal? the behavior is the expected one, but for the decimal, when empty, Required validation fails (so far so good) and then executes the Validate method. If I inspect the property its value is zero.
What is going on here? What am I missing?
Note: I know that Required attribute is suppose to check if the value is null. So I'd expect to be told not to use Required attribute in not-nullable types (because it wont ever trigger), or, that somehow the attribute understand the POST values and note that the field wasn't filled. In the first case the attribute shouldn't trigger and the Validate method should fire. In the second case the attribute should trigger and the Validate method shouldn't fire. But my result are: the attributes triggers and the Validate method fires.
Here is the code (nothing too special):
Controller:
public ActionResult Index()
{
return View(HomeModel.LoadHome());
}
[HttpPost]
public ActionResult Index(HomeViewModel viewModel)
{
try
{
if (ModelState.IsValid)
{
HomeModel.ProcessHome(viewModel);
return RedirectToAction("Index", "Result");
}
}
catch (ApplicationException ex)
{
ModelState.AddModelError(string.Empty, ex.Message);
}
catch (Exception ex)
{
ModelState.AddModelError(string.Empty, "Internal error.");
}
return View(viewModel);
}
Model:
public static HomeViewModel LoadHome()
{
HomeViewModel viewModel = new HomeViewModel();
viewModel.String = string.Empty;
return viewModel;
}
public static void ProcessHome(HomeViewModel viewModel)
{
// Not relevant code
}
ViewModel:
public class HomeViewModel : IValidatableObject
{
[Required(ErrorMessage = "Required {0}")]
[Display(Name = "string")]
public string String { get; set; }
[Required(ErrorMessage = "Required {0}")]
[Display(Name = "decimal")]
public decimal Decimal { get; set; }
[Required(ErrorMessage = "Required {0}")]
[Display(Name = "decimal?")]
public decimal? DecimalNullable { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
yield return new ValidationResult("Error from Validate method");
}
}
View:
#model MVCTest1.ViewModels.HomeViewModel
#{
Layout = "~/Views/Shared/_Layout.cshtml";
}
#using (Html.BeginForm(null, null, FormMethod.Post))
{
<div>
#Html.ValidationSummary()
</div>
<label id="lblNombre" for="Nombre">Nombre:</label>
#Html.TextBoxFor(m => m.Nombre)
<label id="lblDecimal" for="Decimal">Decimal:</label>
#Html.TextBoxFor(m => m.Decimal)
<label id="lblDecimalNullable" for="DecimalNullable">Decimal?:</label>
#Html.TextBoxFor(m => m.DecimalNullable)
<button type="submit" id="aceptar">Aceptar</button>
<button type="submit" id="superAceptar">SuperAceptar</button>
#Html.HiddenFor(m => m.Accion)
}

Considerations after comments' exchange:
The consensual and expected behavior among developers is that IValidatableObject's method Validate() is only called if no validation attributes are triggered. In short, the expected algorithm is this (taken from the previous link):
Validate property-level attributes
If any validators are invalid, abort validation returning the failure(s)
Validate the object-level attributes
If any validators are invalid, abort validation returning the failure(s)
If on the desktop framework and the object implements IValidatableObject, then call its Validate method and return any failure(s)
However, using question's code, Validate is called even after [Required] triggers. This seems an obvious MVC bug. Which is reported here.
Three possible workarounds:
There's a workaround here although with some stated problems with it's usage, apart from breaking the MVC expected behavior. With a few changes to avoid showing more than one error for the same field here is the code:
viewModel
.Validate(new ValidationContext(viewModel, null, null))
.ToList()
.ForEach(e => e.MemberNames.ToList().ForEach(m =>
{
if (ModelState[m].Errors.Count == 0)
ModelState.AddModelError(m, e.ErrorMessage);
}));
Forget IValidatableObject and use only attributes. It's clean, direct, better to handle localization and best of all its reusable among all models. Just implement ValidationAttribute for each validation you want to do. You can validate the all model or particular properties, that's up to you. Apart from the attributes available by default (DataType, Regex, Required and all that stuff) there are several libraries with the most used validations. One which implements the "missing ones" is FluentValidation.
Implement only IValidatableObject interface throwing away data annotations. This seems a reasonable option if it's a very particular model and it doesn't requires much validation. On most cases the developer will be doing all that regular and common validation (i.e. Required, etc.) which leads to code duplication on validations already implemented by default if attributes were used. There's also no re-usability.
Answer before comments:
First of all I've created a new project, from scratch with only the code you provided. It NEVER triggered both data annotations and Validate method at the same time.
Anyway, know this,
By design, MVC3 adds a [Required]attribute to non-nullable value types, like int, DateTime or, yes, decimal. So, even if you remove required attribute from that decimal it works just like it is one there.
This is debatable for its wrongness (or not) but its the way it's designed.
In you example:
'DataAnnotation' triggers if [Required] is present and no value is given. Totally understandable from my point of view
'DataAnnotation' triggers if no [Required] is present but value is non-nullable. Debatable but I tend to agree with it because if the property is non-nullable, a value must be inputted, otherwise don't show it to the user or just use a nullable decimal.
This behavior, as it seems, may be turned off with this within your Application_Start method:
DataAnnotationsModelValidatorProvider.AddImplicitRequiredAttributeForValueTypes = false;
I guess the property's name is self-explanatory.
Anyway, I don't understand why do you want to the user to input something not required and don't make that property nullable. If it's null then it is your job to check for it, if you don't wan't it to be null, before validation, within the controller.
public ActionResult Index(HomeViewModel viewModel)
{
// Complete values that the user may have
// not filled (all not-required / nullables)
if (viewModel.Decimal == null)
{
viewModel.Decimal = 0m;
}
// Now I can validate the model
if (ModelState.IsValid)
{
HomeModel.ProcessHome(viewModel);
return RedirectToAction("Ok");
}
}
What do you think it's wrong on this approach or shouldn't be this way?

Related

How to require parameters in asp.net actions

How to require/validate parameters for actions. Right now I have lot of actions that looks like this (which is horrible):
public ActionResult DoSomething(string paramA, string paramB, string paramC)
{
if (string.IsNullOrWhiteSpace(paramA))
{
return JsonResult(false, "paramA is missing");
}
if (string.IsNullOrWhiteSpace(paramB))
{
return JsonResult(false, "paramB is missing");
}
if (string.IsNullOrWhiteSpace(paramC))
{
return JsonResult(false, "paramC is missing");
}
//Actual Code
}
How to encapsulte this (potentially "globally")? I know that its possible to wrap parameters to model and use ModelState.IsValid like in this post: https://stackoverflow.com/a/39538103/766304
That is maybe one step forward on same places but generally I don't that it's realistic to wrap all parameters to models everywhere (~1 class definition per 1 action method... how nice is that?).
Also this is again per action ceremony which should be handled somewhere centralized:
if (ModelState.IsValid == false)
{
return BadRequest(ModelState);
}
The easiest way to do it would be to create a model class and use [Required] attributes like this:
public class FooModel
{
[Required]
public string ParamA {get;set;}
[Required]
public string ParamB {get;set;}
[Required]
public string ParamC {get;set;}
}
And then use it in your controller like this:
public ActionResult DoSomething(FooModel model)
{
if (!ModelState.IsValid)
{
// return some errors based on ModelState
}
//Actual Code
}
If you are looking for more global approach, then i believe you could look into Action Filters and use OnActionExecuting filter and handle the validation there (haven't used that myself tho).
Here is how to do it:
How can I centralize modelstate validation in asp.net mvc using action filters?
That way your method would never be called if any of the parameters were missing.
The model annotations with [Required] [Length] and all these attributes is one of the most common ways to validate your model, specially it integrates with the Razor View engine and generates JavaScript validation as well, the same will happen if you are using EntityFramework for your back end, so this way you will have validation at the level of the UI, Controller and Data access.
You can also use Code Contracts which allows you to put pre and post conditions for your method in a nice way https://msdn.microsoft.com/en-us/library/dd264808(v=vs.110).aspx
If none of the above is still not enough, then you can add some checks in either your controller action or in your business domain service to make some business validation and return an error code if any errors found

HTTP POST can't find the ASP.NET MVC controller Method and bind the ViewModel to it

I have a lengthy ViewModel class that I POST to my Controller with an HTTPPost but the controller method doesn't catch it and instead in my browser console log I get HTTP 500 POST Error
Simplified my code is like this
public class MyDataViewModel{
public int Property1 { get; set; }
public string Property2 { get; set; }
public string Property3 { get; set; }
public string Property4 { get; set; }
//etc to Property 100
}
cshtml:
<form action="#(Url.Action("Edit", "MyData"))" id="myform" class="myform" method="post">
#Html.AntiForgeryToken()
<div class="form-horizontal">
#Html.ValidationSummary(true)
#Html.EditorFor(model => model.Property1)
//...
#Html.EditorFor(model => model.Property100)
Some properties make use of Data Annotations.
When I submit the from the client side validation does work correctly and block the post before it is submitted if a required field is not captured.
However after the form submission my POST Method in my controller does not get hit. I verify this by putting a breakpoints on it as well as seeing the HTTP 500 error in my browser console log.
This is the method
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(MyDataViewModel myDataViewModel)
{
However when I change it the method parameter to an object i.e.
public async Task<IActionResult> Edit(object myDataViewModel)
it does get hit. I then try to cast that object to (MyDataViewModel)myDataViewModel the casting fails so something isn't right.
I have looked through fiddler to confirm all those data properties are being sent and they are.
I then tried the following:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(object myDataViewModel)
{
Type myType = myDataViewModel.GetType();
IList<PropertyInfo> props = new List<PropertyInfo>(myType.GetProperties());
foreach (PropertyInfo prop in props)
{
object propValue = prop.GetValue(myDataViewModel, null);
// Do something with propValue
}
}
To see if I could find which property has the issue. However props.Count = 0 which means the object is empty. Why would fiddler show all those fields but MVC not receive it? It makes no sense
I have the same type forms on other page that use the same <form> tags and method of posting which work so not sure why this one if failing.
In Visual Studio I can collapse the various html tags in the cshtml including the <form> tag so that doesn't pick up anything suspicious and shows each html tag has a corresponding closing tag.
I ended up creating a dummy viewmodel, using this dummy view model type as the HTTPPost parameter, and putting in a few properties of the real viewmodel in at a time, compiling the solution, test, until I reach the point where it doesn't hit the POST controller method..
Took a while but I eventually found the culprit property.
[Required, Display(Name = "Flag"),StringLength(50)]
public bool IsFlag{ get; set; }
I used a data annotation StringLength for a bool field which wasn't valid for a bool.
This resulted in the front end not showing any client validation jquery.validate error before HTTPPost and the HTTPPost method controller method unable to get hit.
I would still like to now if there is a quicker way to troubleshoot this type of issue or determine which property is at fault as the method I used was very laborious.
In my case, I forgot to put "public" in the post action. It puzzled me quite a while. Also, if there is some problem with the post action, it will enter the get action (if you don't specifically put HttpGet there) and it makes you wonder why I click the button but still goes to the get action.

ASP.NET MVC Model validation error beside field

I have a self validation model in my code based on this link:
ASP.NET MVC: Custom Validation by DataAnnotation
public class TestModel : IValidatableObject
{
public string Title { get; set; }
public string Description { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (Title == null)
yield return new ValidationResult("The title is mandatory.", new [] { "Title" });
if (Description == null)
yield return new ValidationResult("The description is mandatory.", new [] { "Description" });
}
}
All of this works well. But my question is this: The error messages above are displayed as ValidationSummary errors. Is there any way to make the title error message display beside the title field (on the form view) and the description error message display beside the description field, just like in client side validation?
First make sure that you have added the correct razor markup next to each field, for example:
#Html.ValidationMesageFor(m => m.Title)
This will display the error message only if there is an error in the ModelState that is associated with the same field, so ensure ModelState["Title"] contains the error, otherwise you won't see the message.
This all is customized, at least, by CSS, or you can always use javascript (jquery).
UPDATE:
Well, i think, this is quite little info, therefore, try to add a some more.Basically, you also can use html.helper ValidationMessageFor + span tag. For instance:
#Html.EditorFor(x=>x.ModelProperty)<span>#Html.ValidationMessageFor(x=>x.ModelProperty)</span>
And in action method after your magic object validates itself, analyze result of your validation.
Just raugh demo:
var result = myObject.Validate(someContext);
if (result != result.Success)
{
ModelState.AddModelError("Title"); // if error in Title prop (otherwise "Description")
return View (myObject);
}
Or, if your model is validated through ValidationAttribute, you can check out this via ModelState.IsValid or ModelState.IsValidField methods.

How to get the boolean value of checkbox in ASP.net MVC 4

I have a checkbox in .cshtml page;
#Html.CheckBox("All", new { #class = "chkBox"})
I want to get the boolean value of this checkbox in my controller class, for that I have tried
bool all = Convert.ToBoolean(collection["All"]);
where collection is the object of FormCollection Class.
But I am getting value of the checkbox as "all", I don't know how to get the checked or unchecked value using formcollection object.
If anyone have any Idea then please tell me. Thanks.
Why not bind it to a model?
public class MyModel {
public bool All {get; set;}
}
In your view just do the following
#html.CheckBoxFor(m=>m.All)
That should do it.
You Can Get that In this way also:
Let us assume your controller name is Index
[HttpPost]
public ActionResult Index(bool All)
{
return View();
}
If "All"(Checkbox) is Checked then All becomes true.
If "All"(Checkbox) is UNchecked then All becomes false.
Based on true or false You can modify your code in your way.
Note:The variable you mentioned in Post Controller Must be same as the name which you given for checkbox in cshtml.
i.e;
#Html.CheckBox("All", new { #class = "chkBox" })
public ActionResult Index(bool All)
That's because you input tag has the value as all. In this case, it's a good pratice to bind the right type, for sample:
public ActionResult Post(bool all)
{
//here you will get the all boolean value, if check or not as boolean
}
Or, better than this, you could create a ViewModel and bind it, for sample:
Add a class wih the fields you want to use on the Models folder.
public class MyViewModel
{
public string Name { get; set; }
public bool All { get; set; }
}
After it, in your View, you just type the View and create the controls using the Html helpers, for sample:
#model Models.MyViewModel
#using (Html.BeginForm("Post", "Home", FormMethod.Post))
{
#Html.TextBoxFor(model => model.Name)
#Html.CheckBoxFor(model => model.All)
<input type="submit" value="Send" />
}
Obs: it's just a sample
And in your controller, you just get a instance of this model, and let asp.net mvc bind it for you as the object:
public ActionResult Post(MyViewModel model)
{
// here you have the model.All as boolean value
}
It's a good pratice to do, because ViewModels represents the fields you need on the View and transfer it between contexts (controllers, views, services, apis, etc..).
You can try this approach. You can get the more information from here asp.net mvc: why is Html.CheckBox generating an additional hidden input
bool all = (collections["all"] != "false");
You need to modify your code like this. Its working: you need to first convert it in string then convert to boolean.
Convert.ToBoolean(collection["All"].ToString());
Thanks Guys for all of your answers, all make sense. But in accordance of achieving the above requirement I have modified my .cshtml code a bit, rather then using razor syntax I am using this in my .cshtml page
<input id="All" name="All" value="true" type="checkbox" class="chkBox" />
Now in my .cs page I have harvested the boolean value as follows;
all = Convert.ToBoolean(collection["All"]);
So, basically what it does is, if the checkbox is left unchecked then it will give value as false otherwise, formcollection will take into account the checkbox and will give the value of all as true.

MVC2 and two different models using same controller method? Possible?

I don't know if this is the right way of doing this or not, but I am using Jquery and MVC2. I am using a the $.ajax method to make a call back to a controller to do some business logic on a .blur of a textbox.
I have two views that basically do the same thing with the common data, but are using different models. They both use the same controller. It might be easier to explain with code:
So here are the two models:
public class RecordModel {
public string RecordID { get; set; }
public string OtherProperties { get; set; }
}
public class SecondaryModel {
public string RecordID { get; set; }
public string OtherPropertiesDifferentThanOtherModel { get; set; }
}
There are two views that are strongly typed to these models. One is RecordModel, the other SecondaryModel.
Now on these views is a input="text" that is created via:
<%= Html.TextBoxFor(model => model.RecordID) %>
There is jQuery javascript that binds the .blur method to a call:
<script>
$('#RecordID').blur(function() {
var data = new Object();
data.RecordID = $('#RecordID').val();
// Any other stuff needed
$.ajax({
url: '/Controller/ValidateRecordID',
type: 'post',
dataType: 'json',
data: data,
success: function(result) {
alert('success: ' + result);
},
error: function(result) {
alert('failed');
}
});
}
</script>
The controller looks like:
[HttpPost]
public ActionResult ValidateRecordID(RecordModel model) {
// TODO: Do some verification code here
return this.Json("Validated.");
}
Now this works fine if I explicitly name the RecordModel in the controller for the View that uses the RecordModel. However, the SecondaryModel view also tries to call this function, and it fails because it's expecting the RecordModel and not the SecondaryModel.
So my question is this. How can two different strongly typed views use the same Action in a controller and still adhering to the modeling pattern? I've tried abstract classes and interfaces (and changing the view pages to use the Interface/abstract class) and it still fails.
Any help? And sorry for the robustness of the post...
Thanks.
You could define an interface for those classes.
interface IRecord
{
string RecordID { get; set; }
string OtherProperties { get; set; }
}
and make the method receive the model by using that:
[HttpPost]
public ActionResult ValidateRecordID(IRecord model)
{
// TODO: Do some verification code here
return this.Json("Validated.");
}
If you only need the RecordID, you can just have the controller method take int RecordID and it will pull that out of the form post data instead of building the view model back up and providing that to your action method.
[HttpPost]
public ActionResult ValidateRecordID(int RecordID) {
// TODO: Do some verification code here
return this.Json("Validated.");
}
There is no direct way of binding data to a interface/abstract class. The DefaultModelBinder will try to instantiate that type, which is (by definition) impossible.
So, IMHO, you should not use that option. And if you still want to share the same controller action between the two views, the usual way of doing that would be using a ViewModel.
Make your strongly-typed views reference that viewmodel. Make the single shared action receive an instance of it. Inside the action, you will decide which "real" model should be used...
If you need some parameter in order to distinguish where the post came from (view 1 or 2), just add that parameter to the ajax call URL.
Of course, another way is keeping what you have already tried (interface/abstract class), but you'll need a custom Model Binder in that case... Sounds like overcoding to me, but it's your choice.
Edit After my dear SO fellow #Charles Boyung made a gracious (and wrong) comment below, I've come to the conclusion that my answer was not exactly accurate. So I have fixed some of the terminology that I've used here - hope it is clearer now.
In the case above your action could accept two strings instead of a concrete type.
Another possibility is having two actions. Each action taking one of your types. I'm assuming that functionality each type is basically the same. Once the values have been extracted hand them off to a method. In your case method will probably be the same for each action.
public ActionResult Method1(Record record)
{
ProcessAction(record.id, record.Property);
}
public ActionResult Action2(OtherRecord record)
{
ProcessAction(record.id, record.OtherProperty);
}
private void ProcessAction(string id, string otherproperity)
{
//make happen
}

Resources