Bear with me, I am new to MVC. I am trying to populate a dropdown list from a list function that retrieves integer values from the database. The dropdown currently shows the model name instead of values.
In my model, I have two functions for the AgentId I am trying to receive and have tried using both. I am uncertain on how this should actually be called.
Edit: I made a few changes and the list is now populating but I am unable to update the selected value. When I try to submit, I am getting this error in my view: "System.InvalidOperationException: 'There is no ViewData item of type 'IEnumerable' that has the key 'AgentId'.'"
Below is my updated code:
// customer model
[DisplayName("Agent")]
public int AgentId { get; set; }
// list function
public static List<int> GetAgentIdDropdown()
{
List<int> agentIdList = new List<int>();
string getAgentIdQuery = #"SELECT AgentId FROM Agents";
using (SqlConnection con = TravelExpertsConn.GetConnection())
{
using (SqlCommand cmd = new SqlCommand(getAgentIdQuery, con))
{
con.Open();
SqlDataReader reader = cmd.ExecuteReader();
Customer Agents = new Customer();
while (reader.Read())
{
Agents.AgentId = Convert.ToInt32(reader["AgentId"]);
agentIdList.Add(Agents.AgentId);
}
con.Close();
}
}
return agentIdList;
}
// view
#Html.DropDownListFor(model => model.AgentId, (IEnumerable<SelectListItem>)ViewBag.AgentId, "Select Agent ID", new { #class = "form-control" })
// controller
public ActionResult Edit()
{
int id = Convert.ToInt32(Session["CustomerId"]);
Customer currentCust = CustomerDB.CustomerDetails(id);
ViewBag.AgentId = new SelectList(CustomerDB.GetAgentIdDropdown(), currentCust.AgentId);
return View(currentCust);
}
// POST: Customer/Edit/5
[HttpPost]
public ActionResult Edit(Customer customer)
{
if (ModelState.IsValid)
{
try
{
int id = Convert.ToInt32(Session["CustomerId"]);
CustomerDB.EditCustomer(id, customer);
return RedirectToAction("Details");
}
catch
{
return View();
}
}
else
{
return View();
}
}
The DropDownListFor() method parameters should consist of first, the attribute of the model representing the selection by the user, which I'm guessing is your model.AgentId. The second parameter should be the IEnumerable property of the Model containing the options, or model.Agents
So one issue then is this line:
#Html.DropDownListFor(model => model.AgentId, ViewBag.Agents as SelectList, new { #class = "form-control" })
should look more like this:
#Html.DropDownListFor(model => model.AgentId, model.Agents, new { #class = "form-control" })
But more so, the model.Agents property is not going to actually return anything without a backing property. Here's a good example I've found online:
public class ViewModel
{
private readonly List<IceCreamFlavor> _flavors;
[Display(Name = "Favorite Flavor")]
public int SelectedFlavorId { get; set; }
public IEnumerable<SelectListItem> FlavorItems
{
get { return new SelectList(_flavors, "Id", "Name");}
}
}
Notice how FlavorItems get method is returning a SelectList of the _flavors. Currently the code you've provided doesn't show any assignment of your GetAgentIdDropDown() to any property of the model, so I would try to match your Model's code with the convention I provided above, assign the results of GetAgentIdDropDown() to the backing property, and change the parameters of the #Html.DropDownListFor() as well.
I found the example from this link if you want to check it out. It's a bit older but there's only a minor difference in syntax.
Related
This question already has answers here:
The ViewData item that has the key 'XXX' is of type 'System.Int32' but must be of type 'IEnumerable<SelectListItem>'
(6 answers)
Closed 6 years ago.
In my MVC project I pass list of currencies to the view within the drop down list. However, once I try to post the view I get the following exception:
The ViewData item that has the key 'FromCurrencyId' is of type
'System.Int32' but must be of type 'IEnumerable'.
Currency Controller
namespace Project.Controllers
{
public class CurrencyController : Controller
{
[HttpGet]
// GET: Currency
public ActionResult Index()
{
CurrenciesClient Cur = new CurrenciesClient();
var listCurrency = Cur.findAll().ToList();
Currencies model = new Currencies();
model.FromCurrencies = new SelectList(listCurrency, "Id", "CurrencyName");
model.ToCurrencies = new SelectList(listCurrency, "Id", "CurrencyName");
return View(model);
}
[HttpPost]
public ActionResult Index(Currencies cur)
{
if (ModelState.IsValid)
{
if (cur.FromCurrencyId == cur.ToCurrencyId)
{
//do something if same currecnies and return.
ModelState.AddModelError("CurrencyCountry", "Can't make the conversion for the same value");
}
else
{
some code .....
}
}
return View(cur);
}
}
}
Currencies VM
namespace Project.ViewModels
{
public class Currencies
{
public int Id { get; set; }
[Required]
public int FromCurrencyId { get; set; }
public SelectList FromCurrencies { get; set; }
[Required]
public int ToCurrencyId { get; set; }
public SelectList ToCurrencies { get; set; }
public string CurrencyName { get; set; }
public string CurrencyCountry { get; set; }
[Required]
public decimal ConversionRate { get; set; }
public decimal Value { get; set; }
public SelectList AvailableCurrencies { get; set; }
}
}
CurrencyClient web service VM
namespace Project.ViewModels
{
public class CurrenciesClient
{
private string base_Url = "http://localhost:51646/api/";
public IEnumerable<Currencies> findAll()
{
try
{
HttpClient client = new HttpClient();
client.BaseAddress = new Uri(base_Url);
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
HttpResponseMessage response = client.GetAsync("currencies").Result;
if (response.IsSuccessStatusCode)
{
var resposeData = response.Content.ReadAsStringAsync().Result;
var Currency = JsonConvert.DeserializeObject<List<Currencies>>(resposeData);
return Currency;
}
return null;
}
catch
{
return null;
}
}
}
}
Index View
model Project.ViewModels.Currencies
#{
ViewBag.Title = "Index";
}
#using (Html.BeginForm("Index", "Currency", FormMethod.Post))
{
#Html.TextBoxFor(m => m.ConversionRate, new { #size = "5" })
#Html.DropDownListFor(m => m.FromCurrencyId, Model.FromCurrencies as IEnumerable<SelectListItem>)
#Html.DropDownListFor(m => m.ToCurrencyId, Model.ToCurrencies as IEnumerable<SelectListItem>)
<button type="submit" class="btn btn-primary">Convert</button>
}
This problem is becuase you are passing null value from your dropdown.Means you are not selecting any value. Check that if you will pass some value from dropdown it will work fine.To solve this problem you need to add the same
code
Currencies model = new Currencies();
model.FromCurrencies = new SelectList(listCurrency, "Id", "CurrencyName");
model.ToCurrencies = new SelectList(listCurrency, "Id", "CurrencyName");
in your controller index post method.Because if selectListitem would be null the following code will be executed
IEnumerable<SelectListItem> selectList = o as IEnumerable<SelectListItem>;
if (selectList == null)
{
throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture,
MvcResources.HtmlHelper_WrongSelectDataType,
name, o.GetType().FullName, "IEnumerable<SelectListItem>"));
}
Which will throw exception.
(It would be better if you will use
ViewBag.FromCurrencies = new SelectList(listCurrency, "Id", "CurrencyName");
like this.)
The better description is given here:
The ViewData item that has the key 'XXX' is of type 'System.Int32' but must be of type 'IEnumerable<SelectListItem>'
The whole explanation is also given in this link like how does this code work.
you will have to set the Dropdown data in the post controller as well, otherwise it will not be able to find the ViewBag values, as a result when after post action it calls the Index view, the ViewBag.FromCurrencies and ViewBag.ToCurrencies will be null which is obviously that we don't want.
For fixing the error, you will have to change you post action to be like:
[HttpPost]
public ActionResult Index(Currencies cur)
{
if (ModelState.IsValid)
{
if (cur.FromCurrencyId == cur.ToCurrencyId)
{
//do something if same currecnies and return.
ModelState.AddModelError("CurrencyCountry", "Can't make the conversion for the same value");
}
else
{
some code .....
}
}
CurrenciesClient Cur = new CurrenciesClient();
var listCurrency = Cur.findAll().ToList();
Currencies model = new Currencies();
model.FromCurrencies = new SelectList(listCurrency, "Id", "CurrencyName");
model.ToCurrencies = new SelectList(listCurrency, "Id", "CurrencyName");
return View(cur);
}
You should not return the view from your POST action or you will run into many issues. Here is the problem with that:
When you submit the form the URL is pointing to your Index. The body of your http post will have a bunch of Currencies items in it. Therefore, MVC, will submit the form to your Index method with Currencies cur parameter.
If all is well you return the same view.
If you refresh the page, your browser will simply reissue the last request and guess what, it will submit the form again. But this is not what you intended by refreshing. You wanted to get the form as it was originally presented not resubmit it.
Therefore instead of returning a view from a POST, you should always, except if AJAX was used, return a redirect.
In this case if all goes well, you may want to send the user to a success page or some other page so you should do this:
return RedirectToAction("YourActionName", "YourControllerName");
This pattern is called the PRG pattern. What this does is this:
user submits a form
If all goes well on the server side, you tell the browser to issue another request and get another page.
The browser gets the other page which may be a success page.
Now the user is on the success page. If they hit refresh they will get the success page again. They will not be submitting the same form over and over.
i have some issues with default value of my dropdownlist when returning my model to view in case of one or many errors. I have a dropdownlist in the view which is filled from the controller and others empty dropdownlists in the same view which are filled with JSON on selection of the first dropdownlist.
public ActionResult Countriesdata()
{
CountrydetailsViewModel vm= new CountrydetailsViewModel();
vm.countries= dal.countries().Select(x => new SelectListItem { Text = x.Name, Value = x.CountryID.ToString() })
.ToList();
return View(vm);
}
here, dal is my data access layer and allows me to fill the list of countries from the database. The code use to fill the countries list in the view is like this
#Html.DropDownListFor(m => m.selectedcountry, new SelectList(Model.countries, "Value", "Text", Model.selectedcountry), "-Select a Country-", new { #class = "ddlist" })
one of the empty dropdowlists is as the one below
#Html.DropDownListFor(m => m.selectedtown, new SelectList(Enumerable.Empty<SelectListItem>(), "Value", "Text", Model.selectedtown), "-Select a Town/City-", new { #class = "ddlist" })
This code work very well i reach the page for the first time because i have set a default value for country dropdownlist which is select a country. i use the following code to post my form.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Countriesdata(CountrydetailsViewModel returnmodel)
{
if (! ModelState.IsValid)
{
returnmodel.countries= dal.countries().Select(x => new SelectListItem { Text = x.Name, Value = x.CountryID.ToString() })
.ToList();
return View(returnmodel);
}
return RedirectToAction("mainpage");
}
If the form contains errors, my model is returned back to my view with the posted value of country selected dropdownlist as default, which is not my goal because the others dropdowlists which are filled using JSON on the country dropdownlist selection change are empty.Thus, I ought to select this same country once to fill the others dropdowlists, which is cumbersome. To be logic, i would like to send back my model to my view with default value of the dropdowlist of country when an error occurs. I am using MVC4 and VS 2010
You need to populate both SelectList's in the controller methods so they get passed to the view. In the GET method, the 2nd one will be an empty SelectList (assuming its a 'Create' metod), but in the POST method it will be populated based on the country that has been selected.
You model should include
public class CountrydetailsViewModel
{
[Required(Error Message = "..")]
public int? SelectedCountry { get; set; }
[Required(Error Message = "..")]
public int? SelectedTown { get; set; }
....
public IEnumerable<SelectListItem> CountryList{ get; set; }
public IEnumerable<SelectListItem> TownList { get; set; }
}
And your controller methods
public ActionResult Countriesdata()
{
CountrydetailsViewModel vm = new CountrydetailsViewModel();
ConfigureViewModel(vm);
return View(vm);
}
[HttpPost]
public ActionResult Countriesdata(CountrydetailsViewModel returnmodel)
{
if(!ModelState.IsValid)
{
ConfigureViewModel(returnmodel);
return View(returnmodel);
}
.... // save and redirect
}
private ConfigureViewModel(CountrydetailsViewModel model)
{
var countries = dal.countries();
model.CountryList= countries.Select(x => new SelectListItem
{
Text = x.Name,
Value = x.CountryID.ToString()
});
if (model.SelectedCountry.HasValue)
{
// adjust query to suit your property names
var towns = db.towns.Where(e => e.CountryId == model.SelectedCountry);
model.TownList = towns.Select(x => new SelectListItem
{
Text = x.Name,
Value = x.TownID.ToString()
});
}
else
{
model.TownList = new SelectList(Enumerable.Empty<SelectListItem>());
}
}
This also allows you to generate the correct options and default selections when editing an existing CountrydetailsViewModel.
Then in the view, use
#Html.DropDownListFor(m => m.SelectedCountry, Model.CountryList, "-Select a Country-", new { #class = "ddlist" })
#Html.ValidationMessageFor(m => m.SelectedCountry)
#Html.DropDownListFor(m => m.SelectedTown, Model.TownList, "-Select a Country-", new { #class = "ddlist" })
#Html.ValidationMessageFor(m => m.SelectedTown)
Note that there is no point creating an identical SelectList from the original one you passed to the view by using new SelectList(..) - its just unnecessary extra overhead. Note also that the last parameter in the SelectList constructor is ignored when your binding to a model property (internally the method builds its own SelectList based on the value of the property) - you could put whatever value you wanted as the last parameter and you will see that the option is still correct selected based on the value of the property.
I am using MVC5, Razor, Entity Framework, C#. I am trying to pass a value of a dorpdown list using a link.
my model is
public class TestVM
{
public string TheID { get; set; }
}
I am loading an enum into a IEnumerable<SelectListItem>.
My enum is
public enum DiscountENUM
{
SaleCustomer,
SaleCustomerCategory,
SaleProduct,
SaleProductCategory,
SaleCustomerAndProduct,
SaleCustomerAndProductCategory,
SaleCustomerCategoryAndProductCategory,
PurchaseVendor,
PurchaseVendorAndProduct,
PurchaseVendorAndProductCategory,
PurchaseProduct,
PurchaseProductCategory,
Unknown
}
I am using the index method of the home controller
public ActionResult Index()
{
ViewBag.ListOfDiscounts = SelectListDiscountENUM();
TestVM d = new TestVM();
return View(d);
}
Where I load the ListOfDiscounts using:
private IEnumerable<SelectListItem> SelectListDiscountENUM()
{
List<SelectListItem> selectList = new List<SelectListItem>();
var listOfEnumValues = Enum.GetValues(typeof(DiscountENUM));
if (listOfEnumValues != null)
if (listOfEnumValues.Length > 0)
{
foreach (var item in listOfEnumValues)
{
SelectListItem sVM = new SelectListItem();
sVM.Value = item.ToString();
sVM.Text = Enum.GetName(typeof(DiscountENUM), item).ToString();
selectList.Add(sVM);
}
}
return selectList.OrderBy(x => x.Text).AsEnumerable();
}
My create method which is called from the view is
public ActionResult Create(TestVM d, string TheID)
{
return View();
}
My Index view is
#model ModelsClassLibrary.Models.DiscountNS.TestVM
<div>#Html.ActionLink("Create New", "Create", new { TheID = Model.TheID})</div>
<div>
#Html.DropDownListFor(x => x.TheID, #ViewBag.ListOfDiscounts as IEnumerable<SelectListItem>, "--- Select Discount Type ---", new { #class = "form-control" })
</div>
The problem is in the following line in the View
<div>#Html.ActionLink("Create New", "Create", new { TheID = Model.TheID })</div>
I have tried adding a model with the name of the field as "TheID"... no luck. Also, added a string field in the parameter, no luck. I looked at the FormControl object, and there was nothing in it either! I suspect something has to be added at the Route level in the helper, but I don't know what.
Model.TheID is always null. Even when I select an item in the DropDownListFor.
Does anyone have an idea how I can capture the select value of the DropDownListFor and send it into the Html.ActionLink TheID?
I am bit to new asp.net mvc and using aps.net mvc 5. I have create the below dropdown using html helpers in aps.net mvc. When i submit(post back) the form i want to set the selected index to zero. Here i am using a optionLabel "--select--". I want to set the selected value to that one ("--select--") after post back. How to achieve this. Please help. Thank you.
#Html.DropDownListFor(model => model.TestCategory, new SelectList(#ViewBag.TestCategories, "value", "text"), "-- Select --", new { #class = "form-control input-sm"})
Controller Code
[HttpGet]
public ActionResult Index()
{
var model = new LaboratoryViewModel {
medicaltestlist = new List<MedicalTest>()
};
PopTestCategory();
PopEmptyDropdown();
return View(model);
}
[HttpPost]
public ActionResult Index(LaboratoryViewModel labvm)
{
var test = PopMedicalTests().Where(x => x.TestSerial == Convert.ToInt32(labvm.TestCode)).FirstOrDefault();
if (labvm.medicaltestlist == null)
labvm.medicaltestlist = new List<MedicalTest>();
if(!labvm.medicaltestlist.Any(x=> x.TestSerial == test.TestSerial))
labvm.medicaltestlist.Add(test);
labvm.TestCategory = "";
PopTestCategory();
return View(labvm);
}
public void PopTestCategory()
{
var categorylist = new List<DropDownItem>
{
new DropDownItem{value="Medical",text="Medical"},
new DropDownItem{value="Animal",text="Animal"},
new DropDownItem{value="Food",text="Food"},
new DropDownItem{value="Water",text="Water"}
};
ViewBag.TestCategories = categorylist;
}
public class DropDownItem
{
public int id { get; set; }
public string value { get; set; }
public string text { get; set; }
}
You return the view in you post method so if you selected (say) Animal then that value will be selected when you return the view because the html helpers use the values from ModelState, not the model property. Setting labvm.TestCategory = ""; has no effect. The correct approach is to follow the PRG pattern and redirect to the GET method, however you can make this work by calling ModelState.Clear(); before setting resetting the value of TestCategory although this will clear all ModelState properties and errors and may have other side effects.
Side note: You DropDownItem class seems unnecessary. MVC already has a SelectListItem class designed to work with dropdownlists, and in any case you can replace all the code in your PopEmptyDropdown() method with
ViewBag.TestCategories = new SelectList(new List<string>() { "Medical", "Animal", "Food", "Water" });
and in the view
#Html.DropDownListFor(m => m.TestCategory, (SelectList)#ViewBag.TestCategories, "-- Select --", new { #class = "form-control input-sm"})
If you set the "value" attribute of the top item in the drop down list to something and then pass back a model containing that for the bound property it should work?
I have populated a dropdown list with values from Database Table. The list gets populated with correct table data but all values have ZERO index in the list. Here is the code to fill dropdown list:
//Get
public ActionResult NewBooking()
{
var db = new VirtualTicketsDBEntities2();
IEnumerable<SelectListItem> items = db.Attractions
.ToList()
.Select(c => new SelectListItem
{
Value = c.A_ID.ToString(),
Text = c.Name
});
ViewBag.Attractions = items;
return View();
}
And on Dropdown View Page:
<div class="editor-label">
#Html.LabelFor(model => model.Attraction)
</div>
<div class="editor-field">
#Html.DropDownList("Attractions")
</div>
For example if table have 3 values A,B, and C. These values are appearing in dropdown list but when I get its selected index in POST request function, it always returns ZERO. Here is the POST submit function:
//Post
[HttpPost]
public ActionResult NewBooking(BookingView booking)
{
try
{
BookingManager bookingManagerObj = new BookingManager();
bookingManagerObj.Add(booking);
ViewBag.BookingSavedSucess = "Booking saved!";
return View("WelcomeConsumer","Home");
}
catch
{
return View(booking);
}
}
booking.Attraction is always ZERO even user selected greater than ZERO index item.
Any suggestions?
I would guess that it is because you are getting a collection of SelectListItems back and not an actual SelectList. Try something like:
<div class="editor-field">
#Html.DropDownListFor(model => model.Attraction, new SelectList(ViewBag.Attractions, "Value", "Text");
It's best not to use ViewBag, you should always use a ViewModel.
Say you have a ViewModel like this:
public class AttractionViewModel
{
public int AttractionId { get; set; }
public SelectList Attractions { get; set; }
}
and modify your view like this - I presume you already have a form in there, the relevant bit is the #Html.DropDownListFor(...) and making sure you have the full namespace to the ViewModel if you haven't already included it in the Views web.config file:
#model AttractionViewModel
#using(Html.BeginForm("NewBooking", "ControllerName"))
{
<div class="editor-label">
#Html.LabelFor(model => model.AttractionId)
</div>
<div class="editor-field">
#Html.DropDownListFor(model => model.AttractionId, Model.Attractions)
</div>
<input type="submit" value="Submit">
}
and modify your HttpGet like this:
//Get
public ActionResult NewBooking()
{
var db = new VirtualTicketsDBEntities2();
var items = db.Attractions.ToList();
var attractionIdDefault = 0;// default value if you have one
var vm = new AttractionViewModel {
AttractionId = attractionIdDefault,// set this if you have a default value
Attractions = new SelectList(items, "A_ID", "Name", attractionIdDefault)
}
return View(vm);
}
and create an HttpPost ActionResult like this:
// Post
public ActionResult NewBooking(AttractionViewModel vm)
{
var attractionId = vm.AttractionId; // You have passed back your selected attraction Id.
return View();
}
Then it should work.
I know that you have already selected your answer but here is an alternative way of doing what you did. When I started off with ASP.NET MVC I struggled with SelectListItem and found another way of populating my drop down list. I have stuck to this way ever since.
I always have a view model that I bind to my view. I never send through a domain model, always a view model. A view model is just a scaled down version of your domain model and can contain data from multiple domain models.
I have made some modifications to your code and tips, but like I mentioned, it's just an alternative to what you already have.
Your domain model could look like this. Try and give your property names some meaningful descriptions:
public class Attraction
{
public int Id { get; set; }
public string Name { get; set; }
}
You view model could look something like this:
public class BookingViewModel
{
public int AttractionId { get; set; }
public IEnumerable<Attraction> Attractions { get; set; }
// Add your other properties here
}
Do not have your data access methods in your controllers, rather have a service layer or repository expose this functionality:
public class BookingController : Controller
{
private readonly IAttractionRepository attractionRepository;
public BookingController(IAttractionRepository attractionRepository)
{
this.attractionRepository = attractionRepository;
}
public ActionResult NewBooking()
{
BookingViewModel viewModel = new BookingViewModel
{
Attractions = attractionRepository.GetAll()
};
return View(viewModel);
}
[HttpPost]
public ActionResult NewBooking(BookingViewModel viewModel)
{
// Check for null viewModel
if (!ModelState.IsValid)
{
viewModel.Attractions = attractionRepository.GetAll();
return View(viewModel);
}
// Do whatever else you need to do here
}
}
And then your view will populate your drop down like this:
#model YourProject.ViewModels.Attractionss.BookingViewModel
#Html.DropDownListFor(
x => x.AttractionId,
new SelectList(Model.Attractions, "Id", "Name", Model.AttractionId),
"-- Select --"
)
#Html.ValidationMessageFor(x => x.AttractionId)
I hope this helps.