I have a list of Organizations (CalledOrganizationSelectList) and one of them is the one that's being called (CalledOrganizationId). I set both in my ViewModel but MVC / Razor doesn't render the dropdown correctly (ie does not set the selected="selected" attribute for the correct item in the dropdown).
There is a workaround which makes even less sense. I added a new member to my ViewModel and bound the dropdown to that. This works fine. Until just now when it's stopped working...
public class CallViewModel{
public int? CalledOrganizationId { get; set; }
public SelectList CalledOrganizationSelectList { get; set; }}
In my controller:
var vm = new CallViewModel();
var calledOrganizationSelectList = new List<object>();
calledOrganizationSelectList.Add( new {Text="",Id=""});
calledOrganizationSelectList.AddRange(
db.MyOrganizations.OrderBy(x=>x.Name)
.Select(x => new { Text = x.Name, Id = x.Id.ToString()}).ToList());
var sl = new SelectList(calledOrganizationSelectList, "Id", "Text",
vm.CalledOrganizationId);
vm.CalledOrganizationSelectList = sl;
return View(vm);
In my view:
<div>CalledOrganizationId = #Model.CalledOrganizationId :
select list contains
<ul>#foreach (var itm in Model.CalledOrganizationSelectList)
{<li>Value: #itm.Value Text: #itm.Text Is Selected: #itm.Selected</li>}
</ul>
</div>
#Html.DropDownList("CalledOrganizationId", Model.CalledOrganizationSelectList)
In my rendered page source:
<div>CalledOrganizationId = 38 : select list contains
<ul>
<li>Value: Text: Is Selected: False</li>
<li>Value: 37 Text: rrr Is Selected: False</li>
<li>Value: 38 Text: sss1 Is Selected: True</li>
</ul>
</div>
<select data-val="true" data-val-number="The field CalledOrganizationId must be a number." id="CalledOrganizationId" name="CalledOrganizationId">
<option selected="selected" value=""></option>
<option value="37">rrr</option>
<option value="38">sss1</option> </select>
I've worked around it after first pulling out what was left of my hair and then by introducing a new variable on my ViewModel, which seems to work ok.
Having 2 properties on your viewmodel is the correct way to do DropDownLists. One property holds all of the available options, and the other holds the currently selected option:
public class CallViewModel
{
// this captures the selected item
public int? CalledOrganizationId { get; set; }
// this contains the select list options
public IEnumerable<SelectListItem> CalledOrganizationSelectList { get; set; }
}
However, you do not need to add the empty option in the controller. You can do this in the view:
#Html.DropDownListFor(m => m.CalledOrganizationId,
Model.CalledOrganizationSelectList, "")
You can just get away with this in the controller:
var vm = new CallViewModel();
//var calledOrganizationSelectList = new List<object>();
//calledOrganizationSelectList.Add( new {Text="",Id=""});
//calledOrganizationSelectList.AddRange(
//db.MyOrganizations.OrderBy(x=>x.Name)
// .Select(x => new { Text = x.Name, Id = x.Id.ToString()}).ToList());
//var sl = new SelectList(calledOrganizationSelectList, "Id", "Text",
// vm.CalledOrganizationId);
//vm.CalledOrganizationSelectList = sl;
vm.CalledOrganizationSelectList = db.MyOrganizations.OrderBy(x => x.Name)
.Select(x => new SelectListItem
{
Text = x.Name,
//Id = x.Id.ToString() // do not use "Id" for the property name,
Value = x.Id.ToString() // use "Value" -- it is a SelectListItem property
});
// if you want to set the selected item on the dropdownlist, do it here
vm.CalledOrganizationId = 37;
return View(vm);
Related
I'm using ASP.NET Core v3 MVC with Visual Studio 2019 Enterprise.
I have database like this:
I'm using database first approach to generate models for that database.
After that I'm adding following type of controller for product_info table:
Which will also generate Views. My question is - how can I display attributes from other tables in drop-down of Create View.
Here is example of what I mean:
I don't need complete solution, though it could be useful, any hints or pointers on how to do it would suffice.
You can create a viewmodel to display attributes in other model.
ViewModel:
public class ViewModel
{
public IEnumerable<SelectListItem> Model1Items{ get; set; }
public int SelectedId { get; set; }
}
Controller:
var items = (from m in db.Model1s
select new SelectListItem{
Value = m.Id,
Text = m.Name
}).ToList();
var vm = new ViewModel();
vm.Model1Items = new SelectList(items, "Value", "Text");
View:
#Html.DropDownListFor(model => model.SelectedId, Model.Model1Items, "--Select--")
This is full solution with fix for various problems I've encountered:
MODEL
Create folder in your project and call it ViewModel
Create class in that folder and call it ProductInfoViewModel.cs.Here is full content of ProductInfoViewModel.cs:
namespace ProductTest.ViewModels
{
public class ProductInfoViewModel
{
public int ProductId { get; set; }
public IEnumerable<SelectListItem> ProductName { get; set; } = new List<SelectListItem>();
public int CategoryId { get; set; }
public IEnumerable<SelectListItem> CategoryName { get; set; } = new List<SelectListItem>();
public int SubcategoryId { get; set; }
public IEnumerable<SelectListItem> SubcategoryName { get; set; } = new List<SelectListItem>();
}
}
Initialization with new List<SelectListItem>(); is important so you don't get NULL reference error.
CONTROLLER
Under // GET: ProductInfoes/Create add logic for getting data from database and loading it to ProductInfoViewModel object :
public IActionResult Create() {
// Hooks on databse to get list of Products
var products = (from m in _context.Product
select new SelectListItem
{
Value = m.ProductId.ToString(),
Text = m.ProductName
}).ToList();
// Create new instance of our ViewModels.ProductInfoViewModel(()
var vm = new ViewModels.ProductInfoViewModel();
// Fill in ProductName in ProductInfoViewModel with list fetch from database
vm.ProductName = new SelectList(products, "Value", "Text").Where(products => products != null);
// Do same thing to Category and Subcategory
var categories = (from m in _context.Category
select new SelectListItem{
Value = m.CategoryId.ToString(),
Text = m.CategoryName
}).ToList();
vm.CategoryName = new SelectList(categories, "Value", "Text").Where(categories => categories != null);
var subcategories = (from m in _context.Subcategory
select new SelectListItem
{
Value = m.SubcategoryId.ToString(),
Text = m.SubcategoryName
}).ToList();
vm.SubcategoryName = new SelectList(subcategories, "Value", "Text").Where(subcategories => subcategories != null);
//Pass that model to View
return View(vm);
}
VIEW
than go to Views/ProductInfoes/Create.cshtml file.
Add following code:
#model ProductTest.ViewModels.ProductInfoViewModel
#{
ViewData["Title"] = "Create";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h1>Create</h1>
<h4>ProductInfo</h4>
<hr />
<form asp-action="Create">
#Html.DropDownListFor(model => Model.ProductId, Model.ProductName, "--Select--")
#Html.DropDownListFor(model => Model.CategoryId, Model.CategoryName, "--Select--")
#Html.DropDownListFor(model => Model.SubcategoryId, Model.SubcategoryName, "--Select--")
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary" />
</div>
</form>
<div>
<a asp-action="Index">Back to List</a>
</div>
#section Scripts {
#{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
If you go to your Create page, something like:
https://localhost:44382/ProductInfoes/Create
and you will get this:
Click Create after you choose from dropdown. And if you go back to index page you'll see that product is successfully added on the list.
I'm trying to create a dropdown list and I get nothing in the dropdown except 'Please select one' but no values.
I've tried this so far:
View:
<select asp-for="StatusToEdit.Color.Name" asp-items="Model.AvailableColors">
<option>Please select one</option>
</select>
Model:
public SelectList AvailableColors { get; set; }
public void OnGet()
{
AvailableColors = new SelectList(nameof(StatusColor.ColorId),
nameof(StatusColor.Name));
}
Mocklist:
private static List<ItemStatus> _mockStatuses = new List<ItemStatus>
{
new ItemStatus { StatusId = 1, Name = "Complete", Color = new
StatusColor{ ColorId = 1, Name = "Auto" } },
new ItemStatus { StatusId = 2, Name = "Complete, Ongoing", Color =
new StatusColor{ ColorId = 2, Name = "Green" }},
new ItemStatus { StatusId = 3, Name = "In Process", Color = new
StatusColor{ ColorId = 3, Name = "Yellow" }}
};
I expect to see all of the colors to show in the dropdown.
if you are using asp.net core MVC (Razor Pages is similar), you need to modify the model property type to IEnumerable<SelectListItem> AvailableColors or List<SelectListItem> AvailableColors
1.Model
public class ManageStatusesEditViewModel
{
//other properties
public List<SelectListItem> AvailableColors { get; set; }
}
2.Action:
[HttpGet]
public IActionResult ManageStatusesEdit(int id)
{
//other logic
var dropdownData = new List<SelectListItem>();
_mockStatuses.ForEach(d => dropdownData.Add(new SelectListItem()
{
Value = d.Color.Name,
Text = d.Color.Name
}));
var editManageStatusesEditViewModel = new ManageStatusesEditViewModel
{
AvailableColors = dropdownData
};
return View(editManageStatusesEditViewModel);
}
3.View:
<select asp-for="StatusToEdit.Color.Name" asp-items="Model.AvailableColors">
<option>Please select one</option>
</select>
4.Result:
You should create your list as below:
ViewBag.ColorList = new SelectList(_mockStatuses.Select(d=> { return new SelectListItem { Text = d.Color.Name, Value = d.Color.ColorId.ToString() }; }),"Value","Text");
I used ViewBag for it but you can obviously pass it to your view by a ViewModel if you prefer.
For your View you should do something as below:
<select asp-items="#ViewBag.ColorList" asp-for="StatusToEdit.Color.Name" >
<option value="">Please select one</option>
</select>
Please make sure you have added Value and Text at the end of SelectList when creating it.
I have one static dropdown list in my view
<select>
<option value="volvo">Volvo</option>
<option value="saab">Saab</option>
<option value="mercedes">Mercedes</option>
<option value="audi">Audi</option>
</select>
I want to bind this drop down list with my model so that when I submit selected value of drop down , I get that value from model in my controller.
Also I want to select option in drop down as per data in my model.
I just made list item for static dropdown list & passed it to dropdown list where I binded it with my viewmodel.
#{
var listItems = new List<ListItem> {new ListItem {Text = "Single", Value = "Single"}, new ListItem {Text = "Married", Value = "Married"}, new ListItem {Text = "Divorse", Value = "Divorse"}};
}
#Html.DropDownListFor(model => model.EmployeeDetail.MaritalStatus, new SelectList(listItems),"-- Select Status --")
It works perfectly for me as it shows value which comes from model & also stores value of dropdown list in model when I submit data.
Instead of constructing the drop down list in HTML, build it in your service/controller and add it to your model:
ViewModel:
public class YourViewModel
{
public string SelectedCarManufacturer { get; set; }
public Dictionary<string, string> CarManufaturers { get; set; }
// your other model properties
}
Controller get action method
[HttpGet]
public ActionResult SomeAction()
{
var model = new YourViewModel
{
SelectedCarManufacturer = null, // you could get this value from your repository if you need an initial value
CarManufaturers = new Dictionary<string, string>
{
{ "volvo", "Volvo" },
{ "saab", "Saab" },
{ "audi", "Audi" },
/// etc.
}
};
return this.View(model);
}
In your view, replace the hard coded drop down list with:
#Html.DropDownListFor(m => m.SelectedCarManufacturer , new SelectList(Model.CarManufaturers , "Key", "Value"), "Select a manufacturer...")
Controller post action method
[HttpPost]
public ActionResult SomeSaveAction(YourViewModel model)
{
// do something with the model...
// model.SelectedCarManufacturer
}
OR
[HttpPost]
public ActionResult SomeSaveAction()
{
var model = someService.BuildYourViewModel()
this.TryUpdateModel(model);
// do something with the model...
someService.SaveYourViewModel(model);
}
I hope this helps...
in controller
List<SelectListItem> items = new List<SelectListItem>();
items.Add(new SelectListItem { Text = "Volvo", Value = "volvo"});
items.Add(new SelectListItem { Text = "Saab", Value = "saab" });
items.Add(new SelectListItem { Text = "Mercedes", Value = "mercedes" });
items.Add(new SelectListItem { Text = "Audi", Value = "audi" });
on view
Html.DropDownListFor(Model.items)
I have this VM properties
public IList<Guid> SelectedEligiableCategories { get; set; }
public IList<SelectListItem> EligiableCategories { get; set; }
I have this helpers in my view
#Html.LabelFor(x => x.EligibleCategoryFrmVm.SelectedEligiableCategories, "Eligible Categories:")
#Html.ListBoxFor(x => Model.EligibleCategoryFrmVm.SelectedEligiableCategories, Model.EligibleCategoryFrmVm.EligiableCategories, new { #class = "eligibleCategoryListBox" })
I have this code in my controller
List<SelectListItem> eligibleCategoriesListItems = Mapper.Map<List<EligibleCategory>, List<SelectListItem>>(eligibleCategories);
foreach (var rewardTier in creditCard.RewardTiers)
{
CbRewardTierFrmVm rewardTierFrmVm = new CbRewardTierFrmVm();
rewardTierFrmVm.EligibleCategoryFrmVm.EligiableCategories = eligibleCategoriesListItems;
foreach (var ec in rewardTier.EligibleCategories)
{
rewardTierFrmVm.EligibleCategoryFrmVm.SelectedEligiableCategories.Add(ec.Id);
}
vm.CbRewardTierFrmVm.Add(rewardTierFrmVm);
}
Yet when I load up my view. None of values for my ListBox are selected. I am not sure why. If this was a selectList this would work as it would match up the SelectedEligiableCategories to the value in the list.
I am not sure if this is because there is multiple selects
Edit
<select name="CbRewardTierFrmVm[63b504c0-0f9a-47ba-a8ff-db85f48d5f0f].EligibleCategoryFrmVm.SelectedEligiableCategories" multiple="multiple" id="CbRewardTierFrmVm_63b504c0-0f9a-47ba-a8ff-db85f48d5f0f__EligibleCategoryFrmVm_SelectedEligiableCategories" data-val-required="Must choose at least one eligible category." data-val="true" class="eligibleCategoryListBox ui-wizard-content ui-helper-reset ui-state-default" style="display: none;">
<option value="ed2bb5f9-4565-4f69-ab15-9fca011c0692">Gas</option>
</select>
Do you think it is because I am using http://blog.stevensanderson.com/2010/01/28/editing-a-variable-length-list-aspnet-mvc-2-style/ ?
Edit2
I gone ahead and make an example. I must be missing something(not sure what). When I use "Darin Dimitrov" it works.
I switched the example to a dropdown as I am getting the same problem with it as well.
In this example I am not using a viewmodel since my initial assumption was somehow the helper I was using from Steven Sanders might be effecting it so I was going off his example.
This does not seem to be the case as I removed it and still get this problem.
public class Gift
{
public string Name { get; set; }
public double Price { get; set; }
public string SelectedItem { get; set; }
public IList<SelectListItem> Items { get; set; }
}
public ActionResult Index()
{
List<SelectListItem> items = new List<SelectListItem>
{
new SelectListItem {Value = "",Text ="--"},
new SelectListItem {Value = "1",Text ="1"},
new SelectListItem {Value = "2",Text ="2"},
};
var initialData = new[] {
new Gift { Name = "Tall Hat", Price = 39.95, Items = items, SelectedItem = "2" },
new Gift { Name = "Long Cloak", Price = 120.00, Items = items, SelectedItem = "1" }
};
return View("Index3",initialData);
}
#model IList<EditorDemo.Models.Gift>
#{
ViewBag.Title = "Index3";
}
#for (int i = 0; i < Model.Count; i++)
{
#Html.DropDownListFor(x => x[i].SelectedItem, new SelectList(Model[i].Items, "Value", "Text"))
}
It seems to not be able to handle when you put it in forloop and try it make more than one dropdown list.
The following works for me.
Model:
public class MyViewModel
{
public IList<Guid> SelectedEligiableCategories { get; set; }
public IList<SelectListItem> EligiableCategories { get; set; }
}
Controller:
public class HomeController : Controller
{
public ActionResult Index()
{
var model = new MyViewModel
{
SelectedEligiableCategories = new[]
{
// preselect the second and the fourth item
new Guid("35830042-3556-11E1-BCDC-A6184924019B"),
new Guid("4253876A-3556-11E1-BC17-B7184924019B")
}.ToList(),
EligiableCategories = new[]
{
new SelectListItem { Value = "2DA62E3A-3556-11E1-8A0A-9B184924019B", Text = "item 1" },
new SelectListItem { Value = "35830042-3556-11E1-BCDC-A6184924019B", Text = "item 2" },
new SelectListItem { Value = "3D07EBAC-3556-11E1-8943-B6184924019B", Text = "item 3" },
new SelectListItem { Value = "4253876A-3556-11E1-BC17-B7184924019B", Text = "item 4" },
}
};
return View(model);
}
}
View:
#model MyViewModel
#using (Html.BeginForm())
{
#Html.ListBoxFor(
x => x.SelectedEligiableCategories,
Model.EligiableCategories,
new { #class = "eligibleCategoryListBox" }
)
}
Result:
UPDATE:
Now that you have shown an example allowing to illustrate the problem, you could specify the selected item when building the SelectList:
#Html.DropDownListFor(
x => x[i].SelectedItem,
new SelectList(Model[i].Items, "Value", "Text", Model[i].SelectedItem)
)
The reason a value was not preselected was because you were binding the dropdownlist to a list of properties (x => x[i].SelectedItem) whereas in my example I was using a simple property.
And if you wanted to do this with the ListBoxFor helper you could use the following:
#Html.ListBoxFor(
x => x[i].SelectedItems,
new MultiSelectList(Model[i].Items, "Value", "Text", Model[i].SelectedItems)
)
The SelectedItems property becomes a collection and we use a MultiSelectList instead of a SelectList.
The main problem is using
#Html.DropDownListFor
instead of this
#Html.ListBoxFor
Using the DropDownListFor will NOT help you with multiple values, whatever you do and no matter what your model is. Once you use ListBoxFor ... it will automatically just work !
Working on my first ASP.Net MVC2 web app recently, I came across some issues when I needed to select multiple values in a list box. I worked around it with some jQuery, but went ahead and put together some very simple code to demonstrate. I'm using EF for the model, with two entities - Customers and HelpDeskCalls:
Controller:
public ActionResult Edit(int id)
{
Customer currCustomer = ctx.Customers.Include("HelpDeskCalls").Where(c => c.ID == id).FirstOrDefault();
List<HelpDeskCall> currCustCalls = (ctx.HelpDeskCalls.Where(h => h.CustomerID == id)).ToList();
List<SelectListItem> currSelectItems = new List<SelectListItem>();
List<String> selectedValues = new List<string>();
foreach (HelpDeskCall currCall in currCustCalls)
{
bool isSelected = (currCall.ID % 2 == 0) ? true : false;
//Just select the IDs which are even numbers...
currSelectItems.Add(new SelectListItem() { Selected = isSelected, Text = currCall.CallTitle, Value = currCall.ID.ToString() });
//add the selected values into a separate list as well...
if (isSelected)
{
selectedValues.Add(currCall.ID.ToString());
}
}
ViewData["currCalls"] = (IEnumerable<SelectListItem>) currSelectItems;
ViewData["currSelected"] = (IEnumerable<String>) selectedValues;
return View("Edit", currCustomer);
}
View:
<div class="editor-field">
<%: Html.ListBoxFor(model => model.HelpDeskCalls, new MultiSelectList(Model.HelpDeskCalls, "ID", "CallTitle", (IEnumerable) ViewData["currSelected"]), new { size = "12" })%>
<%: Html.ListBoxFor(model => model.HelpDeskCalls, ViewData["currCalls"] as IEnumerable<SelectListItem>, new { size = "12"}) %>
<%: Html.ListBox("Model.HelpDeskCalls", new MultiSelectList(Model.HelpDeskCalls, "ID", "CallTitle", (IEnumerable)ViewData["currSelected"]), new { size = "12"}) %>
<%: Html.ValidationMessageFor(model => model.HelpDeskCalls) %>
</div>
For this sample, I'm just selecting HelpDeskCall.IDs which are even. I'm trying two different syntaxes for ListBoxFor: One uses an IEnumerable of values for selections, one using an IEnumerable of SelectListItems. By default, when I run this code, no selections are made to either ListBoxFor, but the non-strongly typed ListBox selects correctly.
I read this post on ASP.Net and this thread on SO, but no joy. In fact, if I add the override ToString() to my HelpDeskCall class (as suggested in the ASP.net thread) all values are selected, which isn't right either.
If someone could shed some light on how this should work (and what I'm missing or doing wrong), this then neophyte would be very grateful.
Here's an example illustrating the strongly typed version:
Model:
public class MyViewModel
{
public int[] SelectedItemIds { get; set; }
public MultiSelectList Items { get; set; }
}
Controller:
public class HomeController : Controller
{
public ActionResult Index()
{
// Preselect items with id 1 and 3
var selectedItemIds = new[] { 1, 3 };
var model = new MyViewModel
{
Items = new MultiSelectList(
new[]
{
// TODO: Fetch from your repository
new { Id = 1, Name = "item 1" },
new { Id = 2, Name = "item 2" },
new { Id = 3, Name = "item 3" },
},
"Id",
"Name",
selectedItemIds
)
};
return View(model);
}
}
View:
<%: Html.ListBoxFor(x => x.SelectedItemIds, Model.Items) %>
I don't know if this behaviour has changed in the RTM of MVC3 that I'm using, but it seems that selection and binding now works out of the box. The only catch is that the model should contain a property with the IDs, like that :
public class MyViewModel {
public int[] ItemIDs { get; set; }
}
Then the following in the view would work fine, both pre-selecting the correct values and binding correctly during post:
#Html.ListBoxFor(model => model.ItemIDs, (IEnumerable<SelectListItem>)(new[] {
new SelectListItem() { Value = "1", Text = "1" },
new SelectListItem() { Value = "2", Text = "2" }
}))
I have found better workaround. Usual way to preselect select list:
#Html.ListBoxFor(
model => model.Roles,
new MultiSelectList(db.Roles, "Id", "Name")
)
#Html.ValidationMessageFor(model => model.Roles)
Doesn't work.., never ever any option is selected, until:
public ActionResult Edit(int id)
{
var user = db.Users.Find(id);
// this is workaround for http://aspnet.codeplex.com/workitem/4932?ProjectName=aspnet
ViewData["Roles"] = user.Roles.Select(r => r.Id);
return View(user);
}
Selected Roles has to be stored in ViewData, to workaround nasty bug.
Another option is to take advantage of nameof, you could do something like this;
Html.ListBox(nameof(MainProjectViewModel.Projects), Model.Projects)