Mike Marks
Mike Marks

Reputation: 10129

MVC4 Validation DataAnnotations not working (not validating)

I have a pretty complex model and a view (below). I only have ONE of my fields as Required (Key Active Mg). When I mark this [Required(ErrorMessage="Key Active Mg is required")] and set the @Html.ValidationMessageFor for that field, my app will let me enter nothing and click Save. Of course, Model.IsValid will return false. It doesn't come back and outline the field in red indicating it's required. Does anyone know why?

My model:

    public class RawValues
    {
        [Key]
        public int Pk { get; set; }

        public int? FillerFk { get; set; }
        [Display(Name = "Filler")]
        [ForeignKey("FillerFk")]
        public virtual Filler Filler { get; set; }

        public int? CapsuleFk { get; set; }
        [Display(Name = "Capsule")]
        [ForeignKey("CapsuleFk")]
        public virtual Capsule Capsule { get; set; }

        public int? KeyActiveFk { get; set; }
        [Display(Name = "Key Active")]
        [ForeignKey("KeyActiveFk")]
        public virtual KeyActive KeyActive { get; set; }

        [Display(Name="API #1")]
        public int? Active1 { get; set; }

        [Display(Name = "API #2")]
        public int? Active2 { get; set; }

        [Display(Name = "API #3")]
        public int? Active3 { get; set; }

        [Display(Name = "API #4")]
        public int? Active4 { get; set; }

        [Display(Name = "API #5")]
        public int? Active5 { get; set; }

        [Display(Name = "Key Active Mg")]
        [Required(ErrorMessage="Key Active Mg is required.")]
        public int KeyActiveMg { get; set; }

        [Display(Name = "E4M")]
        public bool E4M { get; set; }

        [Display(Name = "K100M")]
        public bool K100M { get; set; }

        public int TimeReleaseFillerFk { get; set; }
        public int NumberCapsules { get; set; }

        public string CreatedBy { get; set; }
        public DateTime CreatedDate { get; set; }
    }

My View:

@model CapWorx.QuikCap.Models.RawValues

@* This partial view defines form fields that will appear when creating and editing entities *@

<div data-role="fieldcontain">
    @Html.LabelFor(model => model.Capsule, new { @class = "ui-input-text" })
    @Html.DropDownListFor(m => m.Capsule.Pk, new SelectList(ViewBag.Capsules, "Pk", "Name", "Pk"))
</div>
<div data-role="fieldcontain">
    @Html.LabelFor(model => model.Active1)
    @Html.EditorFor(model => model.Active1)
</div>
<div data-role="fieldcontain">
    @Html.LabelFor(model => model.Active2)
    @Html.EditorFor(model => model.Active2)
</div>
<div data-role="fieldcontain">
    @Html.LabelFor(model => model.Active3)
    @Html.EditorFor(model => model.Active3)
</div>
<div data-role="fieldcontain">
    @Html.LabelFor(model => model.Active4)
    @Html.EditorFor(model => model.Active4)
</div>
<div data-role="fieldcontain">
    @Html.LabelFor(model => model.Active5)
    @Html.EditorFor(model => model.Active5)
</div>
<div data-role="fieldcontain">
    @Html.LabelFor(model => model.KeyActive, new { @class = "ui-input-text" })
    @Html.DropDownListFor(m => m.KeyActive.Pk, new SelectList(ViewBag.KeyActives, "Pk", "Name", "Pk"))
</div>
<div data-role="fieldcontain">
    @Html.LabelFor(model => model.KeyActiveMg)
    @Html.EditorFor(model => model.KeyActiveMg)
    @Html.ValidationMessageFor(model => model.KeyActiveMg)
</div>
<div data-role="fieldcontain">
    @Html.LabelFor(model => model.E4M)
    @Html.DropDownListFor(x => x.E4M,
                              new[] { 
                                    new SelectListItem() { Text = "Off", Value = "False", Selected = true }, 
                                    new SelectListItem() { Text = "On", Value = "True" } },
                              new { data_role = "slider" })
</div>
<div data-role="fieldcontain">
    @Html.LabelFor(model => model.K100M)
    @Html.DropDownListFor(x => x.K100M,
                              new[] { 
                                    new SelectListItem() { Text = "Off", Value = "False", Selected = true }, 
                                    new SelectListItem() { Text = "On", Value = "True" } },
                              new { data_role = "slider" })
</div>
<div data-role="fieldcontain">
    @Html.LabelFor(model => model.Filler, new { @class = "ui-input-text" })
    @Html.DropDownListFor(m => m.Filler.Pk, new SelectList(ViewBag.Fillers, "Pk", "Name", "Pk"))
    @Html.ValidationMessage("FillerName", "Filler is required")
</div>

My Controller:

