I'm using this approach using viewmodel for manipulating values inside combo boxes. Now, I'm struggle to select as default value actual selected value used in create action, not the first one from combo. I know this is fourth parameter in SelectList but I do not know how to use actual UserGroupId cause it's give me an error when using like these
var model = new UserViewModel
{
UserGroups = new SelectList(GetAllUsers(), "UserId", "Name", UserGroupId)
}
return View(model);
public class UserViewModel
{
public int UserGroupId { get; set; }
public IEnumerable<SelectListItem> UserGroups { get; set; }
}
Like this:
var model = new UserViewModel
{
// preselct an item in the dropdown whose value equals 5
// This means that inside your `GetAllUsers()` collection you must
// have an element with UserId=5 and this element will automatically be
// preselcted. Here you could put any value of course
UserGroupId = 5,
UserGroups = new SelectList(GetAllUsers(), "UserId", "Name")
};
return View(model);
and in your view:
#model UserViewModel
...
#Html.DropDownListFor(x => x.UserGroupId, Model.UserGroups)
As for the 4th parameter, it needs to be an actual instance that is part of the collection returned by GetAllUsers()
For example:
var users = GetAllUsers();
var defaultUser = users[0];
var model = new UserViewModel
{
UserGroups = new SelectList(users, "UserId", "Name", defaultUser)
};
return View(model);
You may want to manually build your SelectList rather than using the SelectList constructor. That will give you the control you need over handling the "UserId" and the "Name". This also addresses the issue of how to add an integer record id to the select list.
Lets start with a method that builds and returns a List<SelectListItem>
public static List<SelectListItem> UserGroups(int userGroupId, int selectedUserId)
{
var userGroupsQuery = from u in dbContext.Users
where u.UserGroupId == userGroupId
select u;
var userSelectList= new List<SelectListItem>();
foreach (var user in userGroupsQuery.Users)
{
userSelectList.Add(
new SelectListItem
{
Text = userGroup.Name,
Value = userGroup.UserId.ToString(CultureInfo.InvariantCulture),
Selected = (user.UserId == selectedUserId)
};
}
return userSelectList;
}
Now we have a select list that we can use so lets use it in our action method.
int selectedUserId = 1000;
int userGroupId = 5;
var userViewModel = new UserViewModel
{
UserGroups = ListLookup.UserGroups(userGroupId, selectedUserId)
}
return View(userViewModel);
And then in the view.
#Html.DropDownList("UserId", Model.Users)
The key here is that the Value property in the select list must be a string, not an integer. Also note that you cannot call to string inside a lambda expression by default (hence the use of a foreach loop). Most SelectList examples do not show how to use the Selected property to select a particular value in the SelectList item.
I hope this helps :)
Related
In my ASP.NET MVC application, I'm trying to pass some values from controller to view.
For this, I'm using the view bag method.
From the controller, the data is passed to the view.
In the view within the for each loop, it also shows the data in the view bag.
But when it runs, I'm getting an error 'object' does not contain a definition for 'cusName'
This is the controller
var SuggestionList = (from c in db.tbl_Main
where c.Suggestion != null orderby c.CreatedDate descending select new
{
cusName = c.CustomerName,
Suggest = c.Suggestion
}).Take(3).ToList();
ViewBag.suggestList = SuggestionList;
return View();
In the view
#foreach(var data in ViewBag.suggestList) {
<li > #data.cusName < /li>
}
Would suggest creating a model class (concrete type) and returning it as List<ExampleModel> type instead of an anonymous list.
public class ExampleModel
{
public string CusName { get; set; }
public string Suggest { get; set; }
}
var SuggestionList = (from c in db.tbl_Main
where c.Suggestion != null
orderby c.CreatedDate descending
select new ExampleModel
{
CusName = c.CustomerName,
Suggest = c.Suggestion
})
.Take(3)
.ToList();
ViewBag.suggestList = SuggestionList;
return View();
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.
This is something that has always puzzled me as to the best way round, while keeping maintainable code. The below code sets up a list of months and years for a payment gateway form, before assigning these to a variable of type List<SelectListItem>.
Intial Action
PayNowViewModel paymentGateway = new PayNowViewModel();
List<SelectListItem> paymentGatewayMonthsList = new List<SelectListItem>();
List<SelectListItem> paymentGatewayYearsList = new List<SelectListItem>();
for (int i = 1; i <= 12; i++)
{
SelectListItem selectListItem = new SelectListItem();
selectListItem.Value = i.ToString();
selectListItem.Text = i.ToString("00");
paymentGatewayMonthsList.Add(selectListItem);
}
int year = DateTime.Now.Year;
for (int i = year; i <= year + 10; i++)
{
SelectListItem selectListItem = new SelectListItem();
selectListItem.Value = i.ToString();
selectListItem.Text = i.ToString("00");
paymentGatewayYearsList.Add(selectListItem);
}
paymentGateway.ExpiryMonth = paymentGatewayMonthsList;
paymentGateway.ExpiryYear = paymentGatewayYearsList;
return View(paymentGateway);
It's a fair bit of code, and I find myself repeating this code, in similar formats to re-setup the dropdown lists options should the ModelState.IsValid be false and I want to return back to the view for the user to correct there mistakes.
HttpPost Action - Code
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult ConfirmPayment(PayNowViewModel paymentGatewayForm, FormCollection form)
{
if (ModelState.IsValid)
{
// Post processing actions...
return View();
}
else
{
for (int i = 1; i <= 12; i++)
{
SelectListItem selectListItem = new SelectListItem();
selectListItem.Value = i.ToString();
selectListItem.Text = i.ToString("00");
paymentGatewayMonthsList.Add(selectListItem);
}
int year = DateTime.Now.Year;
for (int i = year; i <= year + 10; i++)
{
SelectListItem selectListItem = new SelectListItem();
selectListItem.Value = i.ToString();
selectListItem.Text = i.ToString("00");
paymentGatewayYearsList.Add(selectListItem);
}
form.ExpiryMonth = paymentGatewayMonthsList;
form.ExpiryYear = paymentGatewayYearsList;
return View("MakePayment", form);
}
}
What's the best way to centralise this dropdown setup code so its only in one place? At present you'll see a large proportion (the for loops), is exactly repeated twice. A base controller with function? Or is it better to re-setup like the above?
Any advice appreciated!
Mike.
Add a private method to your controller (the following code assumes your ExpiryMonth and ExpiryYear properties are IEnumerable<SelectListItem> which is all that the DropDownListFor() method requires)
private void ConfigureViewModel(PayNowViewModel model)
{
model.ExpiryMonth = Enumerable.Range(1, 12).Select(m => new SelectListItem
{
Value = m.ToString(),
Text = m.ToString("00")
});
model.ExpiryYear = Enumerable.Range(DateTime.Today.Year, 10).Select(y => new SelectListItem
{
Value = y.ToString(),
Text = y.ToString("00")
});
}
and then in the GET method
public ActionResult ConfirmPayment()
{
PayNowViewModel model = new PayNowViewModel();
ConfigureViewModel(model);
return View(model);
}
and in the POST method
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult ConfirmPayment(PayNowViewModel model)
{
if (!ModelState.IsValid)
{
ConfigureViewModel(model);
return View(model);
}
.... // save and redirect (should not be returning the view here)
}
If the set of your dropdown options is fixed (or recompilation is OK after the potential options change), you can use an enum to store your options.
public enum Month {
// if the dropdown is not required, add default value 0
Optional = 0,
[Display(Name = #"Month_January")]
January = 1,
[Display(Name = #"Month_February")]
February = 2,
// etc ..
}
To render this as a dropdown use an EditorTemplate Enum.cshtml:
#model Enum
#{
var enumType = ViewData.ModelMetadata.ModelType;
var allValues = Enum.GetValues(enumType).Cast<object>().ToSelectList(Model);
// read any attributes like [Required] from ViewData and ModelMetadata ...
var attributes = new Dictionary<string, object>();
}
#Html.DropDownListFor(m => m, allValues, attributes)
The ToSelectList extension method loops over all enum values and converts them to SelectListItems:
public static IList<SelectListItem> ToSelectList<T>(this IEnumerable<T> list) {
return ToSelectList<T>(list, list.FirstOrDefault());
}
public static IList<SelectListItem> ToSelectList<T>(this IEnumerable<T> list, T selectedItem) {
var items = new List<SelectListItem>();
var displayAttributeType = typeof(DisplayAttribute);
foreach (var item in list) {
string displayName;
// multi-language:
// assume item is an enum value
var field = item.GetType().GetField(item.ToString());
try {
// read [Display(Name = #"someKey")] attribute
var attrs = (DisplayAttribute)field.GetCustomAttributes(displayAttributeType, false).First();
// lookup translation for someKey in the Resource file
displayName = Resources.ResourceManager.GetString(attrs.Name);
} catch {
// no attribute -> display enum value name
displayName = item.ToString();
}
// keep selected value after postback:
// assume selectedItem is the Model passed from MVC
var isSelected = false;
if (selectedItem != null) {
isSelected = (selectedItem.ToString() == item.ToString());
}
items.Add(new SelectListItem {
Selected = isSelected,
Text = displayName,
Value = item.ToString()
});
}
return items;
}
To support multiple languages, add translations for the display name keys, e.g. "Month_January", to the Resource file.
Now that the setup code has been abstracted away using some reflection magic, creating a new viewmodel is a breeze :>
public class PayNowViewModel {
// SelectListItems are only generated if this gets rendered
public Month ExpiryMonth { get; set; }
}
// Intial Action
var paymentGateway = new PayNowViewModel();
return View(paymentGateway);
// Razor View: call the EditorTemplate
#Html.EditorFor(m => m.ExpiryMonth)
Note that in the EditorTemplate, Model is passed as the selected item to ToSelectList. After postback, Model will hold the currently selected value. Therefore it stays selected, even if you just return the model after an error in the controller:
// HttpPost Action
if (!ModelState.IsValid) {
return View("MakePayment", paymentGatewayForm);
}
Took us some time to come up with this solution, credits go to the Saratiba team.
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 need the value of SelectListItem to be int.
So I pull it of the database, convert it to string in the process and store to listitem's value.
public class BookAdd
{
public BookAdd()
{
public Book Book { get; set; }
DataModelContainer db = new DataModelContainer();
IEnumerable<SelectListItem> items = db.PublisherSet
.Select(i => new SelectListItem
{
Value = SqlFunctions.StringConvert((double)i.Id),
Text = i.Name
});
}
}
I then need to store the value again as int to Book.PublisherId when selected from dropdownlist. I know the code below is not complete, I figured I need somehow convert the selected item's value to int, how do I do it?
#model Bookshops.Models.BookAdd
...
#Html.DropDownListFor(model => model.Book.PublisherId, Model.items);
And finaly controler:
public ActionResult Create2()
{
var bookAdd = new BookAdd();
ViewBag.Publisher = bookAdd.items;
return View(bookAdd);
}
//
// POST: /Book/Create
[HttpPost]
public ActionResult Create2(BookAdd book)
{
if (ModelState.IsValid)
{
Book book2 = new Book();
book2.Id = book.Book.Id;
book2.AuthorId = book.Book.AuthorId;
book2.Isbn = book.Book.Isbn;
book2.Id = int.Parse(book.Book.PubId);
book2.Title = book.Book.Title;
book2.YearPublished = book.Book.YearPublished;
db.BookSet.Add(book2);
db.SaveChanges();
}
return RedirectToAction("Index");
}
I'm using MVC 3, EF 4.
Instead of building a SelectListItem, I built something like this:
public struct BookItem
{
int id;
string name;
}
Then add the following item to the model and select data into it
IEnumerable<BookItem> bookList {get; set;}
Fianally
#Html.DropDownListFor(model => model.Book.PublicherId, new SelectList(Model.bookList, "id", "name"))
When the selected Item of the DropDownList will get posted back convert it to int from string like this Convert.ToInt32(DropDownSelectedItem) or with Int32.TryParse(value, out number)
to start you can simplify the mapping code to
db
.PublisherSet
.Select(x=> new SelectListItem{Value = x.Id.ToString(), Text = x.Name})
.ToArray();
In the view you can then do
#Html.DropDownListFor(model => model.Book.PublisherId, Model.items);
and it will properly render a select element with the options and the appropriate publisher selected.
You can then use the model binding conventions to pass this back to the controller action when the user clicks the submit button.
In the end, it's all text. that's the nature of the web. we have a lot of tricks, converters and binders to turn string dictionaries into rich view models, but ultimately it's just strings.