2

I am having an issue with my post-back from a DropDownList. I am trying to create a page which will allow users to create a new skillset, but the skillset links with skill categories, so I am using a DropDownList so that the user can select which category the skillset belongs to, but I am receiving this exception:

An exception of type 'System.ArgumentNullException' occurred in System.Web.Mvc.dll but was not handled in user code

Additional information: Value cannot be null.

This is my controllers for create:

// GET: SkillSets/Create
public ActionResult Create()
{
    var categoryIDs = db.Categories.Where(c => c.Active == 1).Select(x => x.IDCategory).Distinct();
    List<SelectListItem> items = new List<SelectListItem>();
    foreach (var t in categoryIDs)
    {
        SelectListItem s = new SelectListItem();
        s.Text = t.ToString();//db.Categories.Where(c => c.IDCategory == t & c.Active == 1).Select(x => x.Category + ": " + x.C_Role).Single();
        s.Value = t.ToString();
        items.Add(s);
    }
    ViewBag.Campaign = items;
    return View();
}

// POST: SkillSets/Create
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "IDSkillset,IDCategory,Product,P_Version,Notes")] Models.SkillSetsModel ss)
{
    try
    {
        if (ModelState.IsValid)
        {
            db.SkillSets.Add(ss);
            db.SaveChanges();
            return RedirectToAction("Index");
        }
        return RedirectToAction("Index");
    }
    catch
    {
        return View();
    }
}

And this is the DropDownList:

<div class="form-group">
    @Html.LabelFor(model => model.IDCategory, htmlAttributes: new { @class = "control-label col-md-2" })
    <div class="col-md-10">
        @Html.DropDownList("Campaign", new SelectList(ViewBag.Campaign, "Value", "Text"), new { htmlAttributes = new { @class = "form-control" } })
        @Html.ValidationMessageFor(model => model.IDCategory, "", new { @class = "text-danger" })
    </div>
</div>

Here is the model:

namespace ITSSkillsDatabase.Models
{
    [Table("SkillSets")]
    public class SkillSetsModel
    {
        [Key]
        public int IDSkillset { get; set; }

        public int IDCategory { get; set; }

        public string Product { get; set; }

        [Display(Name = "Product Version")]
        public string P_Version { get; set; }

        public string Notes { get; set; }

        public virtual ICollection<PersonSkillsModel> PersonSkills { get; set; }
    }
}

The DropDownList works for the Get part of the create method, there just seems to be issues with either the view or the Post method, as the DropDownList is being populated. enter image description here

Community
  • 1
  • 1
TheHornyDonut
  • 127
  • 1
  • 2
  • 12
  • Where are you getting that error? is it in the post or the get of the page? – Canvas Sep 15 '15 at 12:16
  • What is your model? Why do you have a dropdownlist binding to a property `Campaign` which does not even appear to exist (and is excluded by your `[Bind"` attribute anyway). Why do you create a `SelectList` in the controller and then create another `SelectList` from it in the view? –  Sep 15 '15 at 12:17
  • @Canvas The Post as the error only occurs when I try to create the skillset, the DropDownList is also fully populated – TheHornyDonut Sep 15 '15 at 12:19
  • @StephenMuecke I editted to include the model, and to be honest, I got that from elsewhere and it worked for the get method, I am fairly new to MVC. – TheHornyDonut Sep 15 '15 at 12:21
  • If I were you don't use the `Bind` to catch what values you want to save. You have a viewmodel so just take your view model as your post parameter and then take a look at what is null in there using a break point. `public ActionResult Create(SkillSetsModel model)` – Canvas Sep 15 '15 at 12:22
  • Based on your model it needs to be `@Html.DropDownListFor(m => m.IDCategory, (IEnumerable)ViewBag.Campaign)`. But you really need to do to the MVC site and work through a few tutorials –  Sep 15 '15 at 12:24
  • @Canvas done that seems to be the ICollection in the model causing the issue. http://i.imgur.com/Zyk0h4C.png – TheHornyDonut Sep 15 '15 at 12:31
  • @StephenMuecke That fixed the issue, thanks! And yeah I completely agree, I'm going to start running through some at home. – TheHornyDonut Sep 15 '15 at 12:37
  • @TheHornyDonut, Almost every line of your code either does not make sense or is plain bad code and can cause errors. I'll add an answer later to explain some of them. –  Sep 15 '15 at 12:40

2 Answers2

2

The reason for the error is that you binding the dropdownlist to a property named Campaign but you model does not contain a property with that name. Based on the associated label and validation message you want to assign the selected value to property IDCategory so in the view it needs to be

@Html.DropDownListFor(m => m.IDCategory, (IEnumerable<SelectListItem>)ViewBag.Campaign)

Note that the ViewBag property is already IEnumerable<SelectListItem> so using new SelectList(ViewBag.Campaign, "Value", "Text") is just pointless extra overhead (you constructing a new IEnumerable<SelectListItem> from the exiting one)

However your code has numerous other errors.

In the GET method, your selecting just the ID property of Category and then using .Distinct(). If your ID properties are not already unique, it suggests a problem with you database structure. Your also only displaying the ID value in the view which is unlikely to make sense to a user. Assuming Category has a property (say) string Name to describe the Category, you can simplify your code to

public ActionResult Create()
{
  ViewBag.Campaign = db.Categories.Where(c => c.Active == 1).Select(c => new SelectListItem
  {
    Value = c.IDCategory.ToString(),
    Text = c.Name
  });
  return View(new SkillSetsModel()); // always return a model
}

Note the fact that the IsActive property is an int rather than a bool also suggests problems with your database/model design.

In the POST method, you currently redirect to another view if ModelState is not valid, meaning the user has no idea that the data they just filled in has not been saved. Then you return the view in the catch block without even indicating what the error is, further confusing the user.

Remove the try/catch blocks and let the framework handle this until you understand more about handling errors (a default error page will be displayed) and change the code to

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(SkillSetsModel model)
{
  if (!ModelState.IsValid)
  {
    // Return the view so the user can correct errors
    ViewBag.Campaign = ... // as per the GET method
    return View(model);
  }
  db.SkillSets.Add(ss);
  db.SaveChanges();
  return RedirectToAction("Index");
}

Finally I would recommend using view models that represent only the data you want to display/edit in the view (refer What is ViewModel in MVC?). For your Create view, it would not include properties for IDSkillset (the object does not exist in the database yet) or PersonSkills, but would include a property IEnumerable<SelectListItem> Campaign (although CategoryList seems a more appropriate name) along with display and validation attributes (a [Display] attribute in a data model is not appropriate since it's view specific)

Community
  • 1
  • 1
0

Changing:

 @Html.DropDownList("Campaign", new SelectList(ViewBag.Campaign, "Value", "Text"), new { htmlAttributes = new { @class = "form-control" } })

to

@Html.DropDownListFor(m => m.IDCategory, (IEnumerable<SelectListItem>)ViewBag.Campaign)

fixed the issue, thanks @StephenMuecke!

TheHornyDonut
  • 127
  • 1
  • 2
  • 12
  • 1
    btw, this would have worked as `@Html.DropDownList("IDCategory", new SelectList(ViewBag.Campaign, "Value", "Text"), new { htmlAttributes = new { @class = "form-control" } })`. When you're binding to a model on post your fields must match the property names – Eonasdan Sep 15 '15 at 13:25