Asp.net MVC ModelState.Isvalid returning false for Id

7.9k views Asked by At

I'm watching this ASP.NET MVC course. I have a customer model with these following attribute.

public class Customer {
    public int Id { get; set; }
    [Required]
    [StringLength(255)]        
    public string Name { get; set; }

    [Display(Name = "Date of Birth")]
    public DateTime? DateOfBirth { get; set; }

    public bool IsSubscribedToNewsLetter { get; set; }


    public MembershipType MembershipType { get; set; }

    [Display(Name="Membership Type")]
    public byte? MembershipTypeId { get; set; }
}

Note thate the Id has no Required data annotation. But in my database, the Id is primary key and Identity is true for the column.

There is a ViewModel consisting Customer and MembershipType models.

    public class CustomerFormViewModel {
        public IEnumerable<MembershipType> MembershipTypes { get; set; }
        public Customer Customer { get; set; }
    }

I have a View that creates new Customer with Name, DateOfBirth, MembershipType and IsSubscribedToNewsLetter fields. It takes the CustomerFormViewModel.

@using Vidly.Models
@model Vidly.ViewModel.CustomerFormViewModel
@{
    ViewBag.Title = "New";
    Layout = "~/Views/Shared/_Layout.cshtml";
}

<h2>@ViewBag.Message</h2>
@using (Html.BeginForm("Save", "Customer")) {
    <div class="form-group">
        @Html.LabelFor(m => m.Customer.Name,new{@class="control-label"})
        @Html.TextBoxFor(m => m.Customer.Name, new { @class = "form-control" })
        @Html.ValidationMessageFor(m=>m.Customer.Name)
    </div>
    <div class="form-group">
        @Html.LabelFor(m => m.Customer.DateOfBirth, new { @class = "control-label"})
        @Html.TextBoxFor(m => m.Customer.DateOfBirth,"{0:d MMM yyyy}", new { @class = "form-control" })
    </div>

    <div class="form-group">
        @Html.LabelFor(m => m.Customer.MembershipTypeId, new { @class = "control-label"})
        @Html.DropDownListFor(m => m.Customer.MembershipTypeId,new SelectList(Model.MembershipTypes,"Id","Name"), "Select Membership Type", new { @class = "form-control" })
    
    </div>
    <div class="checkbox">
        <label>
            @Html.CheckBoxFor(m => m.Customer.IsSubscribedToNewsLetter) Subscribed To Newsletter?
        </label>

    </div>

    @Html.HiddenFor(m=>m.Customer.Id)
    <button type="submit" class="btn btn-primary">Save</button>
}

Here is my Save controller:

    public ActionResult Save(Customer customer) {
        if (!ModelState.IsValid) {
            var viewModel = new CustomerFormViewModel {
                Customer = customer,
                MembershipTypes = _context.MembershipTypes.ToList()
            };
            return View("CustomerForm", viewModel);
        }
        if (customer.Id == 0 || customer.Id==null) {
            _context.Customers.Add(customer);
        }
        else {
            var CustomerInDb = _context.Customers.Single(c => c.Id == customer.Id);
            CustomerInDb.Name = customer.Name;
            CustomerInDb.DateOfBirth = customer.DateOfBirth;
            CustomerInDb.IsSubscribedToNewsLetter = customer.IsSubscribedToNewsLetter;
            CustomerInDb.MembershipTypeId = customer.MembershipTypeId;
        }
        _context.SaveChanges();
        return RedirectToAction("Index", "Customer");
    }

When I fill the CustomerForm view and click the submit button, the ModelState.Isvalid() method always comes false; resulting the first if statement of the Save method true. So I can't store any new customer.

I tried to debug the application by putting breakpoint on if (!ModelState.IsValid) and saw that the Id field is creating a error(saying "The Id field is required"). Why is it saying that Id is required when it isn't? Does the ModelState.IsValid method check the model at database level? I don't think so.

If I change the Customer model's Id property like this:public int? Id { get; set; } and change the if statement by this,if ((!ModelState.IsValid) && (customer.Id==null) ) the application works fine.

Is there any other solution of this Id problem?

9

There are 9 answers

2
progrAmmar On

Perhaps the problem is that the Id field is marked as an int and not int?. Putting a variable as int the Model automatically assumes there's going to be a value for this property since it's not marked nullable.

Try marking the Id Property is int? and see if the results are what you expect or not.

2
corix010 On

This is just a rough draft as I don't have access to VS right now. Anyway, modify your Save action like so:

