Sum None
Sum None

Reputation: 2269

MVC - Handling null values (NullReferenceException) in DropDownListFor

I'm trying to customize Identity (2.1) using MVC5 and EF6.1. I've added a CountryId to the Identity users table and a whole separate Country table (CountryId, CountryName) with a FK constraint on CountryId. I'm trying to do a drop down list that selects the users country if the user has already selected it in the past (from the CountryId in the users table). However, if it hasn't been selected, the value will be null.

The logic I'm trying to accomplish is this:

if (Model.Country.CountryName != null) {
    @Html.DropDownListFor(m => m.Country, new SelectList(Model.Countries, "Value", "Text"), Model.Country.CountryName)<br />
} else {
    @Html.DropDownListFor(m => m.Country, new SelectList(Model.Countries, "Value", "Text"), "Select a Country")<br />
}

If you remove the if else statement above (which doesn't work either), each DropDownListFor by itself works, except that the former throws a NullRefernceException when CountryName is null. The latter simply doesn't remember what the user chose in the past. Here is my controller:

        var userId = User.Identity.GetUserId();
        ApplicationUser user = await UserManager.FindByIdAsync(userId);
        var model = new IndexViewModel
        {
            HasPassword = HasPassword(),
            //PhoneNumber = await UserManager.GetPhoneNumberAsync(userId),
            //TwoFactor = await UserManager.GetTwoFactorEnabledAsync(userId),
            //Or we can write like this because of the user statement above.
            //PhoneNumber = user.PhoneNumber,
            //TwoFactor = user.TwoFactorEnabled,
            Logins = await UserManager.GetLoginsAsync(userId),
            BrowserRemembered = await AuthenticationManager.TwoFactorBrowserRememberedAsync(userId),
            Country = user.Country,
            State = user.State,
            City = user.City,
            Countries = new SelectList(db.Countries, "CountryId", "CountryName")
        };
        return View(model);

If I use something like this in my controller model:

Countries = new SelectList(db.Countries, "CountryId", "CountryName", user.Country.CountryName)

Then it throws a NullReferenceException at the controller. I understand why this is happening, but for the life of me can't find an answer after half a day's search.

I'm just wondering if there is a clean way to handle the null value and therefore, pass a different default value if CountryId is null?

Any help is always much appreciated.

Edit: I forgot to mention the viewmodel. I basically added all this to the IndexViewModel (although I'm not sure if I need it all at this point):

    public int CountryId { get; set; }
    public int StateId { get; set; }
    public int CityId { get; set; }

    public virtual Country Country { get; set; }
    public virtual State State { get; set; }
    public virtual City City { get; set; }

    public IEnumerable<SelectListItem> Countries { get; set; }
    public IEnumerable<SelectListItem> States { get; set; }
    public IEnumerable<SelectListItem> Cities { get; set; }

Upvotes: 1

Views: 3067

Answers (1)

user3559349
user3559349

Reputation:

Firstly you cannot bind a dropdown to a complex type. Html has no concept of what your c# Country class is. You need to bind to your CountryId property

@Html.DropDownListFor(m => m.CountryId , Model.Countries)

Note also that Countries is already a SelectList so its a bit pointless and unnecessary extra overhead to then create another SelectList from it, so it's just Model.Countries, not new SelectList(Model.Countries, "Value", "Text")

Next, your binding to a property (CountryId) so its the value of CountryId that determines which option is selected - the 3rd parameter (Model.Country.CountryName) is just ignored. But even if it wasn't, it simply would not have selected anything because the value attributes of your options are the values of the CountryId property, not the CountryName property. You need to set the value of CountryId in the controller if Country exists.

Finally, your if (Model.Country.CountryName != null) { throws an exception because the value of Country is null (you cannot access the CountryName property of a null object).

The if/else block is completely unnecessary and you need just one line of code in the view

@Html.DropDownListFor(m => m.CountryId , Model.Countries, "-Please select-")

If the value of CountryId is 0 (the default for int) then the "-Please select-" option will be selected. If the value of CountryId matches one of the values in the collection, then that option will be selected when the view is rendered. Because CountryId is typeof int, then a validation error will be generated if the user does not select a valid country.

And finally, despite the IndexViewModel naming, it is not a view model! A view model includes only the properties you need to display/edit in a view so the 3 virtual properties you have should be removed (and probably the 4 properties relating to States and Cities but not sure if you have just omitted some code from you view and controller)

Upvotes: 2

Related Questions