[HttpPost]
        public ActionResult Create(RawValues rawvalues)
        {
            rawvalues.CreatedBy = User.Identity.Name;
            rawvalues.CreatedDate = DateTime.Now;
            rawvalues.TimeReleaseFillerFk = Helpers.OperationContext.GetTimeReleaseFillerFk(rawvalues.E4M, rawvalues.K100M);

            rawvalues.CapsuleFk = rawvalues.Capsule.Pk;
            rawvalues.FillerFk = rawvalues.Filler.Pk;
            rawvalues.KeyActiveFk = rawvalues.KeyActive.Pk;

            rawvalues.Filler = Helpers.OperationContext.GetFiller(rawvalues.Filler.Pk);
            rawvalues.Capsule = Helpers.OperationContext.GetCapsule(rawvalues.Capsule.Pk);
            rawvalues.KeyActive = Helpers.OperationContext.GetKeyActive(rawvalues.KeyActive.Pk);

            rawvalues.NumberCapsules = 100;

            var errors = ModelState.Values.SelectMany(v => v.Errors);

            if (ModelState.IsValid) {
                rawvaluesRepository.InsertOrUpdate(rawvalues);
                rawvaluesRepository.Save();
                List<Results> results = Helpers.OperationContext.CallCalculate(rawvalues);
                return View("Results", results);
            } else {
                ViewBag.Error = "Model State was not valid.";
                return View("Error");
            }
        }

My screenshot:

enter image description here

UPDATE

I've updated my controller code to be the following:

    if (ModelState.IsValid) {
        rawvaluesRepository.InsertOrUpdate(rawvalues);
        rawvaluesRepository.Save();
        List<Results> results = Helpers.OperationContext.CallCalculate(rawvalues);
        return View("Results", results);
    } else {
        ViewBag.Capsules = Helpers.OperationContext.GetCapsules();
        ViewBag.Fillers = Helpers.OperationContext.GetFillers();
        ViewBag.KeyActives = Helpers.OperationContext.GetKeyActives();
        return View();
    }

This resolves my issue. I needed to return the same View to display the errors on the screen. With DataAnnotations Validation, the form does in fact hit the HttpPost method of Create, and if there are errors (validation errors) the ModelState.IsValid will return false, in which case I need to return the same view. See screenshot below!

enter image description here

Upvotes: 0

Views: 14189

Answers (4)

Artless
Artless

Reputation: 4568

When your model is invalid, you don't actually return it with it's validation errors. Then, you do this:

@Html.ValidationMessageFor(model => model.KeyActiveMg)

but there is no Validation Message for anything. You returned a brand new view with a blank Model. Instead, you should return your invalid model to the view, like such:

return View(rawvalues);

UPDATE

Your controller should look like this:

public ActionResult Create()
{
    return View(new RawValues());
}

[HttpPost]
public ActionResult Create(RawValues model)
{
    if (ModelState.IsValid)
    {
        // Do stuff
    }
    else
    {
        return View(model);
    }
}

Your view seems fine, so it should work from here.

If you're talking about client side validation, you need to reference the necessary JS files. They should be already preconfigured in your Bundle config. You just need to add to your view:

@Scripts.Render("~/bundles/jquery")
@Scripts.Render("~/bundles/jqueryui")
@Scripts.Render("~/bundles/jqueryval")

Just to make sure, your BundleConfig class that sits in the App_Start folder should have these:

bundles.Add(new ScriptBundle("~/bundles/jquery").Include(
            "~/Scripts/jquery-{version}.js"));

bundles.Add(new ScriptBundle("~/bundles/jqueryui").Include(
            "~/Scripts/jquery-ui-{version}.js"));

bundles.Add(new ScriptBundle("~/bundles/jqueryval").Include(
            "~/Scripts/jquery.unobtrusive*",
            "~/Scripts/jquery.validate*"));

Upvotes: 0

ssilas777
ssilas777

Reputation: 9804

I think you need this

[HttpPost]
public ActionResultCreate(RawValues model)
{
 ....

 if (!ModelState.IsValid)
 {
    ModelState.AddModelError("KeyActiveMg", "KeyActiveMg field is required");
    var model = new GetModel();
    return View("Results", model);
 }  

    rawvaluesRepository.InsertOrUpdate(rawvalues);
    rawvaluesRepository.Save();
    List<Results> results = Helpers.OperationContext.CallCalculate(rawvalues);
    return View("Results", results);
}

Upvotes: -1

Shyju
Shyju

Reputation: 218702

Looks like you are returning another view (Error) if ModelState.IsValid is false. You should return the posted viewmodel to the same create view.

public ActionResult Create(RawValues model)
{
  if(ModelState.IsValid)
  {
     //everything is good. Lets save and redirect
  }
  return View(model);
}

Upvotes: 5

Matt Klinker
Matt Klinker

Reputation: 773

My guess is that you are not including 'jquery.unobtrusive-ajax.min.js' and/or 'jquery.validate.unobtrusive.min.js'. I believe these are required to interpret the metadata added by DataAnnotations and supply the validation methods your looking for.

Upvotes: 0

Related Questions