9

I feel a bit stupid.

I'm trying to get a hang of MVC 4, using boxing as a functional example.

I have WeightCategories in the database (Heavyweights, etc), and Boxers.

Seem simple. The relation is a boxer has a current weight category, but when I edit, I want it to be able to change it with a drop down.

I understand how to do it if it's a list I've made myself in the code, but I have problem understanding how to "load" the list from the WeightCategory table and show it in the view/model of the boxer.

So, here is my code for the WeightCategory item:

[Table("WeightCategories")]
public class WeightCategory
{
    [Key]
    public int WeightCategoryId { get; set; }

    public WEIGHT_CATEGORIES WeightCategoryType { get; set; }

    [Display(Name = "Weight Category Name")]
    [Required]
    [MinLength(5)]
    public string Name { get; set; }
    [Display(Name = "Weight Limit In Pounds")]        
    public int? WeightLimit { get; set; }
}

Here is the code for the boxer item

[Table("Boxers")]
public class Boxer
{
    [Key]
    public int BoxerId { get; set; }

    public WeightCategory CurrentWeightCategory { get; set; }

    [Required]
    public string Name { get; set; }
    public int Wins { get; set; }
    public int Losses { get; set; }
    public int Draws { get; set; }
    public int Kayos { get; set; }
}

In the view, I'm really not sure how to tackle that, I'm pretty sure it's not automatic and I need to load the table somewhere in the controller maybe... I'm looking for best practice or something.

Something like that in the view at the end:

@Html.DropDownListFor(model => model.CurrentWeightCategory.WeightCategoryId,
                      new SelectList(Model.WeightCategories, "WeightCategoryId", "Name", 
                                     Model.WeightCategories.First().WeightCategoryId))
Ajeett
  • 814
  • 2
  • 7
  • 18
Nicolas Belley
  • 803
  • 2
  • 12
  • 29

1 Answers1

27

You could design a view model:

public class MyViewModel
{
    public Boxer Boxer { get; set; }
    public IEnumerable<SelectListItem> WeightCategories { get; set; }
}

and then have your controller action populate and pass this view model to the view:

public ActionResult Edit(int id)
{
    var model = new MyViewModel();
    using (var db = new SomeDataContext())
    {
        // Get the boxer you would like to edit from the database
        model.Boxer = db.Boxers.Single(x => x.BoxerId == id);

        // Here you are selecting all the available weight categroies
        // from the database and projecting them to the IEnumerable<SelectListItem>
        model.WeightCategories = db.WeightCategories.ToList().Select(x => new SelectListItem
        {
            Value = x.WeightCategoryId.ToString(),
            Text = x.Name
        })
    }
    return View(model);
}

and now your view becomes strongly typed to the view model:

