0
votes

I have the following Address ViewModel:

public class AddressViewModel
{
    [StringLength(20, MinimumLength = 2, ErrorMessage = "Country name is too short")]
    public String Country { get; set; }

    public String City { get; set; }
    public String Street { get; set; }
    public String Number { get; set; }
    public String ApartmentBuilding { get; set; }
    public String Sector { get; set; }
}

And the view that renders it:

<div class="control-group offset2 span6">
    @Html.LabelFor(m => m.Country)
    <div class="controls">
        @{
            var countryCtrlName = Html.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName("Country");
            Html.RenderAction("List", "Country", new { ControlName = countryCtrlName });
        }
        @Html.ValidationMessageFor(m => m.Country)
    </div>
</div>

Html.RenderAction("List") calls a controller method that gets all countries from the database and renders and renders a partial with the dropdown, here's the view:

@model IEnumerable<SelectListItem>
@{var controlName = (string)ViewBag.ControlName;}
@Html.DropDownList(controlName, Model, new {@class = ViewBag.CssClass})

Even though my DropdownList control is rendered with the correct name and thus mapped to the correct ViewModel upon POST, the input control isn't decorated with the necessary data-val attributes to enable client-side validation (I believe this is because the model for the partial is IEnumerable instead of the the string property that holds the country name.

The address view model is used through my application as a nested property on many views. Any ideas on how to make it validate?

Edit: updated ViewModel based on @Robert's answer:

public class AddressViewModel { [StringLength(20, MinimumLength = 2, ErrorMessage = "Country name is too short")] public String Country { get; set; }

public String City { get; set; }
public String Street { get; set; }
public String Number { get; set; }
public String ApartmentBuilding { get; set; }
public String Sector { get; set; }

public IEnumerable<CountryViewModel> CountryList {get; set;}

//Constructor to pass the list of countries
public AddressViewModel(IEnumerable<CountryViewModel> countries)
{
    this.CountryList = countries;
}

}

2
I'm not familiar with KDropDownList. Google tells me it's part of Keyoti.Search. Is that correct?Ann L.
KDropDownList is an extension I made that decorates the dropdown with some additional classes. It's the same as calling Html.DropDownListFor (I updated the code anyway for readability)amhed

2 Answers

1
votes

Have you tried making a CountryModel and have a separate controller that deals with your dropdown list. have the controller return a partial view that you can put on any page you want. Have one property on the CountryModel with an IEnumerable?

Address View:

@model AddressModel

@Html.Partial("nameOfPartialView", CountryModel)

Model:

public class CountryModel
{
    public IEnumerable<Countries> Countries { get; set; }
}

Controller:

public ActionResult Countries()
{
    var countries = //get the list from the database
    return PartialView(countries);
}

Partial View with the countries DropDownList:

@model CountryModel
@{var controlName = (string)ViewBag.ControlName;}
@Html.DropDownListFor(Model => Model.Countries)

Controller that accepts the country:

public ActionResult GetCountry(int CountryId)
{
     //do something with the selected country
}
0
votes

I think you're right about what the problem is: you're not passing your annotated model to the partial view, but an IEnumerable of SelectListItem. The framework doesn't know what you're displaying represents: it just knows what to call it.

I can see that doing it this way is handy, but it kind of violates the spirit of MVC. In this case, your "model" isn't really a model, it's just a way of passing a list of markup items (the list items).

I would use the whole AddressViewModel as your model. That way you'll retain the information from the data annotations that tell you what the requirements for that property are.