How do I set a value for the default option with Html.DropDownList - asp.net-mvc

I'm using ASP MVC RC1.
A form I'm using contains a dropdownlist which I have put in a view with this code.
<%= Html.DropDownList("areaid", (SelectList)ViewData["AreaId"], "Select Area Id")%>
However, when rendered, this is what I get
<select id="areaid" name="areaid">
<option value="">Select Area Id</option>
<option value="1">Home</option>
...
</select>
What I'd like is for the Select Area Id option to have a value of 0 and mark it as selected by default so it is consistent with the other values and I can validate whether or not an area has been chosen as it is a mandatory value. AreaId is an integer so when I currently click the form without touching the dropdownlist at all, MVC complains that "" is not an integer and gives me a binding error.
SO how do I set a value for the default option and then make it selected on the form?
Thanks, Dan

I think you have three four options. First when you are building your SelectList or enumerable of SelectItemList, prepend the selection with your option label and default value. Putting it at the top will make it the default if some other value isn't already chosen in the model. Second, you could build the select (and options) "by hand" in the view using a loop to create the options. Again, prepending your default selection if one isn't supplied in the model. Third, use the DropDownList extension, but modify the value of the first option using javascript after the page is loaded.
It doesn't seem to be possible to use the DropDownList extension to assign a value to an optionLabel as it is hard-coded to use string.Empty. Below is the relevant code snippet from http://www.codeplex.com/aspnet.
// Make optionLabel the first item that gets rendered.
if (optionLabel != null) {
listItemBuilder.AppendLine(ListItemToOption(new SelectListItem() { Text = optionLabel, Value = String.Empty, Selected = false }));
}
EDIT: Finally, the best way is to have your model take a Nullable value and mark it as required using the RequiredAttribute. I would recommend using a view-specific model rather than an entity model for the view. Since the value is Nullable, the empty string will work fine if posted back without choosing a value. Setting it as a required value will cause the model validation to fail with an appropriate message that the value is required. This will allow you to use the DropdownList helper as is.
public AreaViewModel
{
[Required]
public int? AreaId { get; set; }
public IEnumerable<SelectListItem> Areas { get; set; }
...
}
#Html.DropDownListFor( model => model.AreaId, Model.Areas, "Select Area Id" )

For MVC3, SelectList has an overload whereby you can define the selected value.
Function Create() As ViewResult
ViewBag.EmployeeId = New SelectList(db.Employees, "Id", "Name", 1)
Return View()
End Function
In this case, I happen to know that 1 is the id of the default list item I want, but presumably you could select the default via query or what ever floats your boat

Instead of passing the default item from the definition in the View, You can add the "Select Area" data at the 0th index of the List from the controller.
This way the Select Area data has an index value of 0.

I wanted to use the same SelectList for multiple drop downs and didn't want to duplicate the SelectList in the model so I just added a new Html Extension method that took in a value and set the selected item.
public static MvcHtmlString DropDownList(this HtmlHelper htmlHelper, string name, string value, IList<SelectListItem> selectList, object htmlAttributes)
{
IEnumerable<SelectListItem> items = selectList.Select(s => new SelectListItem {Text = s.Text, Value = s.Value, Selected = s.Value == value});
return htmlHelper.DropDownList(name, items, null /* optionLabel */, htmlAttributes);
}

Related

How to make DropDownListFor use item IDs instead of indices?

