0

So I thought this would be extremely simple, but I'm having a hard time figuring it out.

First, I'll go over what I have so far.

The RegisterViewModel:

public class RegisterViewModel
{
    [Display(Name = "First Name:")]
    public string FirstName { get; set; }
    [Display(Name = "Last Name:")]
    public string LastName { get; set; }

    [Required]
    [EmailAddress]
    [Display(Name = "Email:")]
    public string Email { get; set; }

    [Required]
    [StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)]
    [DataType(DataType.Password)]
    [Display(Name = "Password:")]
    public string Password { get; set; }

    [Required]
    [DataType(DataType.Password)]
    [Display(Name = "Confirm password:")]
    [System.ComponentModel.DataAnnotations.Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
    public string ConfirmPassword { get; set; }

    [Display(Name = "Please choose the category that best describes your professional relation to the field of Orthotics and Prosthetics:")]
    public List<string> ProfessionalRelations { get; set; }
    [Display(Name = "Please check all of the following that apply to you:")]
    public List<string> UserRelations { get; set; }
    [Required]
    [Display(Name = "Country")]
    public SelectList Countries { get; set; }
    [Required]
    [Display(Name = "EDGE Direct E-mail Newsletter:")]
    public List<string> EdgeNewsletter { get; set; }
}

Notice the SelectList for Countries.

Now, in my controller. When someone goes to the Register page, I populate the Country Select List as follows:

var countries = oandpService.GetCountries().OrderBy(x => x.CountryName).ToList();
var usa = countries.FirstOrDefault(x => x.CountryID == "US");
countries.Remove(usa);
countries.Insert(0, new Country() { CountryID = string.Empty, CountryName = string.Empty });
countries.Insert(1, usa);

rvm.Countries = new SelectList(countries, "CountryID", "CountryName");

Finally, here's a part of the view for the countries DropDownListFor:

@Html.DropDownListFor(x => x.Countries, Model.Countries, Model.Countries)

This all displays fine when you go to the page. Countries drop down is generated with the CountryID as the value and CountryName as the Display Text.

Then, just for testing the radio buttons, checkboxes, and dropdowns I have the following:

[HttpPost]
[ValidateAntiForgeryToken]
[ReCaptchaFilter]
public ActionResult Register(RegisterViewModel rvm)
{
    ViewBag.Countries = rvm.Countries.SelectedValue;
    ViewBag.UserRelation = rvm.UserRelations;
    ViewBag.ProfessionalRelation = rvm.ProfessionalRelations;

    return View(rvm);
}

Except I receive the following errors on post:

  1. No parameterless constructor defined for this object.
  2. No parameterless constructor defined for this object. Object type 'System.Web.Mvc.SelectList'.

Could anyone help point me in the right direction? I've done some research but haven't figured it out. Any input would be greatly appreciated.

AJ Tatum
  • 653
  • 2
  • 15
  • 35
  • *No parameterless constructor defined for this object.* Add a constructor with no parameters to your Controller – Divyang Desai Sep 25 '16 at 16:37
  • I already have a parameterless constructor when the person first goes to that page (not a HTTP post). If I try to add another ActionResult Register() it says that the AccountController already defines a member called Register with the same parameter types. – AJ Tatum Sep 25 '16 at 16:41
  • http://stackoverflow.com/a/12701768/4753489 – Divyang Desai Sep 25 '16 at 16:47
  • So I tried this by creating a new property in my model called public Country Country {get;set;} and changed my DropDown to: @Html.DropDownListFor(x => x.Country.CountryID, Model.Countries); and now I just get the error: Object reference not set to an instance of an object. – AJ Tatum Sep 25 '16 at 21:17

1 Answers1

3

You are using the helper method wrong! Check the view source of the page! Your current code is generating a SELECT element with name "Countries". When the form is submitted, the selected value will be submitted against the form element key "Countries".When Model binder tries to map this submitted values to an instance of your RegisterViewModel, It sees "Countries" as of type SelectList(as per the view model class definition) and it cannot build a SelectList object from an integer value( Selected CountryId submitted from the form). That is the reason you are seeing this error! The reason for all this is, because you used the helper method wrong!

Ideally you should add another property to hold the selected value.

public class RegisterViewModel
{
   [Required]
   [Display(Name = "Country")]
   public int SelectedCountry { set;get;}

   public SelectList Countries { get; set; }

   //Your existing properties goes here
}

and in the view

@Html.DropDownListFor(x => x.SelectedCountry, Model.Countries )

and in your HttpPost action, you will get the selected option value in the SelectedCountry property

public ActionResult Register(RegisterViewModel rvm)
{
    var selectedCountry = rvm.SelectedCountry;
    //to do : Reload Countries property of rvm again here(same as your GET action)

    return View(rvm);
}

Also, to add the empty select option, you do not need to add an empty item in your controller code, you can simply use this overload of DropDownListFor helper method.

@Html.DropDownListFor(x => x.SelectedCountry, Model.Countries,string.Empty)
Shyju
  • 214,206
  • 104
  • 411
  • 497
  • Thank you for your response. However, when I post to the controller, I get the error: There is no ViewData item of type 'IEnumerable' that has the key 'SelectedCountry'. Is there something wrong with how I'm populating the DropDownList? Have it above, but its: rvm.Countries = new SelectList(countries, "CountryID", "CountryName"); Thank you again! – AJ Tatum Sep 25 '16 at 21:06
  • Also, don't know if it matters but the SelectedCountry is a string. – AJ Tatum Sep 25 '16 at 21:33
  • @ajtatum, The error is caused because you return the view in the POST method but did not re-populate the SelectList as you did in the GET method (refer [this answer](http://stackoverflow.com/questions/34366305/the-viewdata-item-that-has-the-key-xxx-is-of-type-system-int32-but-must-be-o)) –  Sep 25 '16 at 21:55
  • In the answer I have a todo comment which tells you to reload the countries property same as your GET action because you are returning to the same view and view is using the countries property to build the select element. So you should reload it before calling the View method. Regarding the type of SelectedCountry property, you can use the appropriate one based on your use case. Usually people use int as their primary key. That is y I chose int type – Shyju Sep 25 '16 at 22:04
  • Thanks @Shyju, so we've made a little progress. I have the following for the DropDownListFor: @Html.DropDownListFor(x => x.SelectedCountry, Model.Countries). In the ViewModel, I have it just like how you recommended it, except with SelectedCountry being a sting (CountryID is the abbreviated Country, ie: US. In the Account Controller for Post Registration, I populate all the fields. When I submit the page, it returns "Please select a country." however, in the drop down list the country is selected. I've tried some ways to fix that but I'm a little lost. Any ideas? – AJ Tatum Sep 26 '16 at 13:16
  • Never mind. I had the Required on the public SelectList Countries and not on the public string CountryID. Changed the required to the CountryID and now everything works. Thank you! – AJ Tatum Sep 26 '16 at 13:39