Search code examples
c#asp.net-mvcasp.net-mvc-5modelstate

ModelState is coming up as invalid?


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

Solution

  • You should not be using a hack like ModelState.Clear() nor is TryValidateModel(model); required. Your issue stems from the fact you have a [Required] attribute on both your created_date and created_by properties but you don't post back a value, so they are null and validation fails. If you were to post back a more complex model, then you would use a view model which did not even have properties for created_date and created_by (its a Create method, so they should not be set until you post back).

    In your case a view model is not necessary since your only posting back a single value ( for model-description) used to create a new INV_Models model.

    Change the 2nd line in the script to

    var data = { description: $('#textNewModel').val() };
    

    Change your post method to

    [HttpPost]
    public JsonResult createNewModel(string description)
    {
      // Initialize a new model and set its properties
      INV_Models model = new INV_Models()
      {
        model_description = description,
        created_date = DateTime.Now,
        created_by = System.Environment.UserName
      };
      // the model is valid (all 3 required properties have been set)
      try
      {
        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);
    }
    

    Side notes:

    1. I suggest modified_date be DateTime? (nullable in database also). You are creating a new object, and are setting the created_date and created_by properties, but setting modified_date and modified_by properties does not seem appropriate (it hasn't been modified yet).
    2. I suspect you don't really want to set created_by to System.Environment.UserName (it would be meaningless to have every record set to administrator or whatever UserName of the server returns. Instead you need to get users name from Identity or Membership whatever authorization system you are using.