@model MyViewModel
@Html.DropDownListFor(
    x => model.Boxer.CurrentWeightCategory.WeightCategoryId,
    Model.WeightCategories
)
Darin Dimitrov
  • 1,023,142
  • 271
  • 3,287
  • 2,928
  • 1
    Thanks! With your answer, you also helped me understand the MVVM vs MVC too. – Nicolas Belley Jan 13 '13 at 15:50
  • I replaced my @model variable with the viewModel and it fails with a runtime exception: `@Html.DropDownListFor( Line 27: model => model.Boxer.CurrentWeightCategory.WeightCategoryId, Line 28: Model.Boxer.WeightCategories)` runtime exception: 'System.Web.Mvc.HtmlHelper' does not contain a definition for 'DropDownListFor' and the best extension method overload ... has some invalid arguments – Nicolas Belley Jan 13 '13 at 16:07
  • Sorry, had problem with my posting, just added it! Seems the second argument is not good. For now it points to my WeightCategories list in the Boxer DO, is this ok? – Nicolas Belley Jan 13 '13 at 16:12
  • Your second argument is wrong. You have specified `Model.Boxer.WeightCategories`, but the correct one is `Model.WeightCategories`. Look at my answer once again. I have defined the `WeightCategories` as an `IEnumerable` property of the view model. – Darin Dimitrov Jan 13 '13 at 16:13
  • Yes, my bad! Now I'm just having an "Instance not set..." exception. Not sure why, probably the WeightCategories is not filled. – Nicolas Belley Jan 13 '13 at 16:18
  • Yes, probably it's not filled. You should populate it inside the controller action as shown in my answer. And when you submit the form you should also populate it the same way in your HttpPost action if you intend to redisplay the same view. – Darin Dimitrov Jan 13 '13 at 16:22
  • Ok, my bad again. I feel like my tiredness is not helping. Added the code in the "edit" but not in the "new"... Thanks again for your help. – Nicolas Belley Jan 13 '13 at 16:24
  • Well, if you're still up for help, now it did display, on the postback, the model was invalid (no real problem there, I'll see after why), and when it tried to show the view to I guess display the validation error, I had that exception on the line of the DropDownFor, and I'm a bit baffled: The ViewData item that has the key 'Boxer.CurrentWeightCategory.WeightCategoryId' is of type 'System.Int32' but must be of type 'IEnumerable'. – Nicolas Belley Jan 13 '13 at 16:45
  • You forgot to populate the `Model.WeightCategories` property in your HttpPost action. You should populate it the same way you did in the Get action if you intend to redisplay the same view. – Darin Dimitrov Jan 13 '13 at 16:47
  • Hmm, I'm wondering why I did not received the weightCategories on the postback, I do receive the ViewModel, but the weightCategories is null. I'll populate it, but wondering why it's not comming back. – Nicolas Belley Jan 13 '13 at 16:49
  • That's normal. When you submit a form containing a ` – Darin Dimitrov Jan 13 '13 at 16:50
  • True. I hope I'm not flooding you, but now the ModelState is always invalid. The name is required, it is infact filled in viewModel.Boxer.Name, but it seems the ModelState does'nt check there, I probably need to set somewhere that the Boxer is now in viewModel.Boxer and not just Boxer... – Nicolas Belley Jan 13 '13 at 16:55
  • You need to have an input field bound to this property: `@Html.EditorFor(x => x.Boxer.Name)`. Also make sure that your HttpPost action is taking the view model as argument and not only a Boxer. – Darin Dimitrov Jan 13 '13 at 16:56
  • I have a field bound like that: `@Html.EditorFor(model => model.Boxer.Name)` Edit: when it comes back to the page, the name I entered is in fact in the field. – Nicolas Belley Jan 13 '13 at 16:58
  • Great, then if your Name property is decorated with the `[Required]` attribute you will be able to successfully validate this property. – Darin Dimitrov Jan 13 '13 at 17:01
  • The property is already there, it is what is making the validation fail. If I don't enter a name and push OK, I've got the error message. If I fill it, then on the server side I do have it filled in the ViewModel.Boxer.Name, but it still fails. – Nicolas Belley Jan 14 '13 at 06:54
  • Oh this means that you have some other validation failing. Maybe you have some other properties on this Boxer class which are decorated with validation attributes and which you haven't supplied. – Darin Dimitrov Jan 14 '13 at 06:56
  • Well, when I check the list of objects, the one failing is the Name one, it's the only one on this object. I'll try to find if there's some code somewhere still referncing my old way of doing it. – Nicolas Belley Jan 14 '13 at 19:00
  • Ok, so here is the problem, the ViewModel Boxer, with the object WeightCategory, gets validated. So, since it's null (only the ID is filled in the Boxer.WeightCategory gets filled). Not sure where to go from here... – Nicolas Belley Jan 14 '13 at 19:10
  • When the httppost for the create happens, the WeightCategory does get associated in the boxer object, but only the Id and the enum, not the name. Not sure why. – Nicolas Belley Jan 14 '13 at 19:37