I have a table with a dropdown list in each row, like this:
#Html.DropDownListFor(m => m.Transactions[i].CategoryID, ...)
and everything mostly works. I can select items, submit the form, and the model has my updated selections. So far so good.
The problem is that this isn't very reliable. Since the name of each dropdown is based on an index rather than an ID that means the match-up between the post values and the actual items living in the database are based on indices. Most of the time that works fine, but what if the list of items changes between the time the page loads and the time a user does a postback? The indices have changed, which means the post data won't match up correctly, and bad things happen. OR I've seen the browser incorrectly try to preserve selections in dropdowns between posts, but because the list of items is changing (what may be item #2 now may be item #3 by the time the page is refreshed) and everything is based on indices, the wrong dropdowns get the wrong values.
So basically, how can I force the dropdowns to generate a name and ID that looks more like this:
Transactions_CategoryID_12385652 // 12385652 is the CategoryID
rather than this:
Transactions_4_CategoryID // 4 is the array index
and still have the benefits of automatic binding?
Edit: The second issue I mentioned (input values being restored incorrectly after a refresh) seems to only happen with Firefox. https://bugzilla.mozilla.org/show_bug.cgi?id=46845
You'd have to write your own custom model binder as well as a Html extension to generate the element names in this way, or you could generate the markup manually using Razor.
Is there a particular reason you want need to do it this way? You're almost always following the conventions of the framework unless there's a good reason not to.
You can pass the exact collection you want to the view and bind that to the DropDownFor html helper.
Say you have a Person.
public class Person
{
public int Id { get; set; }
public int Name { get; set; }
}
And you want to add a new person. Create a view model. Create a property in this view model of type SelectList. This select list will hold the collection of the model you want to populate the dropdown list with.
public class PersonViewModel
{
public int Id { get; set; }
public string Name { get; set; }
public SelectList MySelectList { get; set; }
}
Let's say you want the drop down list to hold the Person's Id as the selected value and the Person's name as the text value. You may want to create a second view model to represent that or use an anonymous object. Let's call the collection myCollection. And let's say that the collection is made up of objects that have two properties (and Id and Name property). Now simply pass this view model with a value for MySelectList.
var viewModel = new MyViewModel
{
MySelectList = new SelectList(myCollection, "Id", "Name")
};
In your razor view you can set up the #Html.DropDownListFor like so:
#Html.DropDownListFor(m => m.Id, Model.MySelectList)
In order to pass a select value to the view for the dropdownlist simply use the SelectList constructor overload that allows this value to be passed:
new SelectList(myCollection, "Id", "Name", selectedValue)

ModelState binding custom array of checkboxes

ViewModel Binding is working, the object passed back to the edit controller contains the correct values, which is a list of selected options. However, ModelState binding is not working, the model state AttemptedValues exist, but aren't being reloaded into the fields.
I have a model with the following properties
class Model
{
public List<string> AvailableValues { get; set; }
public List<string> SelectedValues { get; set; }
}
But in my view I have some categorization, so I can't do a direct foreach.
foreach (var category in CatgoryList.Categories)
{
foreach (var available in Model.AvailableValues.Where(x => category.AvailableValues.Contains(x))
{
var check = Model.SelectedValues!= null && Model.SelectedValues.Contains(available.Id);
check &= (ViewData.ModelState["SelectedValues"] != null) && ViewData.ModelState["SelectedValues"].Value.AttemptedValue.Contains(available.Id);
<input type="checkbox" name="SelectedValues" id="available.Id" value="available.Id" checked="#check"/>#available.FriendlyName<br>
}
}
The ModelState does contain SelectedValues from the previous post, but it doesn't auto-bind, because I have a custom field for the checkboxes.
This code is smelly
Is there a better way to get the data to load from the Attempted Value
EDIT:
Ok, so my question wasn't clear enough, let me clarify.
On a validate, I'm retuning the same view if there was an error.
The modelstate is holding the previously entered values in ModelState["field"].Value.AttemptedValue.
With fields created using the helpers, TextboxFor, CheckboxFor, etc, these values are automatically filled in.
However, when using the normal reflexes for checkbox binding, only the values of the checked checkboxes are returned in the data object passed back to the controller. This means I'm not using the logic that fills values in from the ModelState.
What I've done is dig through the modelstate myself for the attempted values, because they do exist under the field name "SelectedValues". But I have to manually apply them. The value there looks like this.
ModelState["SelectedValues"] = "Value1;Value2;Value4"
Is there a better way to get the data to load from the Attempted Value in the model state.
The primary "smell" (to use your term) I see here is that the code you have in the nested foreach is written directly in your view (*.cshtml), but code of that complexity should be in your Controller action.
You should calculate and generate all the data your view will need in the controller, and then pass that data through to the view using Model (looks like you are already doing that) and you can also use the ViewBag to pass additional data not contained in your Model. Then the view is just responsible to generate the HTML.
That's the other problem I see with your code - you are referencing the ViewData.ModelState which is highly unusual to see in a view. The ModelState should be examined in the controller before you even decide which view to render.
It looks like maybe you are just passing data through ViewData.ModelState that should actually be passed through ViewData/ViewBag.
You can read more about passing data to a view here.
Ok, so basically, I couldn't find anything that will do this for me. The default Html helper methods just don't cover this scenario.
So, I wrote an extension method.
Basically it pulls in the enumerator from the model using the expression you send to it, just like any other helper, but you also send the entry in the list you want to build a checkbox against.
It ends up looking like this.
#Html.CheckboxListEntryFor(x => x.SelectedEntries, AvailableEntries[i].Id)
The method does the following
Get the propertyInfo for the list and check if selected entries contains the values.
Check if the ModelState is invalid, if so, overwrite the checked value with the modelstate entry
build an html checkbox that uses the property name as the name and id of the checkbox, and sets checked based on the previous steps.
public static MvcHtmlString CheckboxListEntryFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TProperty>> expression, string entryValue)
{
PropertyInfo info = GetPropertyInfo(typeof (TModel), expression);
var enumerator = info.GetValue(htmlHelper.ViewData.Model);
var check = enumerator != null && ((IList) enumerator).Contains(entryValue);
if (!htmlHelper.ViewData.ModelState.IsValid)
{
check = htmlHelper.ViewData.ModelState[info.Name] != null &&
htmlHelper.ViewData.ModelState[info.Name].Value.AttemptedValue.Contains(entryValue);
}
var fieldString = String.Format(
"<input type=\"checkbox\" name=\"{0}\" id =\"{1}\" value=\"{1}\"{2}/>",
info.Name, entryValue, check ? " checked=\"checked\"" : string.Empty);
return MvcHtmlString.Create(fieldString);
}

