ModelState is coming up as invalid?

Analytic Lunatic picture Analytic Lunatic · Feb 13, 2015 · Viewed 13.5k times · Source

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!");
                }
            });
        });

Answer

David L picture David L · Feb 13, 2015

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)