suppose I have the following class
class A
{
[Required]
public string Name {get; set;}
[Required]
public string NickName {get; set;}
[Required]
public string UserId {get; set;}
}
and from the form I am passing only the Name and NickName to controller and before checking the model state simply I assign the user id to the UserId property as below
[HttpPost]
public IActionResult Save(A model)
{
model.UserId = User.GetLoggedInUserId<string>();
if (!ModelState.IsValid)
{
return View(model);
}
}
even though I have assign the user id before checking the model state it still returns the validation state false and complaining the for the user id. one way to come out of this problem is to create a view model which makes things more complex, because of assigning the values from view model to class it self.
any idea how to solve this.
Note: the question is not only for the User Id in the class there maybe other properties as well that may not be passed from the form to controller and the values maybe assigned to them from controller
You could try to remove the 'UserId' from the model validation before calling ModelState.IsValid. Code like this:
[HttpPost]
public IActionResult CreateA(A a)
{
var state1 = ModelState.IsValid; // false
ModelState.Remove("UserId"); //using Remove method to remove the specified object from the model-state dictionary.
var state2 = ModelState.IsValid; // true
a.UserId = "SN1001";
if (ModelState.IsValid)
{
var data = a.UserId;
}
return RedirectToAction(nameof(Index));
}
The screenshot as below:
Besides, you could also try to use the TryValidateModel() method to validate the model again in the controller, code like this:
[HttpPost]
public IActionResult CreateA(A a)
{
var state1 = ModelState.IsValid; // false
ModelState.Remove("UserId");
a.UserId = "SN1001";
if (!TryValidateModel(a, nameof(a)))
{
var state2 = ModelState.IsValid;
}
if (ModelState.IsValid)
{
var data = a.UserId;
}
return RedirectToAction(nameof(Index));
}
The result like this:
Reference: Model State Rerun validation
Edit
[Note] If the ModelState.IsValid is false first, before rerun validation using the TryValidateModel method, we have to remove the error from the ModelState.
You can also pass the value of UserId field using a hidden field with a default value, like:
#Html.HiddenFor(m => m.UserId, new { #Value = User.Identity.Name });
This answer might help What does ModelState.IsValid do?
ModelState.IsValid indicates if it was possible to bind the incoming values from the request to the model correctly and whether any explicitly specified validation rules were broken during the model binding process.
There is no way to solve this. If you don't need this validation, then remove it altogether with the attributes and add the appropriate value handling logic with code.
The isValid will just validate the state on binding time and that's that.
Related
I have the following [post]create method on my controller:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include="Username","Name")] Admin admin)
{
// I assign the current date as the value to the HireDate property
admin.HireDate = DateTime.Today;
if (ModelState.IsValid)
{
// I do the insert
}
return View(admin);
}
ModelState.IsValid returns as false.
I looked at the ModelState object and found that the error IS in the HireDate property, because it's a non null field and the value is still null in the ModelState object.
I don't know much about ModelState but I'm assuming it's only validating the model built with the POST call.
Is there a way to "update" the ModelState object with the new data that I assigned on the controller (admin.HireDate = DateTime.Today)?
A more appropriate approach would be to assign the property before the Page even renders. So on your Create method that returns the original view you can do this.
public ActionResult Create()
{
Admin admin = new Admin();
admin.HireDate = DateTime.Today;
return View(admin);
}
You will then have to use #Html.HiddenFor(x => x.HireDate) in order for the View to be able to send it back to the Controller.
C# valid ModelState fails .isValid
Search Controller
//
// GET: /Search/Create
public ActionResult Create() { return View(); }
//
// POST: /Search/Create
[HttpPost]
public ActionResult Create(Search search)
{
search.Created = DateTime.Now;
search.SearchSet = "test data";
search.URLParameter = 1432567389;
if (ModelState.IsValid)
{
_db.Searchs.Add(search);
_db.SaveChanges();
return RedirectToAction("Index");
}
return View(search);
}
Search Class
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel.DataAnnotations;
namespace TalentProfile.Models
{
public class Search
{
public int Id { get; set; }
[Required]
public int URLParameter { get; set; }
[Required, MaxLength(50, ErrorMessage = "Client must be 50 characters or less")]
public string Client { get; set; }
[DataType(DataType.MultilineText), StringLength(150, ErrorMessage = "{0} must be {1} characters or less")]
public string Notes { get; set; }
[Required]
public string SearchSet { get; set; }
[Required]
public DateTime Created { get; set; }
}
}
If I run in debug all the fields in the class are properly set but it fails ModelState.IsValid. If I drill into the ModelState.IsValid it is false. Drilling down further I find the error “The SearchSet field is required”.
The SearchSet field is properly set to “test data”. If I remove the ModelState.IsValid check the save to the database succeeds.
Why am I getting the “field is required” error if the field contains valid data.
Talent Controller
//
// GET: /Talent/Create
public ActionResult Create()
{
return View();
}
//
// POST: /Talent/Create
[HttpPost]
public ActionResult Create(Talent talent)
{
talent.Modified = talent.Created = DateTime.Now;
if (ModelState.IsValid)
{
_db.Talents.Add(talent);
_db.SaveChanges();
CreatePhoto(talent.Id);
return RedirectToAction("Index");
}
return View(talent);
}
Updated: Search Controller and Create View
//
// GET: /Search/Create
public ActionResult Create()
{
Search search = new Search();
search.SearchSet = "test Data";
return View(search);
}
//
// POST: /Search/Create
[HttpPost]
public ActionResult Create(Search search)
{
search.Created = DateTime.Now;
search.URLParameter = 1435267836;
if (ModelState.IsValid)
{
_db.Searchs.Add(search);
_db.SaveChanges();
return RedirectToAction("Index");
}
return View(search);
}
In view put: #Html.HiddenFor(model => model.SearchSet)
There are a couple of things to note here:
1) putting a Required attribute on value types is pointless. Value types must always contain a value, they can't be null.. thus Required will always pass for them.
In particular, DateTime is a value type. Also, your UrlParameter is an int, which is also a value type. it will always contain at least a default value. Required is redundant.
2) As others have said, the ModelState is only set during databinding. data binding only occurs before the method is called, or when you call UpdateModel or TryUpdateModel. Regardless of whether or not you have other errors, you cannot update the model and expect the ModelState to reflect the changes if you have not called UpdateModel or TryUpdateModel.
3) Client is set as required, but you don't seem to be setting it. So if you set Client and SearchSet then do TryUpdateModel(search) it should be valid.
4) Talent works because, like in Search, talent.Modified and talent.Created are DateTimes and are non-nullable, thus they will never fail validation, even if you don't set a value (see for yourself, remove the assignment and see how it still validates). Any other required fields you may have on Talent are likely also value types.
The ModelState is resolved during model binding, so the Search object passed to Create has to have the SearchSet value set. I.e. setting SearchSet in the controller is not going to make the model valid if the value was empty during model binding.
Use TryUpdateModel(search); before your ModelState check. So it should be:
[HttpPost]
public ActionResult Create(Search search)
{
search.Created = DateTime.Now;
search.SearchSet = "test data";
search.URLParameter = 1432567389;
TryUpdateModel(search);
if (ModelState.IsValid)
{
_db.Searchs.Add(search);
_db.SaveChanges();
return RedirectToAction("Index");
}
return View(search);
}
The MVC runtime would've already validated the model before executing your action, and it hasn't revalidated it after you set the parameters locally in the action.
So the idea is you do a post with the search model in it, for example from an html form, and MVC checks validity BEFORE going into your action, so you don't have to :)
I have two related POCOs
public class Parent
{
public Guid Id {get; set;}
public IList<Child> ChildProperty {get; set;}
}
public class Child
{
public Guid Id {get; set;}
public String Name {get; set;}
}
and I have a .cshtml Razor view with
<div>
#{
var children =
new SelectList(Child.FindAll(), "Id", "Name").ToList();
}
#Html.LabelFor(m => m.Child)
#Html.DropDownListFor(m => m.Child.Id, , children, "None/Unknown")
</div>
I'd like to do the following in my controller class:
[HttpPost]
public ActionResult Create(Parent parent)
{
if (TryUpdateModel(parent))
{
asset.Save();
return RedirectToAction("Index", "Parent");
}
return View(parent);
}
Such that if the user selects "None/Unknown", the child value of the parent object in the controller is null but if the user selects any other value (i.e. an ID of a child object retrieved from the database), the child value of the parent object is instantiated and populated with that ID.
Basically I'm struggling with how to persist a list of possible entities across the HTTP stateless boundary such that one of the entities is properly rehydrated and assigned via the default model binder. Am I just asking for too much?
Am I just asking for too much?
Yes, you are asking for too much.
All that's sent with the POST request is the ID of the selected entity. Don't expect to get much more than that. If you want to rehydrate or whatever you should query your database. The same way you did in your GET action to populate the child collection.
Oh and there's a problem with your POST action => you are calling the default model binder twice.
Here are the 2 possible patterns (personally I prefer the first but the second might be useful in some situations as well when you want to manually call the default model binder):
[HttpPost]
public ActionResult Create(Parent parent)
{
if (ModelState.IsValid)
{
// The model is valid
asset.Save();
return RedirectToAction("Index", "Parent");
}
// the model is invalid => we must redisplay the same view.
// but for this we obviously must fill the child collection
// which is used in the dropdown list
parent.ChildProperty = RehydrateTheSameWayYouDidInYourGetAction();
return View(parent);
}
or:
[HttpPost]
public ActionResult Create()
{
var parent = new Parent();
if (TryUpdateModel(parent))
{
// The model is valid
asset.Save();
return RedirectToAction("Index", "Parent");
}
// the model is invalid => we must redisplay the same view.
// but for this we obviously must fill the child collection
// which is used in the dropdown list
parent.ChildProperty = RehydrateTheSameWayYouDidInYourGetAction();
return View(parent);
}
In your code you've made some mix of the two which is wrong. You are basically invoking the default model binder twice.
My Model class is as follow :
public class PostInputViewModel
{
[Required]
[MinLength(1)]
[MaxLength(125)]
public string Title { get; set; }
[Required]
[AllowHtml]
[Display(Name="Content")]
public string Content { get; set; }
}
and controller is as follow :
[HttpPost]
public ActionResult Write(PostInputViewModel input)
{
if (!ModelState.IsValid)
return View(input);
var post = new Post
{
Title = input.Title,
Content = input.Content,
DateCreated = DateTime.Now,
DateModified = DateTime.MaxValue,
};
dbContext.Posts.Add(post);
dbContext.SaveChanges();
return RedirectToAction("Index", "Home");
}
When I run web application by clicking F5, and if I don't input title and content value, ModelState.IsValid is false, However if I test controller class with unit test case, ModelState.IsValid is always true. The test case is as follow :
[TestMethod]
public void write_should_validate_model()
{
var input = new PostInputViewModel();
input.Title = null;
input.Content = null;
var actionResult = controller.Write(input) as ViewResult;
Assert.IsFalse(actionResult.ViewData.ModelState.IsValid);
}
Am I missing something?
Thanks in advance.
If you want to have your controller try to validate the model, you can call the TryValidateModel method before your assert:
controller.TryValidateModel(input);
But I agree that you'd really only be testing the validation attributes. It might be OK, though; it would validate that your model has the expected attributes applied.
Validation of model doesn't happen in the Controller. It happens before the model is passed to the controller.
Notice the controller action is only 'testing' whether the model is valid. Who is validating the model in your test case? Nothing!
You can do the validation using Validator class of .NET however in that case you would be testing .NET validation. This is one of the common mistakes people make while writing unit tests. They test 3rd party code instead of their own.
If you really want to test that you have applied the correct validation attributes to the class then you can simply reflect your class and check for attributes on properties. This way you would be skipping the .NET validation layer and your test will only fail if you miss an attribute.
Validation actually happens before the Write method on your controller is called, which populates the ModelState property.
Your unit test isn't really testing the controller in my opinion (if that is in fact what you're trying to do).
A true controller test would look something like this:
[TestMethod]
public void write_should_validate_model()
{
controller.ModelState.AddModelError("Title", "Empty"); //These values don't really matter
var actionResult = controller.Write(new PostInputViewModel()) as ViewResult;
//Assert that the correct view was returned i.e. Not Home/Index
//Assert that a new post has not been added to your Mock Repository
}
I'm using ASP.NET MVC 3 code-first and I have added validation data annotations to my models. Here's an example model:
public class Product
{
public int ProductId { get; set; }
[Required(ErrorMessage = "Please enter a name")]
public string Name { get; set; }
[Required(ErrorMessage = "Please enter a description")]
[DataType(DataType.MultilineText)]
public string Description { get; set; }
[Required(ErrorMessage = "Please provide a logo")]
public string Logo { get; set; }
}
In my website I have a multi-step process to create a new product - step 1 you enter product details, step 2 other information etc. Between each step I'm storing each object (i.e. a Product object) in the Session, so the user can go back to that stage of the process and amend the data they entered.
On each screen I have client-side validation working with the new jQuery validation fine.
The final stage is a confirm screen after which the product gets created in the database. However because the user can jump between stages, I need to validate the objects (Product and some others) to check that they have completed the data correctly.
Is there any way to programatically call the ModelState validation on an object that has data annotations? I don't want to have to go through each property on the object and do manual validation.
I'm open to suggestions of how to improve this process if it makes it easier to use the model validation features of ASP.NET MVC 3.
You can call the ValidateModel method within a Controller action (documentation here).
ValidateModel and TryValidateModel
You can use ValidateModel or TryValidateModel in controller scope.
When a model is being validated, all validators for all properties are
run if at least one form input is bound to a model property. The
ValidateModel is like the method TryValidateModel except that the
TryValidateModel method does not throw an InvalidOperationException
exception if the model validation fails.
ValidateModel - throws exception if model is not valid.
TryValidateModel - returns bool value indicating if model is valid.
class ValueController : Controller
{
public IActionResult Post(MyModel model)
{
if (!TryValidateModel(model))
{
// Do something
}
return Ok();
}
}
Validate Models one-by-one
If you validate a list of models one by one, you would want to reset ModelState for each iteration by calling ModelState.Clear().
Link to the documentation
//
var context = new ValidationContext(model);
//If you want to remove some items before validating
//if (context.Items != null && context.Items.Any())
//{
// context.Items.Remove(context.Items.Where(x => x.Key.ToString() == "Longitude").FirstOrDefault());
// context.Items.Remove(context.Items.Where(x => x.Key.ToString() == "Latitude").FirstOrDefault());
//}
List<ValidationResult> validationResults = new List<ValidationResult>();
bool isValid = Validator.TryValidateObject(model, context, validationResults, true);
if (!isValid)
{
//List of errors
//validationResults.Select(r => r.ErrorMessage)
//return or do something
}
I found this to work and do precisely as expected.. showing the ValidationSummary for a freshly retrieved object on a GET action method... prior to any POST
Me.TryValidateModel(MyCompany.OrderModel)