Question

I try to add a new Country which has a link to continent. When I press the "Create" button, it doesn't add a new record. I debugged my project and I think it's because the ValidState is false. The reason because of this is that the property "Continent" is null, but the Continent_Id isn't. I have the same problem when I try to edit an existing Country. (I have populated my database with an SQL script in SQL Management Studio)

Can someone help me please?

Continent class:

public class Continent
{
    public int Id { get; set; }
    [Required, MaxLength(25)]
    public string Name { get; set; }

    //Navigation
    public virtual List<Country> Countries { get; set; }
}

Country class

public class Country
{
    public int Id { get; set; }
    [Required, MaxLength(25)]
    public string Name { get; set; }
    [MaxLength(5)]
    public string Abbreviation { get; set; }

    public int Continent_Id { get; set; }

    //Navigation
    [Required, ForeignKey("Continent_Id")]
    public virtual Continent Continent { get; set; }

}

Controller class ( create function )

  //
    // GET: /Countries/Create

    public ActionResult Create()
    {
        ViewBag.Continent_Id = new SelectList(db.Continents, "Id", "Name");
        return View();
    }

    //
    // POST: /Countries/Create

    [HttpPost]
    public ActionResult Create(Country country)
    {
       var errors = ModelState.Values.SelectMany(v => v.Errors); //to check the errors
        if (ModelState.IsValid)
        {
            db.Countries.Add(country);
            db.SaveChanges();
            return RedirectToAction("Index");
        }


        ViewBag.Continent_Id = new SelectList(db.Continents, "Id", "Name", country.Continent_Id);
        return View(country);
Was it helpful?

Solution 2

I fixed this issue by putting the Required validation off of Continent, and set it only at the Continent_Id. Now the ID property is required, but the Continent isn't.

public class Country
{
public int Id { get; set; }
[Required, MaxLength(25)]
public string Name { get; set; }
[MaxLength(5)]
public string Abbreviation { get; set; }

[Required] //added Required
public int Continent_Id { get; set; }

//Navigation
[ForeignKey("Continent_Id")] //removed Required
public virtual Continent Continent { get; set; }

}

Thanks for the responses !

OTHER TIPS

Just before the line if (ModelState.IsValid) put this ModelState.Remove("v_id"); Where v_id is your primarykey column name in your case

I'm not sure, but I believe your issue is timing. Model validation happens automatically during binding; at that time, the Continent property is null. You set the property later but the model state is not re-evaluated when you check IsValid. I see three options:

  • Quick and dirty: Take the Required validation off of Continent and validate Continent_Id instead, adding a check in the controller to ensure a valid Continent is retrieved from Find().
  • Most work: Create a custom model binder to actually use the Continent_Id to retrieve and populate the Continent. You are almost there on this one since having both Continent_Id and Continent as properties of Country is redundant and an opportunity for inconsistencies.
  • Probably best option: Make your controller accept a view model that only has the data you expect to come back from the form and populate a Country object from it.

The reason the ModelState isn't valid is because you have marked the Continent property as required but in i guess in your view you don't have form fields the will bind to some properties of the Continent object.

So either don't mark the Continent object as required or provide a hidden field with a name of Continent.Id or Continent.Name so that the model binder will populate the Continent property:

@Html.HiddenFor(m => m.Continent.Id)

But that will lead to the next problem: You habe marked the Name property of the Continent class as required so you will have to provide a form field for that property too.

The base problem is, that you try to reuse your repository classes as viewmodel classes. A better approach would be to use separate classes as viewmodels to pass your data between the controller and the view:

class CountryViewModel {
    public int Id { get; set; }
    [Required, MaxLength(25)]
    public string Name { get; set; }
    [MaxLength(5)]
    public string Abbreviation { get; set; }

    public int Continent_Id { get; set; }
}

To map between your Country and CountryViewModel object use a mapper like AutoMapper.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top