ModelState is coming up as invalid?

14,251

Solution 1

When you cannot quickly deduce why your ModelState validation fails, it's often helpful to quickly iterate over the errors.

foreach (ModelState state in ModelState.Values.Where(x => x.Errors.Count > 0)) { }

Alternatively you can pull out errors directly.

var allErrors = ModelState.Values.SelectMany(x => x.Errors);

Keep in mind that the ModelState is constructed BEFORE the body of your Action is executed. As a result, IsValid will already be set, regardless of how you set your model's properties once you are inside of the Controller Action.

If you want the flexibility to manually set properties and then re-evalute the validity of the object, you can manually rerun the validation inside of your Action after setting the properties. As noted in the comments, you should clear your ModelState before attempting to revalidate.

ModelState.Clear();
ValidateModel(model);

try
{
    if (ModelState.IsValid)
    {
        db.INV_Models.Add(model);
        db.SaveChangesAsync();
    }
}
...

As an aside, if the model is still not valid ValidateModel(model) will throw an exception. If you'd like to prevent that, use TryValidateModel, which returns true/false instead:

protected internal bool TryValidateModel(Object model)

Solution 2

The model state is calculated when the binding from your post data to model is done. The ModelState.IsValid property only tells you if there are some errors in ModelState.Errors.

When you set your created date you will need to remove the error related to it from ModelState.Errors

Share:
14,251
Analytic Lunatic
Author by

Analytic Lunatic

Software Developer by Day, Web Designer by Night.

Updated on June 05, 2022