DropDownList not getting selected - MVC4

I have the DropDownList in view as below,
#Html.DropDownListFor(model => model.RoleID, (IEnumerable<SelectListItem>)ViewBag.RoleID, new { #class = "dropdownlistCustom" })
#Html.DropDownList("RoleID", (IEnumerable<SelectListItem>)ViewBag.RoleID, new { #class = "dropdownlistCustom" })
#Html.ValidationMessageFor(model => model.RoleID)
Tried both DropDownList and DropDownListFor, both are not getting the correct SelectedValue.
From Controller, I am setting the selectedValue param (see Last parameter of SelectList()) .
public ActionResult Edit(int id = 0)
{
UserDetail userDetail=db.UserDetails.Find(id);
if(userDetail!=null)
{
ViewBag.RoleID = new SelectList(db.Roles.Where(r => r.RoleStatus == "A"), "RoleID", "RoleName", userdetail.RoleID);
return View(userdetail);
}
}
Model :
[Display(Name = "Name Of the Role")]
public int RoleID { get; set; }
[ForeignKey("RoleID")]
public virtual Role Roles { get; set; }
First of all I would encourage you not to use ViewBag, but view models to pass your select list (or data for select list) along from controllers to views.
Second of all, I would encourage you to look at this example or this example (preferably both) of DropDownListFor usage. I think you over-complicating SelectList. Less simple it is, less confused you will be as to why is it not working as expected. Just take existing example and change variable names to yours.
Another big tip why not to use ViewBag is that when you build your view model you can build a select list right there in the controller where you fetch your data from wherever. Then in the view you will only need to use pre-built view model property where SelectList argument is provided - cleaner and nicer. View should do least manipulations with data, but only to present it in the right format in the right way in the right place.
Just look at those examples I provided, I am sure you smart enough to figure it out how to use DropDownListFor properly, its very very easy, you just need to provide right parameters at the right places, see what others do and do the same.
Hope this helps, please keep me posted about your progress.
That's because you're not using DropDownListFor correctly. The function definition you're after is:
DropDownListFor(lambda, selectList, *selectedItem*, htmlAttributes)
RoleId should be passed as the selectedItem parameter, so you're missing the selectList parameter. MVC doesn't create that for you automatically with DropDownListFor, you have to tell it what select list to use by either creating it inline, passing it into ViewBag or setting it on your model and specifying which property to use.
EDIT
Sorry. That's my bad. It's still early here and we lost an hour (daylight savings), so my brain is not all there yet. I mixed DropDownListFor and DropDownList. With DropDownListFor, the selected item comes from the set value on the model, but you still need to provide a proper SelectList. It appears you're trying to specify the selected item where you should be telling it what SelectList to use.
If you were to use DropDownList, then you would need to specify both the SelectList to use and the selected value.