public ActionResult Save(CustomerFormViewModel customerVM) {
    if (!ModelState.IsValid) {
        return View(customerVM);
    }
    if (customer.Id == 0) {
        _context.Customers.Add(customerVM.Customer);
    }
    else {
        var CustomerInDb = _context.Customers.Find(customerVM.Customer.Id);
        CustomerInDb.Name = customer.Name;
        CustomerInDb.DateOfBirth = customer.DateOfBirth;
        CustomerInDb.IsSubscribedToNewsLetter = customer.IsSubscribedToNewsLetter;
        CustomerInDb.MembershipTypeId = customer.MembershipTypeId;
    }
    _context.SaveChanges();
    return RedirectToAction("Index", "Customer");
}

Oh and you can remove the following from the view since this is for create page:

    @Html.HiddenFor(m=>m.Customer.Id)
2
Ahashan Alam Sojib On

After seeing this question, I made a workaround of my problem. I just disabled my Id error in ModelState at the very beginning of Save action.

public ActionResult Save(Customer customer) {
    ModelState["customer.Id"].Errors.Clear();
    if ((!ModelState.IsValid) ) {
        var viewModel = new CustomerFormViewModel {
            Customer = customer,
            MembershipTypes = _context.MembershipTypes.ToList()
        };
        return View("CustomerForm", viewModel);
    }
    if (customer.Id == 0) {
        _context.Customers.Add(customer);
    }
    else {
        var CustomerInDb = _context.Customers.Single(c => c.Id == customer.Id);
        CustomerInDb.Name = customer.Name;
        CustomerInDb.DateOfBirth = customer.DateOfBirth;
        CustomerInDb.IsSubscribedToNewsLetter = customer.IsSubscribedToNewsLetter;
        CustomerInDb.MembershipTypeId = customer.MembershipTypeId;
    }
    _context.SaveChanges();
    return RedirectToAction("Index", "Customer");
}

Now my Application works fine.

1
kunal gaikwad On

hey change Id to CustomerId in model class. i think just 'Id' may be treated as Primary Key of that model class.

0
zzzz On

we are doing the same course and i ran into the exact same problem lol. i found a pretty nice workaround in my opinion.

just add these lines of code in the CustomerFormView. instead of @Html.HiddenFor(m=>m.Customer.Id)

Add:

 {
        if (Model.Customer == null)
        {
            <input data-val="true" data-val-number="The field Id must be a number." data-val-required="The Id field is required." id="Customer_Id" name="Customer.Id" type="hidden" value="0" />
        }
        else
        {
            @Html.HiddenFor(m => m.Customer.Id)
        }
    }

for some reason i saw that when i try to add a new customer , the value of id is an empty string instead of zero. therefore i changed it to zero manually in case the Customer object is null (which will always be the case when adding a new customer.) and it works fine for me.

let me know if you think this solution is problematic..

BTW Regarding your question : "the Id field is creating a error(saying "The Id field is required"). Why is it saying that Id is required when it isn't?"

Int data type is non nullable therefore it is implicitly required..same as the MembershipId (byte data type that doesnt have the [Required] annotation.)

1
Darthchai On

I watched the same course and I am guessing the author updated since you watched it, as he demonstrated this exact type of issue. The issue is that when returning the View Model to the View on the New Action, the Customer property is not initialized so the Id is null hence the ModelState failure when trying to Save

Just change as below, so that when setting the viewModel, you initialize the Customer and the Id is then 0:

 public ActionResult New()
    {
        var memberShipTypes = _context.MembershipTypes.ToList();

        var viewModel = new CustomerViewModel
        {
            Customer = new Customer(),
            MembershipTypes = memberShipTypes
        };

        return View("CustomerForm", viewModel);
    }
0
Aravindhan R On

I am also going through this course. i have got a same issue. Add this line in the customerviewform

if (Model.Customers !=null)
    {
        @Html.HiddenFor(m => m.Customers.Id)
    }

Why i have add because while hiddenfor which it is used for editing purpose if you remove also it will be problem. So add this line it will we be work and in customer model add public int? Membershiptype. And another thing while adding a new customer if you got error at dropdownlist then add this line before validation area return

 customer.MembershipTypes = _Context.MembershipTypeTableset.ToList(); add this line before     View("CustomerForm", viewModel)
0
Vinay Maurya On

This example is taken from Mosh Hamedani's MVC 5 course. He explained the Customer Id issue in the chapter 55. This can be resolved by passing a new customer() object in New method while creating a CustomerFormViewModel.

0
Don Pickard On

Had a heck of a time with this, and created a workaround, but it seems that Mosh addresses this in a later section where he sets up the Movie Form.

https://codewithmosh.com/courses/222293/lectures/3684111

The short answer is to add @Html.Hidden("Movie.Id", (Model.Movie != null) ? Model.Movie.Id : 0) to the MovieForm.cshtml.

He then describes a way to avoid hard-coding "Movie.Id" into the view (see https://github.com/mosh-hamedani/vidly-mvc-5/commit/e5b994581931a079ad87418ddcf9338e808bd821#diff-e94a8dc96403203b00e58238bb80101c )