C#, Valid ModelState fails .isValid - c#

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 :)

Related

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.

How to Handle C# Data Type Model Validation before get to controller

I have this request model in C# WebAPI
public class RequestModel{
public int Code { set; get; }
}
then I have API Controller that receive that model as its request body (POST). The problem is, if i run a negative case for testing purpose. I request some json like this
{
"Code": "abcde"
}
My request model automatically set its Code value to 0. And 0 is a valid value
Is there anyway to handle this case? so I can return some warning / error before it get processed in my controller?
Thanks
The reason why you are getting 0 is because string can't be mapped to int, so it just leaves a default value (which is zero). To validate users input you can use attributes.
Try something like this:
public class RequestModel
{
[Range(1, int.MaxValue, ErrorMessage = "*Your warning*")]
public int Code { set; get; }
}
Than in your controller you can check if model state is valid and respond properly. Basically in the beginning of your method in controller you should write something like this:
if(!ModelState.IsValid)
{
// Return errors for example
}
else
{
// Something else
}
There is a more elegant way to do such kind of validation. You can simply make your field nullable (int?) and delegate all validation work to fluent validator. It will run the validation before you'll get in controller and automatically return BadRequest if something goes wrong. Example of fluent validator for your model:
public class RequestModelValidator : AbstractValidator<RequestModel>
{
public RequestModelValidator()
{
RuleFor(m => m.Code).NotNull().WithMessage("*Your msg*");
}
}
Change your model to this:
public class RequestModel
{
[Required]
public int? Code { set; get; }
}
Code will default to null when a value that can't be parsed is received. If you perform model validation it will make sure that Code is not null.
If you send a string, in a property that should naturally receive an integer, the complete object will arrive null in its controller, there will be no conversion of the string to zero, as you mentioned.
A practical example:
Class model:
public class RequestModel
{
public int Code { set; get; }
}
Your controller:
using Microsoft.AspNetCore.Mvc;
public class Test: Controller
{
[HttpPost("/test")]
public IActionResult Test([FromBody] RequestModel requestModel)
{
if (requestModel == null)
{
return BadRequest("Invalid request");
}
return Ok(requestModel);
}
}

Make a complex typed property required in an MVC4 form

I can't figure out how to "customize" the rules for the [Required] attribute when I stick it to a custom typed property. Code looks like this:
public class MyProp
{
public Guid Id {get;set;}
public string Target {get;set;}
}
public class MyType : IValidatableObject
{
public string Name {get;set;}
public MyProp Value {get;set;}
private MyType()
{
this.Name = string.Empty;
this.Value = new MyProp { Id = Guid.Empty, Target = string.Empty };
}
public MyType(Guid id) : this()
{
this.Value.Id = id;
// Fill rest of data through magic
}
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if(this.Value.Id == Guid.Empty)
yield return new ValidationResult("You must fill the property");
}
}
This model shows up in forms (through its own EditorTemplate) as a textbox with a button which allows for selection from a list (the backing data is a Dynamics CRM 2011 Environment, and this model is actually aimed to represent a lookup attribute).
public class MyModel
{
// Many props
[Required] // This one is enforced correctly
public string MyString {get;set;}
[Required] // This one isn't
public MyType MyData {get;set;}
public MyModel() { this.MyData = new MyType(); }
}
The resulting view shows the field (empty, of course). User can only input data by clicking the field and choosing from a list (a jquery dialog takes care of this, and it already works).
The IValidatableObject interface sounds promising but the code doesn't seem to be ever invoked.
In the controller, I'm simply doing
[HttpPost]
public ActionResult MyAction(FormCollection data)
{
if (!ModelState.IsValid) return View();
// magic: handle data
}
What am I missing ? I probably misunderstood the IValidatableObject interface usage ?
Your controller action should take the view model as parameter instead of weakly typed FormCollection which has absolutely no relation to your model (and its validation rules):
[HttpPost]
public ActionResult MyAction(MyModel model)
{
if (!ModelState.IsValid)
{
return View();
}
// magic: handle model
}
Now the default model binder is going to be invoked in order to bind the view model from the request and evaluate any validation logic you might have in this model.
How do you expect from your code, ASP.NET MVC, to ever know that you are working with this MyModel class? You absolutely never used it in your POST action, so you cannot expect to have any validation on it.
Once you start using view models you should forget about weakly typed collections such as FormCollection and start working with those view models.

Only Edit some fields and "SaveChanges" fill the missing fields with default values

I have a model class,
public class MyModel
{
public int A { get; set; }
public int B { get; set; }
public int C { get; set; }
public DateTime D { get; set; }
}
In the edit screen, I only need to edit `A and B. And the following will overwrite C and D with default values (0 and '0001-01-01'. How to keep the existed values (in the database table) for C and D besides include them in the view and hide them?
[HttpPost]
public ActionResult Edit(MyModel myModel)
{
if (ModelState.IsValid)
{
_db_Entry(myModel).State = EntityState.Modified;
_db.SaveChanges();
}
return View(myModel);
}
I'm not sure what your model really looks like, but in your controller, I typically do this:
[HttpPost]
public ActionResult Edit(MyModel myModel)
{
if (ModelState.IsValid)
{
var existEntry = _db.YourEntity.firstOrDefault(o => A == o.A);
if(existEntry != null){
existEntry.A = myModel.A;
existEntry.B = myModel.B;
_db.SaveChanges();
}
}
return View(myModel);
}
Of course, this depends really on your model specifics to get the existing entry, and perhaps you don't like this method in which case the other answer might be more suited to your needs.
EDIT: Basically, the way I usually handle this is to get the existing object from the db, update that, and then submit the changes, thus updating only the properties you care about and leaving the rest as they are.
Make a view model that has A and B properties.
public class ViewModel
{
public int A {get;set;}
public int B {get;set;}
}
Then make this as your parameter in your controller then map it to your model class.
You can use UpdateModel (or TryUpdateModel). It updates an existing entity, but only for properties it can find in the value provider; other properties remain intact.
Typically you use it like to this:
[HttpPost]
public ActionResult Edit(int id, TModel model, string returnUrl)
{
// Invalid model; redisplay view
if (!ModelState.IsValid) return View();
var entity = db.Entity.Find(id);
// Entity not found; return 404
if (entity == null) return HttpNotFound();
// Everything OK; update entity and redirect back
UpdateModel(entity);
db.SaveChanges();
return Redirect(returnUrl);
}

asp.net mvc period in POST parameter

i konw that you can have a period in a querystring parameter, but you cant specify a period in variable names in .net.
The following code obviously does not work, but my external system uses the period in the names. Is there a way to do this?
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Index(string hub.mode)
{
return View();
}
You could read the value directly from the Request hash:
[HttpPost]
public ActionResult Index()
{
string hubMode = Request["hub.mode"];
return View();
}
or using an intermediary class:
public class Hub
{
public string Mode { get; set; }
}
[HttpPost]
public ActionResult Index(Hub hub)
{
string hubMode = hub.Mode;
return View();
}
As you will notice . has special meaning for the ASP.NET MVC default model binder.

Categories