Comments

  • Analytic Lunatic
    Analytic Lunatic almost 2 years

    I'm working on a MVC5 Code-First application.

    On one Model's Edit() view I have included [Create] buttons to add new values to other models from within the Edit() view and then repopulate the new value within DropDownFors() on the Edit().

    For this first attempt, I am passing a model_description via AJAX to my controller method createNewModel():

    [HttpPost]
    public JsonResult createNewModel(INV_Models model)
    {
        // model.model_description is passed in via AJAX -- Ex. 411
    
        model.created_date = DateTime.Now;
        model.created_by = System.Environment.UserName;
        model.modified_date = DateTime.Now;
        model.modified_by = System.Environment.UserName;
    
        // Set ID
        int lastModelId = db.INV_Models.Max(mdl => mdl.Id);
        model.Id = lastModelId+1;
    
        //if (ModelState.IsValid == false && model.Id > 0)
        //{
        //    ModelState.Clear();
        //}
    
        // Attempt to RE-Validate [model], still comes back "Invalid"
        TryValidateModel(model);
    
        // Store all errors relating to the ModelState.
        var allErrors = ModelState.Values.SelectMany(x => x.Errors);
    
        // I set a watch on [allErrors] and by drilling down into
        // [allErrors]=>[Results View]=>[0]=>[ErrorMessage] I get
        // "The created_by filed is required", which I'm setting....?
    
        try
        {
            if (ModelState.IsValid)
            {
                db.INV_Models.Add(model);
                db.SaveChangesAsync();
            }
    
        }
        catch (Exception ex)
        {
            Elmah.ErrorSignal.FromCurrentContext().Raise(ex);
        }
    
        return Json(
            new { ID = model.Id, Text = model.model_description },
            JsonRequestBehavior.AllowGet);
    }
    

    What I cannot figure out is why my ModelState is coming up as Invalid?

    All properties are being specified before the ModelState check; the Model is defined as follows:

    public class INV_Models
    {
        public int Id { get; set; }
    
        [Required(ErrorMessage = "Please enter a Model Description.")]
        public string model_description { get; set; }
    
        [Required]
        [DisplayFormat(DataFormatString = "{0:MM/dd/yyyy}")]
        public DateTime created_date { get; set; }
    
        [Required]
        public string created_by { get; set; }
    
        [DisplayFormat(DataFormatString = "{0:MM/dd/yyyy}")]
        public DateTime modified_date { get; set; }
    
        public string modified_by { get; set; }
    }
    

    EDIT:

    Added View code:

    Input Form:

            <span class="control-label col-md-2">Type:</span>
            <div class="col-md-4">
                @Html.DropDownListFor(model => model.Type_Id, (SelectList)ViewBag.Model_List, "<<< CREATE NEW >>>", htmlAttributes: new { @class = "form-control dropdown" })
                @Html.ValidationMessageFor(model => model.Type_Id, "", new { @class = "text-danger" })
            </div>
            <div class="col-md-1">
                <div class="btn-group">
                    <button type="button" class="btn btn-success" aria-expanded="false">CREATE NEW</button>
                </div>
            </div>
    

    SCRIPT:

            $('#submitNewModel').click(function () {
    
                var form = $(this).closest('form');
                var data = { model_description: document.getElementById('textNewModel').value };
    
                $.ajax({
                    type: "POST",
                    dataType: "JSON",
                    url: '@Url.Action("createNewModel", "INV_Assets")',
                    data: data,
                    success: function (resp) {
                        alert("SUCCESS!");
                        $('#selectModel').append($('<option></option>').val(resp.ID).text(resp.Text));
                        alert("ID: " + resp.ID + " // New Model: " + resp.Text); // RETURNING 'undefined'...?
                        form[0].reset();
                        $('#createModelFormContainer').hide();
                    },
                    error: function () {
                        alert("ERROR!");
                    }
                });
            });
    
  • Analytic Lunatic
    Analytic Lunatic about 9 years
    Thanks for replying David. I tried out ValidateModel() and TryValidateModel(), but both are still returning as Invalid. I then used your approach with allErrors and put a watch on allErrors. Drilling down into the [Results View]=>[0]=>[ErrorMessage] I get "The created_by field is required." I have confirmed though that I AM specifying the created_by value...?
  • Analytic Lunatic
    Analytic Lunatic about 9 years
    Thanks for the reply Mike. I didn't realize the ModelState was checked during the binding call (though that makes sense). I've confirmed using David's approach that the ModelState seems to think it's invalid due to the created_by field being required -- but I am specifying that value??
  • Mike Norgate
    Mike Norgate about 9 years
    @AnalyticLunatic As I mentioned in my answer you will need to remove the error from the ModelState. See the answer to this question stackoverflow.com/questions/2588588/…
  • Analytic Lunatic
    Analytic Lunatic about 9 years
    Sorry, I thought that TryValidateModel() and ValidateModel() would clear the error and do a new Validation? What exactly would I specify to Remove?
  • Mike Norgate
    Mike Norgate about 9 years
    @AnalyticLunatic you would need to specify the same key as in ModelState.Errors which I believe would be "created_date"
  • Analytic Lunatic
    Analytic Lunatic about 9 years
    Thanks Mike! I'm not sure which would be better, but I just got it to work by calling ModelState.Clear() before TryValidateModel(model).
  • Analytic Lunatic
    Analytic Lunatic about 9 years
    Correction, it worked once... and now nothing. It appears that if I walk through the code where it adds the new description to DB, it works. But if I let it run without a break point, the new description is added to dropdown but NOT the DB. I'm assuming maybe db.SaveChangesAsync() is not fully completing before the return occurs?
  • David L
    David L about 9 years
    @AnalyticLunatic model.created_by = System.Environment.UserName; Are you certain that this is not null? Remember, [Required] will return invalid if your value is null on a reference type.
  • Analytic Lunatic
    Analytic Lunatic about 9 years
    Correct, model.created_by = System.Environment.UserName is not null. I got it to work by doing a ModelState.Clear() BEFORE I attempt TryValidateModel(model). Once that occurs, everything is functioning as intended. I also had to change to db.SaveChanges() vs db.SaveChangesAsync() or else the value would only get added to DB if I had a Breakpoint. Without, the code would process to quickly and return before the DB add occurred.
  • David L
    David L about 9 years
    Interesting, I don't see anything in your code that would require db.SaveChangesAsync();. Regardless, I'll update my answer to reflect the need for ModelState.Clear();