Extending #Html.DropDownListFor - asp.net

I would like to use the following code by extending the #Html.DropDownListFor helper.
My controller gets a list, and I am adding a default value to the IEnumerable in my view model. However I don't want to write all of this every time I want a dropdown with a default value. So extending the helper seems logical.
public class SiteGetSitesViewModel
{
public string SelectedSiteId { get; set; }
public IEnumerable<SelectListItem> SiteListItems { get; set; }
}
public ActionResult GetSites()
{
List<SiteDto> sites = _siteService.GetSites();
const string defaultText = "Select a site";
const string defaultValue = "-1";
var siteGetSitesViewModel = new SiteGetSitesViewModel
{
SiteListItems = Enumerable.Repeat(new SelectListItem
{
Selected = true,
Text = defaultText,
Value = defaultValue
}, 1).Concat(sites.Select(s => new SelectListItem
{
Text = s.SiteName,
Value = s.SiteId.ToString()
}))
};
return PartialView("_GetSites", siteGetSitesViewModel);
}

Html.DropDownListFor supports adding a default value to the choices already, so there is no need to reinvent the wheel:
Use this overload:
http://msdn.microsoft.com/en-us/library/ee703567(v=vs.108).aspx
#Html.DropDownListFor(
m => m.Property,
GetSelectList(),
"defaultOption", // the text for the default option goes here
null
)

Related

ASP.Net Core MVC - List<SelectListItem> multiselect not showing select = true items

I'm making a strongly typed update/create view for "Medical Supplies" in which users can select multiple options ("Kits") from a dropdown list that received a List<SelectListItem>. The list is a <select multiple="multiple">. It was originally working perfectly, but I must have accidentally changed something.
Now the dropdown does not display SelectListItems passed to it as selected = true as selected (as verified by VS debugger), so I can select new items but not deselect previously selected ones. I need this to compare the list of IDs from the new selection to the old one in order to determine what must be removed from the Db.
This is my view model:
public class MedicalSupplyViewModel
{
public MedicalSupplyViewModel()
{
Supply = new MedicalSupply();
KitList = new List<SelectListItem>();
KitIds = new List<int>();
}
public MedicalSupply Supply { get; set; }
public List<SelectListItem> KitList { get; set; }
public string StringKits { get; set; }
public List<int> KitIds { get; set; }
public void KitStringSet()
{
IEnumerable<string> KitNames = Supply.KitSupplies.Select(ks => ks.Kit.Name);
StringKits = Supply.KitSupplies.Count() == 0 ? "N/A" : string.Join(", " , KitNames);
}
}
This is the relevant cshtml in my view:
<select multiple="multiple" class="form-control" id="kit_select" asp-for="KitIds" asp-items="Model.KitList"></select>
This is the part of the controller method for this page that creates the SelectListItems:
DetailsModel.KitList = _db.Kits.ToList().ConvertAll(k =>
{
return new SelectListItem()
{
Value = k.Id.ToString(),
Text = k.Name,
Selected = SelectedKits.Contains(k)
};
});
Even setting the item to selected = true will not display them as such. I've set breakpoint everywhere, including in the view, and I cannot find a discrepancy between the selected property and what it should be anywhere except for the rendered html. I've also used different browsers and spent hours searching the internet and this website.
What could be the cause of this issue?
You should set the selected items' value to the KitIds. Below is my test example:
Model:
public class MedicalSupplyViewModel
{
public MedicalSupplyViewModel()
{
KitList = new List<SelectListItem>();
KitIds = new List<int>();
}
public List<SelectListItem> KitList { get; set; }
public string StringKits { get; set; }
public List<int> KitIds { get; set; }
}
public class Kit
{
public int Id { get; set; }
public string Name { get; set; }
}
Controller:
public IActionResult Index()
{
List<Kit> kits = new List<Kit>
{
new Kit{ Id = 1, Name = "AA"},
new Kit{ Id = 2, Name = "BB"},
new Kit{ Id = 3, Name = "CC"},
};
List<Kit> SelectedKits = new List<Kit>
{
new Kit{ Id = 1, Name = "AA"},
new Kit{ Id = 2, Name = "BB"}
};
var DetailsModel = new MedicalSupplyViewModel();
DetailsModel.KitIds = SelectedKits.Select(x => x.Id).ToList();
DetailsModel.KitList = kits.ToList().ConvertAll(k =>
{
return new SelectListItem()
{
Value = k.Id.ToString(),
Text = k.Name
};
});
return View(DetailsModel);
}
View:
<select multiple="multiple" class="form-control" id="kit_select" asp-for="KitIds" asp-items="Model.KitList"></select>
Result:

Html.DropDownListFor set selected value

I create a #Html.DropDownListFor and populate it from the database. How can I set a selected value to the drop down?
My View:
#Html.DropDownListFor(m => m.Forms, new SelectList(Model.Forms, "FormsCreatorID", "FormName"),
"Select a Form", new { #class = "form-control" })
My Controller:
var forms = db.formscreators.Where(fp => fp.PropertyID == id || fp.PropertyID == 0)
.OrderByDescending(x => x.PropertyID).GroupBy(x => x.FormName).Select(x => x.FirstOrDefault()).ToList();
var viewModel = new ListFormsCreator { Forms = forms };
My ViewModel:
public class ListFormsCreator
{
public List<formscreator> Forms { get; set; }
}
My Database Model:
public partial class formscreator
{
public int FormsCreatorID { get; set; }
public string FormName { get; set; }
public int PropertyID { get; set; }
}
Thanks
You should add another property to your view model for the store/pass the selected option.
public class ListFormsCreator
{
public int SelectedFormId { set;get;}
public List<formscreator> Forms { get; set; }
}
Now in your GET action, you can set that value
var viewModel = new ListFormsCreator() { Forms = forms };
viewModel.SelectedFormId = 2 ; // This will select the option with 2 as FormsCreatorID
return View(viewModel);
And in the view use the lambda expression with that property as the first parameter of the DropDownListFor helper method.
#model ListFormsCreator
#Html.DropDownListFor(m => m.SelectedFormId ,
new SelectList(Model.Forms, "FormsCreatorID", "FormName"),
"Select a Form", new { #class = "form-control" })
The DropDownListFor helper method will use the value of SelectedFormId property and select the option which has the same value attribute value from the list of options of that SELECT element.
You can also remove the dependency on formscreator class from the view model, by replacing it with a list of SelectListItem
public class ListFormsCreator
{
public int SelectedFormId { set;get;}
public List<SelectListItem> Forms { get; set; }
}
Now in your GET action, you can use the Select method to generate the lsit of SelectListItem from your other collection.
var viewModel = new ListFormsCreator();
viewModel.Forms = someCollection.Select(a=>new SelectListItem {
Value=a.FormsCreatorId.ToString(),
Text=a.FormName})
.ToList();
viewModel.SelectedFormId = 2 ; // This will select the option with 2 as FormsCreatorID
return View(viewModel);
Assuming someCollection is a collection of formscreator objects
Now in the view code is much simpler
#Html.DropDownListFor(m => m.SelectedFormId, Model.Forms ,"Select a Form")
Conform with C#/.NET naming conventions:
Rename formscreator to FormsCreator
Replace ID with Id (as it's an abbreviation, not an initialism)
Rename ListFormsCreator to something like ListFormsCreatorViewModel so it's obvious it's a ViewModel type and not a Model/Entity type.
Modify your ViewModel to add a property to store the selected FormsCreatorId value:
public class ListFormsCreatorViewModel
{
[Required] // add or remove the 'Required' attribute as necessary
public int? SelectedFormsCreatorId { get; set; }
...
}
Set the SelectedFormsCreatorId property value in your controller action if necessary if you know what the value should be.
In your POST handler, ensure the SelectedFormsCreatorId value is maintained, either by directly passing-through the model action parameter back through the View(Object viewModel) method or manually repopulating it.
The view-model property in DropDownListFor should be the SelectedFormsCreatorId property. You do not need new SelectList(...)
#Html.DropDownListFor( m => m.SelectedFormsCreatorId, this.Model.Forms );
Update your viewModel and add an Int SelectId for the dropdown selected value.
In your controller:
var viewModel = new ListFormsCreator { SelectId = PropertyId, Forms = FormSelectList(forms, PropertyId.ToString()) };
I would create a function passing in a list:
public static SelectList FormSelectList(IEnumerable<formscreators> types, string selected = null)
{
return new SelectList(from f in forms
select new SelectListItem
{
Text = f.FormName,
Value = f.FormsCreatorID.ToString()
}, "Value", "Text", selected);
}
And in your .cshtml
#Html.DropDownListFor(m => m.PropertyId, Model.forms, "Select a Form", new { #class = "form-control", required = "required" })
You should generate a 'SelectListItem' list on the controller with setting 'Selected' value and pass it via ViewBag or ViewModel. In my sample, for simplicity, I used ViewBag.
Here is the shortened Controller:
public ActionResult Edit(int? id)
{
if (id == null)
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
Album album = context.Albums.Find(id);
if (album == null)
{
return HttpNotFound();
}
ViewBag.GenreId = context.Genres.Select(
g => new SelectListItem()
{
Value = g.GenreId.ToString(),
Text = g.Name,
Selected = g.GenreId == album.GenreId ? true : false
}).ToList();
return View(album);
}
Here is the shortened View Code
#using MvcMusicStore2017.Models;
#model Album
#Html.DropDownList("GenreId", null, new { #class = "form-control" })

ASP.NET MVC ListBox does not show the selected list items

I am in a big trouble. I read 4 stackoverflow question and one blogpost. I have tried 5 different approach to view the selected items in a multiple selectlist.
I have no success.
The multiple selectlist is generated, but it does not select the items. I have no more idea.
Model:
public class EditableModel
{
public IList<Company> SelectedCompanies { get; set; }
public IList<SelectListItem> SelectListCompanies { get; set; }
}
Controller:
public ActionResult Edit(int id)
{
var service = _serviceDAL.GetEditableModel(id);
if (service!= null)
{
service.SelectListCompanies = GetSelectListCompanies(service.SelectedCompanies);
return View(service);
}
}
private IList<SelectListItem> GetSelectListCompanies(IList<Company> selectedCompanies)
{
List<SelectListItem> items = new List<SelectListItem>();
foreach (Companycompany in _companyService.GetCompanies())
{
items.Add(new SelectListItem
{
Value = company.CompanyId.ToString(),
Text = company.Name,
Selected = selectedCompanies.Any(x => x.CompanyId == company.CompanyId)
});
}
return items;
}
View
#Html.ListBox("SelectedCompanies", Model.SelectListCompanies, Model.SelectedCompanies.Select(x => x.CompanyId.ToString()) )
And nothing. The items in the select list is not selected...
I have tried this Multiselect, the same result, or this one as the current solution.
You cannot bind a <select multiple> to a collection of complex objects. It binds to, and posts back an array of simple values (the values of the selected options).
Your SelectedCompanies property needs to be IEnumerable<int> (assuming the CompanyId of Company is also int). Note also the Selected property of SelectListItem is ignored when binding to a property.
Your also using the same collection for the selected Companies and the list of all Companies which makes no sense. Your SelectListCompanies should be generated from your table of Company.
Model
public class MyViewModel
{
public IEnumerable<int> SelectedCompanies { get; set; }
public IEnumerable<SelectListItem> SelectListCompanies { get; set; }
}
Base on your current code for EditableModel, your code should be
public ActionResult Edit(int id)
{
var service = _serviceDAL.GetEditableModel(id);
....
MyViewModel model = new MyViewModel
{
SelectedCompanies = service.SelectedCompanies.Select(x => x.CompanyId),
SelectListCompanies = GetSelectListCompanies()
};
return View(model);
private IEnumerable<SelectListItem> GetSelectListCompanies()
{
var all companies = ... // call method to get all Companies
return companies.Select(x => new SelectListItem
{
Value = x.CompanyId.ToString(),
Text = x.Name
});
}
However, it look like you should be modifying your EditableModel and the GetEditableModel() code to return the correct data in the first place.

Html.DropDownListFor selecting item based on ModelState rather than Model

In asp.net mvc (v5 in my case), when you post a model back to a controller, and then render a view with a model other than the one that was posted back, then Html.DropDownListFor will select the selected item based on the data from the model that was posted back, rather than the model that was passed to the view.
Why is this?
Here's an example. When you select a new item in the first dropdown, the form posts back, and if the fist dropdown has id=2 selected, then the selectedId2 is changed and the second dropdown should render with the second item selected. However that's not the case. While the Model object in the view does have the new selectedId2 = 2 value, the DropDownListFor under the hood gets the selected value from the ViewState.ModelState object, not the ViewState.Model. What can be done to get the DropDownListFor to render based only on the Model passed to the view?
The Controller:
public class HomeController : Controller
{
public ActionResult Index()
{
var defaultModel = getModel();
return View(defaultModel);
}
[HttpPost]
public ActionResult Index(Model1 postedModel)
{
var defaultModel = getModel();
if (postedModel.selectedId1 == 2)
{
defaultModel.selectedId1 = 2;
defaultModel.selectedId2 = 2;
}
return View(defaultModel);
}
private Model1 getModel()
{
var items1 = new[]
{
new
{
Name = "Item 1-1",
Id = "1"
},
new
{
Name = "Item 1-2",
Id = "2"
}
};
var items2 = new[]
{
new
{
Name = "Item 1-1",
Id = "1"
},
new
{
Name = "Item 2-2",
Id = "2"
}
};
return new Model1
{
selectedId1 = 1,
selectedId2 = 1,
SelectList1 = new SelectList(items1, "Id", "Name", 1),
SelectList2 = new SelectList(items2, "Id", "Name", 1)
};
}
}
The View Model:
public class Model1
{
public SelectList SelectList1 { get; set; }
public SelectList SelectList2 { get; set; }
public int selectedId1 { get; set; }
public int selectedId2 { get; set; }
}
The View:
#model Model1
#using (Html.BeginForm(FormMethod.Post))
{
<div>
#Html.DropDownListFor(m => m.selectedId1, Model.SelectList1, new { title = "Select 1", onchange = "this.form.submit();" })
#Html.DropDownListFor(m => m.selectedId2, Model.SelectList2, new { title = "Select 2", onchange = "this.form.submit();" })
</div>
}
Edit: One Solution I found:
Adding this line before executing the View() clears out the posted model so that the DropDownListFor helper uses the model that is passed to the view:
ViewData.ModelState.Clear();
You can specify the selected value if you use the appropriate SelectList constructor:
SelectList Constructor (IEnumerable, String, String, Object)
The last parameter is the selected item, which must be one object of the IEnumerable.
You are using this constructor, but are specifying 1 as the value. It should be something like items1[0]

Dropdown list in asp.net

i've a view. in the view i've months field(nvarchar type in database) :
#Html.DropDownListFor(model => model.rent_month,
(IEnumerable<SelectListItem>)ViewBag.months)
i've a method in a model class (PostManager) to generate months list like:
public IEnumerable<SelectListItem> GetMyMonthList()
{
return CultureInfo.CurrentCulture.DateTimeFormat.MonthNames
.Select(m => new SelectListItem() { Text = m, Value = m });
}
i get months in get action by :
public ActionResult Create()
{
PostModel p = new PostModel();
ViewBag.months = pm.GetMyMonthList();
return View(p);
}
in my Model my month attributes:
[Required(ErrorMessage = "You Must Select a Month.")]
[Display(Name = "Select Rent Month")]
public string rent_month { get; set; }
in the post action:
public ActionResult Create(PostModel p)
{
if (ModelState.IsValid)
{
post post = new Models.DB.post();
post.rent_month = p.rent_month;
db.posts.AddObject(post);
db.SaveChanges();
}
}
it generates month in the dropdownlist correctly.But after submit the form it gives error:
The ViewData item that has the key 'rent_month' is of type 'System.String' but must be of type 'IEnumerable'
now what is the solution for this error... thanks in advance...
I believe this is happening because in your post action you are not populating the ViewBag again. Make sure you set ViewBag.months = pm.GetMyMonthList(); in your controller POST action similar to what you have done in GET action.
Better solution would be to have a IEnumerable<SelectListItem> MonthList property as part of the PostModel. Instead of loading the months from ViewBag you can access it directly by the MonthList property
In the PostModel
public IEnumerable<SelectListItem> MonthList
{
get
{
return pm
.GetMonthList()
.Select(a => new SelectListItem
{
Value = a.Id,
Text = a.MonthText
})
.ToList();
}
}
Then in the view
#Html.DropDownListFor(model => model.rent_month, Model.MonthList)
After EDIT to the question
Your PostModel class should be like this. I have moved your GetMyMonthList() implementation out of the PostManager class.
public class PostModel
{
[Required(ErrorMessage = "You Must Select a Month.")]
[Display(Name = "Select Rent Month")]
public string rent_month { get; set; }
public IEnumerable<SelectListItem> MonthList
{
get
{
return CultureInfo.CurrentCulture.DateTimeFormat.MonthNames
.Select(m => new SelectListItem() { Text = m, Value = m });
}
}
}
public class PostModel
{
[Required(ErrorMessage = "You Must Select a Month.")]
[Display(Name = "Select Rent Month")]
public string rent_month { get; set; }
public IEnumerable<SelectListItem> MonthList
{
get
{
return CultureInfo.CurrentCulture.DateTimeFormat.MonthNames
.Select(m => new SelectListItem() { Text = m, Value = m });
}
}
}

Resources