Why is DropDownListFor not recognizing the selected value in my editor template?

I have the following editor template called 'DropDown.cshtml'. The list part works fine, and the template uses some voodoo I did to get the required SelectList from ViewData. The controller places all select lists in the view model into ViewData, and there is nothing wrong with the list side of things.
#{
var list = this.GetModelSelectList();
}
#Html.DropDownListFor(m => Model, list)
I use this template on foreign key view model properties like this one:
[Required]
[UIHint("DropDown", "MVC", "SelectListName", "JobLevelSelectList")]
[Display(Name = "Job Level")]
public Guid? JobLevelId { get; set; }
public SelectList JobLevelSelectList { get; set; }
In the controller, JobLevelId has the correct value immediately before executing the view, yet no item it selected in the rendered select element. or rather, the first item in the select list is always selected.
Why does DropDownListFor ignore the property value when used in my editor template and yet work fine when invoked directly?
This is unfortunately a known bug in MVC3 (I haven't tried it in MVC 4 Beta to see if it fixed).
The work around that I have used is to manually set the Selected property accordingly in collection that the DropDownListFor is bound to, it is not ideal but it worked.

Why is ListBoxFor not selecting items, but ListBox is?

I have the following code in my view:
<%= Html.ListBoxFor(c => c.Project.Categories,
new MultiSelectList(Model.Categories, "Id", "Name", new List<int> { 1, 2 }))%>
<%= Html.ListBox("MultiSelectList",
new MultiSelectList(Model.Categories, "Id", "Name", new List<int> { 1, 2 }))%>
The only difference is that the first helper is strongly typed (ListBoxFor), and it fails to show the selected items (1,2), even though the items appear in the list, etc. The simpler ListBox is working as expected.
I'm obviously missing something here. I can use the second approach, but this is really bugging me and I'd like to figure it out.
For reference, my model is:
public class ProjectEditModel
{
public Project Project { get; set; }
public IEnumerable<Project> Projects { get; set; }
public IEnumerable<Client> Clients { get; set; }
public IEnumerable<Category> Categories { get; set; }
public IEnumerable<Tag> Tags { get; set; }
public ProjectSlide SelectedSlide { get; set; }
}
Update
I just changed the ListBox name to Project.Categories (matching my model) and it now FAILS to select the item.
<%= Html.ListBox("Project.Categories",
new MultiSelectList(Model.Categories, "Id", "Name", new List<int> { 1, 2 }))%>
I'm obviously not understanding the magic that is happening here.
Update 2
Ok, this is purely naming, for example, this works...
<%= Html.ListBox("Project_Tags",
new MultiSelectList(Model.Tags, "Id", "Name", Model.Project.Tags.Select(t => t.Id)))%>
...because the field name is Project_Tags, not Project.Tags, in fact, anything other than Tags or Project.Tags will work. I don't get why this would cause a problem (other than that it matches the entity name), and I'm not good enough at this to be able to dig in and find out.
I've stumbled across this problem myself, finally I realized that the problem was a naming convention.
You cannot name the ViewBag or ViewData poperty containing the SelectList or MultiSelectList to the same name your property model containing the selected items. At least not if you're using the ListBoxFor or DropDownListFor helper.
Here's an example:
public class Person
{
public List<int> Cars { get; set; }
}
[HttpGet]
public ActionResult Create()
{
//wont work
ViewBag.Cars = new SelectList(carsList, "CarId", "Name");
//will work due to different name than the property.
ViewBag.CarsList = new SelectList(carsList, "CarId", "Name");
return View();
}
//View
#Html.ListBoxFor(model => model.Cars, ViewBag.CarsList as SelectList)
I'm sure theres plenty of other ways doing this, but it solved my problem, hope it will help someone!
I have also been stuck with this exact same issue and encountered the same problem with ListBox and ListBoxFor.
No matter what I do, I cannot get selections to occur on the ListBoxFor. If I change to the ListBox and name it something OTHER than the property name of the data I am binding to, selections occur.
But then because I'm not using ListBoxFor and the data is sitting inside a model class (Model.Departments) for example, I don't get model binding on the way back to my controller and hence the property is null.
EDIT I found a solution posted by someone else here;
Challenges with selecting values in ListBoxFor
Also, you can try to clear ModelState for c.Project.Categories in the controller:
[HttpPost]
public ActionResult Index(ModelType model)
{
ModelState.Remove("Project.Categories");
return View("Index", model);
}
And use the next construction:
<%= Html.ListBoxFor(c => c.Project.Categories,
new MultiSelectList(Model.Categories, "Id", "Name"))%>
Where c.Project.Categories is IEnumerable<int>.
Sorry for my english. Good luck!
The correct answer is that it doesn't work very well. As such I read the MVC code. What you need to do is implement IConvertible and also create a TypeConverter.
So, in my instance I had a Country class, such that people could choose from a list of countries. No joy in selecting it. I was expecting an object equals comparison on the selectedItems against the listitems but no, that's not how it works. Despite the fact that MultiListItem works and correctly gets the selected items, the moment it is bound to your model it's all based on checking that the string represnetation of your object instance matches the string "value" (or name if that is missing) in the list of items in the SelectItemList.
So, implement IConvertible, return the string value from ToString which would match the value in the SelectItemList. e.g in my case CountryCode was serialized into the SelectItem Value property , so in ToString IConvertible I returned CountryCode. Now it all selects correctly.
I will point out the TypeConverter is used on the way in. This time its the inverse. That Countrycode comes back in and "EN" needs converting into Country class instance. That's where the TypeConverter came in. It's also about the time I realised how difficult this approach is to use.
p.s so on your Category class you need to implement IConvertible. If its from the entity framework as my company is then you'll need to use the partial class to implement IConvertible and implement ToString and decorate it with a TypeConverter you wrote too.
Although this isn't an answer to your main question, it is worth noting that when MVC generates names it will turn something like Project.Tags into Project_Tags, replacing periods with underscores.
The reason that it does this is because a period in an element ID would look like an element named Project with a class of Tags to CSS. Clearly a bad thing, hence the translation to underscores to keep behaviour predictable.
In your first example,
<%= Html.ListBoxFor(c => c.Project.Categories,
new MultiSelectList(Model.Categories, "Id", "Name", new List<int> { 1, 2 }))%>
the listbox is attempting to bind to Model.Project.Categories for your strongly typed Model which has been provided to the page (using the lambda notation). I'm not sure what the second parameter in the ListBoxFor is doing though.
What is the Model that is being passed to the page?
Try this
<%= Html.ListBoxFor(c => c.Project.Categories,
new MultiSelectList(
Model.Categories
,"Id"
,"Name"
,Model.Project.Tags.Select(
x => new SelectListItem()
{
Selected = true,
Text = x.TEXT,
Value = x.ID.ToString()
}).ToList())
)
)%>
Html.ListboxFor and Html.Listbox work great when you're NOT binding the list box to its data source. I assume the intended use is this:
// Model
public class ListBoxEditModel
{
public IEnumerable<Category> Categories { get; set; }
public IEnumerable<Category> SelectedCategories { get; set; }
}
In the view:
#Html.ListBoxFor(m => m.SelectedCategories,
new MultiSelectList(Model.Categories, "Id", "Name"))
// or, equivalently
#Html.ListBox("SelectedCategories" ,
new MultiSelectList(Model.Categories, "Id", "Name"))
Note that in this case you don't have to explicitly say which values are selected in the MultiSelectList - the model you're binding to takes precedence, even if you do!

Resources