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.
Related
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.
I'm trying to better understand how to properly structure my ASP.NET MVC code to handle a situation where a single view contains multiple forms. I feel that it makes sense to submit the forms to their own action methods, so that each form can benefit from its own view model parameter binding and validation, and to avoid putting all form parameters into 1 larger, monolithic view model.
I'm trying to code this pattern, but I can't seem to tie the loose ends together.
I've written some example action methods below, along with example view model classes, that I think demonstrate what I'm trying to achieve. Lets say that I've got an Item Detail action method and view. On this Detail view, I've got two forms - one that creates a new Comment and another that creates a new Note. Both Comment and Note forms POST to their own action methods - DetailNewComment and DetailNewNote.
On success, these POST handler action methods work just fine. On an invalid model state though, I return View(model) so that I can display the issues on the original Detail view. This tries to render a view named Brief though, instead of Detail. If I use the overloaded View call that allows me to specify which view to render, then now I have issues with the different view model classes that I'm using. The specific view model classes now no longer work with the original DetailViewModel.
I get the feeling that I'm doing this completely wrong. How am I supposed to be handling this scenario with multiple forms? Thanks!
public ActionResult Detail(int id)
{
var model = new ItemDetailViewModel
{
Item = ItemRepository.Get(id)
};
return View(model);
}
[HttpPost]
public ActionResult DetailNewComment(int id, ItemDetailNewCommentViewModel model)
{
if (!ModelState.IsValid)
{
return View(model);
}
var comment = CommentRepository.Insert(new Comment
{
Text = model.Text
});
return RedirecToAction("Detail", new { id = id; });
}
[HttpPost]
public ActionResult DetailNewNote(int id, ItemDetailNewNoteViewModel model)
{
if (!ModelState.IsValid)
{
return View(model);
}
var note = NoteRepository.Insert(new Note
{
Text = model.Text
});
return RedirectToAction("Detail", new { id = id; });
}
... with view models something like ...
public class ItemDetailViewModel
{
public Item Item { get; set; }
}
public class ItemDetailNewCommentViewModel
{
public string Text { get; set; }
}
public class ItemDetailNewNoteViewModel
{
public string Text { get; set; }
}
For your case I'd recommend to have a master model for example your
ItemDetailViewModel class to which you'll add a property for each sub-model
public class ItemDetailViewModel
{
public Item Item { get; set; }
public ItemDetailNewCommentViewModel NewCommentModel {get;set;}
public ItemDetailNewNoteViewModel NoteModel {get;set;}
}
Your Detail view will be the master view and the other two will be partial views.
Master view will receive an instance of ItemDetailViewModel as model and inside view you will render your partials by passing Model.NewCommentModel and Model.NoteModel as their corresponding models. For being able to use separate actions for each form, instead of regular forms you can use ajax forms, thus you will send to the server only relevant information without altering the rest of the master view.
The chief problem here is what happens when the user messes up and their post doesn't pass validation server-side. If you choose to take them to a page where just the one form is presented, then you can post to a different action, but if you want both forms re-displayed, then they both should point to the same action.
Really, you just have to make a choice. I've seen sites handle it both ways. Personally, I prefer to re-display the original form, which means handling both forms in the same action. It can lead to bloat, but you can factor out a lot of logic from the action such that you end up with mostly just a branch depending on which form was submitted.
I am using ASP.NET MVC3 with the razor view engine. I am also using a the Yahoo User Interface 2 (YUI2) simple editor.
My view has a view model called ProductEditViewModel. In this view model I have a property defined as:
public string LongDescription { get; set; }
In my view I would create the YUI2 simple editor from this input field. The field is defined in the view like:
<td>#Html.TextAreaFor(x => x.LongDescription, new { cols = "75", rows = "10" })<br>
#Html.ValidationMessageFor(x => x.LongDescription)
</td>
Here is a partial view of my Edit action method:
[Authorize]
[HttpPost]
[ValidateInput(false)]
public ActionResult Edit(ProductEditViewModel viewModel)
{
if (!ModelState.IsValid)
{
// Check if valid
}
// I added this as a test to see what is returned
string longDescription = viewModel.LongDescription;
// Mapping
Product product = new Product();
product.InjectFrom(viewModel);
// Update product in database
productService.Update(product);
return RedirectToRoute(Url.AdministrationProductIndex());
}
When I view the contents of the longDescription variable then it should contain the values from the editor. If I edit the contents in the editor then longDescription still only contains the original contents, not the updated contents. Why is this?
I suspect that somewhere in your POST action you have written something like this:
[Authorize]
[HttpPost]
[ValidateInput(false)]
public ActionResult Edit(ProductEditViewModel viewModel)
{
...
viewModel.LongDescription = "some new contents";
return View(viewModel);
}
If this is the case then you should make sure that you have cleared the value from the ModelState before modifying it because HTML helpers will always first use the value from model state and then from the model.
So everytime you intend to manually modify some property of your view model inside a POST action make sure you remove it from modelstate:
ModelState.Remove("LongDescription");
viewModel.LongDescription = "some new contents";
return View(viewModel);
Now when the view is displayed, HTML helpers that depend on the LongDescription property will pick the new value instead of using the one that was initially submitted by the user.
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?
In my model, it seems that Validate() is only called AFTER both properties pass validation.
public class MyModel : IValidatableObject
{
[Required]
public string Name { get; set;}
[Required]
public string Nicknames {get; set;}
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if(Nicknames != null && Nicknames.Split(Environment.NewLine.ToCharArray()).Count() < 2)
return yield result new ValidationResult("Enter at least two nicknames, new [] { "Nicknames" });
}
}
When a user enters a single line of text in the Nicknames text area but leaves the Name text box empty, only the Required error message for the Name property is displayed. The error message that should be displayed from the Validate() function never shows up.
Only after entering a name in the Name text box and some text in the Nicknames text is the Validate() function called.
Is this how it's supposed to work? It seems odd that a user is shown an error message on a subsequent page when the error is being caused on the current page.
This is by design. Object-level validation does not fire until all the properties pass validation because otherwise it is possible that the object is incomplete. The Validate method is meant for thing like comparing one property to another. In your case you should write a custom property validator.