How can I validate with ModelState after adding values on the controller? - c#

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.

Related

ModelState.IsValid = False

ModelState is throwing an error that one of the parameters [UserId] is null. That field isn't being set on my form. It's being set in the controller.
How do I remove it from the ModelState.IsValid test?
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("Id,UserId,Created,TimeStamp,Name,Role,Description,Partner,PartnerAmount,Competitor,IsDeleted")] Relationship relationship)
{
relationship.UserId = User.Identity.Name.Replace(#"XXXXX\","");
relationship.Created = DateTime.Now;
relationship.TimeStamp = DateTime.Now;
relationship.IsDeleted = false;
if (ModelState.IsValid)
{
_context.Add(relationship);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
return View(relationship);
}
It won't pass ModelState.IsValid b/c of UserId. But, it doesn't check Created, Timestamp or IsDeleted. Why doesn't it check those, but it does check UserId? All of these fields are required in my Model.
Remove the field from the view model. If you don't have one, create on. Using entities in your views generates security vurnabilities as an attacker could set fields that you might not change once posted.
Using an view model makes sure that only fields that are allowed to be changed are available in the browser.
If you still want to use your entity, change the UserId to int? which tells the model validator that it doesn't have to be set.
You can also remove the validations ModelState["UserId"].Errors.Clear(); But I strongly discourage from that. Use a specific view model.
You can't change ModelState.IsValid by changing any properties inside of the controller. IsValid still will be the same, since a list of errors will be the same. UserId should be assigned before the model reaches the controller.
So assign UserId=0 if it is nullable and add this line somewhere inside of the form of the view,
<input type="hidden" asp-for="#Model.UserId" value="#Model.UserId"/>
In the controller action method, try to use Rerun validation, call the TryValidateModel method, as shown here:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("Id,UserId,Created,TimeStamp,Name,Role,Description,Partner,PartnerAmount,Competitor,IsDeleted")] Relationship relationship)
{
relationship.UserId = User.Identity.Name.Replace(#"XXXXX\","");
relationship.Created = DateTime.Now;
relationship.TimeStamp = DateTime.Now;
relationship.IsDeleted = false;
if (!TryValidateModel(relationship, nameof(relationship)))
{
return View(relationship);
}
_context.Add(relationship);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}

satisfying the validation before checking the validation

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.

Does going from one Action method to the other action method clear class variables?

So in the same controller I have a Login Action method like this:
public ActionResult Login()
{
LoginModel model = this.LoginManager.LoadLoginPageData();
this.ForgotPasswordMethod = model.ForgotPasswordMethod;
return View(model);
}
Notice I set a variable there: ForgotPasswordMethod
So now when there on that page if they click on a link, it call another action result in the same controller class like this:
public ActionResult ForgotPassword()
{
if (!string.IsNullOrWhiteSpace(this.ForgotPasswordMethod) && this.ForgotPasswordMethod.Trim().ToUpper() == "TASKS")
return View();
return null; //todo change later.
}
Notice I tried to read the value of ForgotPasswordMethod , but it was NULL but it is NOT null when I am in the Login() method. So what should I do?
ASP.NET MVC was designed to return back to a cleaner, more straightforward web world built on HTTP, which is stateless, meaning that there is no "memory" of what has previously occurred unless you specifically use a technique that ensures otherwise.
As a result, whatever state you set via one ActionResult will no longer be the same state that exists when another ActionResult is invoked.
How do you "fix" this? You have a variety of options, depending on what your needs are:
Render the value to the client and post the value back to your second ActionResult method.
Store the value as a header and check that header.
Store the value in a cookie and check the cookie.
Store the value in session.
Store the value in a database.
Store the value in a static dictionary.
what if you store forgotpasswordmethod in Viewbag like
public ActionResult Login()
{
LoginModel model = this.LoginManager.LoadLoginPageData();
Viewbag.ForgotPasswordMethod = model.ForgotPasswordMethod;
return View(model);
}
then in the link of your page you can pass the value from the ViewBag
<a href=#Url.Action("ForgotPassword", "Name of your Controller", new { methodName = ViewBag.ForgotPasswordMethod })>Forgot Password</a>
Change your forgotpassword to
public ActionResult ForgotPassword(string methodName)
{
if (!string.IsNullOrWhiteSpace(methodName) && methodName.Trim().ToUpper() == "TASKS")
return View();
return null; //todo change later.
}

I change the model values in the controller but the view receives the model with the old values

I have this action:
//Edit
[HttpPost]
public ActionResult InvoiceType(clsInvoiceType Model, string Stade)
{
Model.Stade = Stade== "1" ? true : false;
return PartialView(Model);
}
In this action I try to change the value for Model.Stade and return the model but the view receive always the values sent to this action, the problem is that I try to change the value of model here in the action and return the new model values to the view but the view receive the original model values so my changes is not working, what is the problem?
How can I change the original model values and send them to the view?
The ModelState object contains the values that were posted back to your action method. The Html.*For() helper methods in your view will always pull values from the ModelState before looking at the actual vaules in your model. If you want to change a value you need to remove the posted value from the ModelState.
[HttpPost]
public ActionResult InvoiceType(clsInvoiceType Model, string Stade)
{
this.ModelState.Remove("Stade"); // or .Clear() to remove all keys
Model.Stade = Stade== "1" ? true : false;
return PartialView(Model);
}
May be you should disable caching by:
[OutputCache(Duration=0, VaryByParam="none")]
[HttpPost]
public ActionResult InvoiceType(clsInvoiceType Model, string Stade)
{
Model.Stade = Stade== "1" ? true : false;
return PartialView(Model);
}

How to update ModelState?

This is a controller action that I call with ajax post method:
[HttpPost]
public ActionResult Add(Comment comment)
{
if (User.Identity.IsAuthenticated)
{
comment.Username = User.Identity.Name;
comment.Email = Membership.GetUser().Email;
}
if (ModelState.IsValid)
{
this.db.Add(comment);
return PartialView("Comment", comment);
}
else
{
//...
}
}
If the user is logged in, submit form doesn't have Username and Email fields, so they don't get passed by ajax call. When action gets called ModelStat.IsValid returns false, because these two properties are required. After I set valid values to properties, how do I trigger model validation to update ModelState?
You can use a custom model binder to bind the comment's Username and Email properties from User.Identity. Because binding occurs before validation the ModelState will be valid then.
Another option is to implement a custom model validator for the Comment class, that checks the ControllerContext.Controller for a validated user.
By implementing any of these options you can remove the first if check.
You can try calling the built in TryUpdateModel method which returns a boolean so you can check that value.
UPDATE: Try using TryUpdateModel with exceptions. Use a formcollection instead of Comment into the Action.
[HttpPost]
public ActionResult Add(FormCollection collection)
{
string[] excludedProperties = new string[] { "Username". "Email" };
var comment = new Comment();
if (User.Identity.IsAuthenticated)
{
comment.Username = User.Identity.Name;
comment.Email = Membership.GetUser().Email;
}
TryUpdateModel<Comment>(comment, "", null, excludedProperties, collection.ToValueProvider());
if (ModelState.IsValid)
{
this.db.Add(comment);
return PartialView("Comment", comment);
}
else
{
//...
}
}

Categories