Selected value in ViewBag SelectList not working - c#

I'm trying to set the selected value for a dropdown menu in my controller and pass it to my view via ViewBag. When I debug, all the correct values are being passed to the controller and to the view as well, but when it actually renders on the page, the selected value is not set.
View
<tr class="form-group">
<th>
<label for="CompanyID" class="control-label col-md-9">Company</label>
</th>
<td>
#Html.DropDownList("CompanyID", (IEnumerable<SelectListItem>)ViewBag.CompanyID, "", new { #class = "required-validation" })
#Html.ValidationMessageFor(model => model.CompanyID)
</td>
Controller
public ActionResult Edit(int? id) {
if (id == null) {
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
var sale = db.SalesOpportunities.Find(id);
if (sale == null) {
return HttpNotFound();
}
ViewBag.CompanyID = new SelectList(db.Companies, "CompanyID", "Name", sale.CompanyID);
var technicians = db.UserProfiles.Select(t => new {
ID = t.ID,
Name = t.FirstName + " " + t.LastName,
}).OrderBy(t => t.Name);
ViewBag.UserID = new SelectList(technicians, "ID", "Name", sale.UserID);
return View(sale);
}

The name of the field and the name of the variable in ViewBag for your choices cannot be the same. Change it from ViewBag.CompanyID to something like ViewBag.CompanyChoices and it will work.

Related

How to add a List<string> to other strings that filters results in ASP.NET MVC C#?

I have been stuck on this for over a day now and it's driving me nuts. I don't know what I'm doing wrong or what I'm missing. Anyways, I have a Users List page that is using ASP Identity framework. I have some filters at the top of the page that will filter the users by: UserName(txtbox), CompanyName(dropdown), and by Role(dropdown). My current code is filtering it, but it won't filter it with the other criteria's if they use multiple filters (like CompanyName and Role). I had to query a List string to populate my model to display in the view. So my problem I'm having trouble figuring out is how to add the "Roles" (which is the List string) to my controller code that incorporates 3 strings from the filters together. But since Roles is already a string, if I put .ToString() after it, the filter will not show any records when triggered. If I take the .ToString() off of Roles, it works only if I were to filter the Roles by itself, but it will not work with the other filters. Any suggestions on what I can do would be greatly appreciated. Here is my model:
public class UserList
{
public UserList() { }
public ApplicationUser User { set; get; }
public List<string> Roles { get; set; }
}
Here is my view:
#using (Html.BeginForm("ListUsers", "Administrator", FormMethod.Get))
{
<div class="form-inline title">
<h4>Users</h4>
<button class="create-btn"><span class="fa-solid fa-plus"></span> #Html.ActionLink("Create New User", "Register", "Account", new { #class = "CreateLink" })</button>
</div>
<p class="filter-section">
<label class="filter-lbl1">By Company:</label><label class="filter-ddl">#Html.DropDownList("strComp", Extensions.GetCompanyList(), "All", new { #class = "comp-ctl" })</label>
<label class="filter-lbl2">By Role:</label><label class="filter-ddl">#Html.DropDownList("strRole", Extensions.GetRolesList(), "All", new { #class = "role-ctn" })</label><br />
<label class="filter-lbl3">By User Name:</label><label class="filter-txtbox">#Html.TextBox("strName", "", new { #class = "name-ctl" })</label>
<button type="button" class="red" onclick="location.href='#Url.Action("ListUsers", new { })'"><i class="fa solid fa-trash-o"></i> Reset</button>
<button type="submit" class="filter-btn"><i class="fa solid fa-filter"></i> Filter</button>
</p>
}
<table class="table">
<tr>
<th>
Email
</th>
<th>
Company
</th>
<th>
Role
</th>
<th>
Last Login
</th>
<th></th>
</tr>
#foreach (var user in Model)
{
var appUser = user.User;
var roles = user.Roles;
<tr>
<td class="email">
#Html.HiddenFor(modelitem => user.User.Id)
#Html.DisplayFor(modelitem => user.User.Email)
</td>
<td>
#Html.DisplayFor(modelitem => user.User.CompanyName)
</td>
<td>
#Html.DisplayFor(modelitem => user.Roles, string.Join(",", roles))
</td>
<td>
</td>
<td>
#Html.ActionLink("Edit", "EditUser", new { id = appUser.Id }, new { #class = "edit-link" }) |
#Html.ActionLink("Delete", "DeleteUser", new { id = appUser.Id }, new { #class = "delete-link" })
</td>
</tr>
}
</table>
<link href="~/Content/PagedList.css" rel="stylesheet" />
<div></div>
Page #(Model.PageCount < Model.PageNumber ? 0: Model.PageNumber)/#Model.PageCount
#Html.PagedListPager(Model, page => Url.Action("ListUsers", new { page, strComp = ViewData["strFilter1"], strRole = ViewData["strFilter2"], strName = ViewData["strFilter3"] }))
And here is my code for my controller part:
public ActionResult ListUsers(string strFilter1, string strFilter2, string strFilter3, string strComp, string strName, string strRole, int? page)
{
if (strComp != null && strRole != null && strName != null)
{
strFilter1 = strComp;
strFilter2 = strRole;
strFilter3 = strName;
}
ViewBag.strFilter1 = strComp;
ViewBag.strFilter2 = strRole;
ViewBag.strFilter3 = strName;
var user = (from u in context.Users
let query = from ur in context.Set<IdentityUserRole>()
where ur.UserId.Equals(u.Id)
join r in context.Roles on ur.RoleId equals r.Id
select r.Name
select new UserList() { User = u, Roles = query.ToList<string>() })
.ToList();
if (!String.IsNullOrEmpty(strComp))
{
//Filter results based on company selected.
var pageNumber = page ?? 1;
var pageSize = 25;
if (strRole == null && strName == null)
{
var comp = user.Where(u => u.User.CompanyName.ToString().Contains(strComp));
return View(comp.OrderBy(u => u.User.Id).ToPagedList(pageNumber, pageSize));
}
else
{
var compuser = user.Where(u => u.User.CompanyName.ToString().Contains(strComp) &&
u.Roles.ToString().Contains(strRole) &&
u.User.UserName.ToString().Contains(strName));
return View(compuser.OrderBy(u => u.User.Id).ToPagedList(pageNumber, pageSize));
}
}
if (!String.IsNullOrEmpty(strRole))
{
//Filter results based on role selected.
var pageNumber = page ?? 1;
var pageSize = 25;
if (strComp == null && strName == null)
{
var roll = user.Where(u => u.Roles.Contains(strRole));
return View(roll.OrderBy(u => u.User.Id).ToPagedList(pageNumber, pageSize));
}
else
{
var rolluser = user.Where(u => u.Roles.Contains(strRole) &&
u.User.CompanyName.ToString().Contains(strComp) &&
u.User.UserName.ToString().Contains(strName));
return View(rolluser.OrderBy(u => u.User.Id).ToPagedList(pageNumber, pageSize));
}
}
if (!String.IsNullOrEmpty(strName))
{
//Filter results based on the username typed in.
var pageNumber = page ?? 1;
var pageSize = 25;
if (strComp == null && strRole == null)
{
var uname = user.Where(u => u.User.UserName.ToString().Contains(strName));
return View(uname.OrderBy(u => u.User.Id).ToPagedList(pageNumber, pageSize));
}
else
{
var nameuser = user.Where(u => u.User.UserName.ToString().Contains(strName) &&
u.User.CompanyName.ToString().Contains(strComp) &&
u.Roles.ToString().Contains(strRole));
return View(nameuser.OrderBy(u => u.User.Id).ToPagedList(pageNumber, pageSize));
}
}
//var userList = user.OrderBy(u => u.User.Id).ToList();
{
var pageNumber = page ?? 1;
var pageSize = 25;
var userList = user.OrderBy(u => u.User.CompanyName).ToList();
return View(userList.ToPagedList(pageNumber, pageSize));
}
}
The controller is where I believe the issue is. If I leave the .ToString() off of the Roles, its works perfect if I am just filtering by the role. But if I try to filter with anything else, it filters it to empty results. I'm thinking since the Roles is a list string, for some reason it's not able to properly add to the other strings. What's weird is with my current code, it will filter records by UserName and by Role at the same time, but the CompanyName and Role do not work in sync with each other.
I think the bit of logic you want is Enumerable.Any.
e.g. your Where clause for roles can be:
u.Roles.Any(role => role.Contains(strRole)))
this will return true if any of the u.Roles contain the specified strRole string.
Edit: Sidenote, if I've read this correctly, all of your code after the user declaration can be simplified to just:
public ActionResult ListUsers(/*...*/)
{
var user = /*...*/;
var pageNumber = page ?? 1;
var pageSize = 25;
IEnumerable<ApplicationUser> filteredUsers = user;
if (!String.IsNullOrEmpty(strComp)) {
filteredUsers = filteredUsers.Where(u => u.User.CompanyName.ToString().Contains(strComp));
}
if (!String.IsNullOrEmpty(strName)) {
filteredUsers = filteredUsers.Where(u => u.User.UserName.ToString().Contains(strName));
}
if (!String.IsNullOrEmpty(strRole)) {
filteredUsers = filteredUsers.Where(u => u.Roles.Any(role => role.Contains(strRole)));
}
return View(filteredUsers.OrderBy(u => u.User.Id).ToPagedList(pageNumber, pageSize));
}

MVC Dropdown list not showing selected value using ViewBag

I have filled the ViewBag.BankNames using Banks table as
ViewBag.BankNames = new SelectList(db.Banks, "BankId", "BankName", applicationSettings.BankId);
where applicationSettings.BankdId is having the value which I need to show as the selected value in dropdown, then passed the applicationSetting model object to view.
In my View I have used that ViewBag like
#Html.DropDownListFor(model => model.BankId, ViewBag.BankNames as SelectList, "Select Bank", htmlAttributes: new { #class = "form-control" })
It will show me the dropdown list with all values of Bank names but the selected value is Select Bank and not the value which I set as selected value in ViewBag?
Can you assign applicationSettings.BankId to BankId of model property and check..
you are binding #Html.DropDownListFor(model => model.BankId, ViewBag.BankNames as SelectList, "Select Bank", htmlAttributes: new { #class = "form-control" }) but in model.BankId is zero..
Assign Model.BankId = applicationSettings.BankId before returning it to view
In Controller
public ActionResult Index()
{
var banks = new List<Banks>()
{
new Banks()
{
BankId =1,
BankName = "Bank 1"
},
new Banks()
{
BankId =2,
BankName = "Bank 2"
}
};
var list = new SelectList(banks, "BankId", "BankName");
ViewBag.Banks = list;
var modelBanks= new Banks();
modelBanks.BankId = 2;
return View(modelBanks);
}
In View
#model Banks
#using WebApplication3.Models
#Html.DropDownListFor(model => model.BankId, ViewBag.Banks as SelectList,"Select Bank")

How to keep SelectListItem value selected after submitting form in ASP.NET Core?

I'm trying to filter a table using a dropdown-list but I'm not able to keep SelectListItem value selected after submitting the form in ASP.NET Core.
The filter seems to work but whenever the page reloads the selected item is always the same
Get Controller
public ActionResult Index(int myDropDown)
{
var myItems= new List<Items>();
myItems = _myRepository.GetAllItems();
var filteredItems = myItems.Where(x => x.Color.DemoId == myDropDown);
if (myDropDown != 0 )
{
return View(filteredItems);
}
return View(myItems);
}
Index.chtml
<form asp-controller="MyController">
<div class="row">
<div class="form-group col-3">
<select class="form-control" id="exampleFormControlSelect1" name="myDropDown"
asp-items="(_myRepository.GetDemos()).Select(x => new SelectListItem()
{ Text = x.Name, Value = x.Id.ToString()})" onchange="this.form.submit()">
</select>
</div>
</form>
C# Method
public List<Demo> GetDemos()
{
var demos = _dbContext.Demos.Select(x => new Demo()
{
Id = x.Id,
Name = x.Name
}).ToList();
return demos;
}
trying to filter a table using a dropdown-list but I'm not able to keep SelectListItem value selected after submitting the form
You can try to dynamically set Selected property of SelectListItem, like below.
<select class="form-control" id="exampleFormControlSelect1" name="myDropDown"
asp-items="(_myRepository.GetDemos()).Select(x => new SelectListItem()
{ Text = x.Name, Value = x.Id.ToString(), Selected = x.Id == ViewBag.SelectedIndex ? true : false })" onchange="this.form.submit()">
</select>
In action method
public ActionResult Index(int myDropDown)
{
var myItems = new List<Items>();
myItems = _myRepository.GetAllItems();
var filteredItems = myItems.Where(x => x.Color.DemoId == myDropDown);
//pass selected value through ViewBag etc
ViewBag.SelectedIndex = myDropDown;
if (myDropDown != 0)
{
return View(filteredItems);
}
return View(myItems);
}

There is no ViewData item of type 'IEnumerable<SelectListItem>' that has the key 'Department'

I did check all issues about that but I couldn't figure out how to solve it. I need to your help guys.
Here is my code about dropdownlist. When I post the page I got the error message which I mentioned above.
.cshtml
<div class="editor-label">
#Html.LabelFor(model => model.Department, new { #style = " margin-top:12px;display:block;text-align:center" })
</div>
<div class="editor-field">
#Html.DropDownList("Department",(SelectList) ViewBag.Department, new { id = "Department", #class = "form-control", #style = "width:250px; margin-top:5px;margin-left: auto;margin-right:auto; text-align:center;" })
#Html.ValidationMessageFor(model => model.Department.Name)
</div>
model
public ActionResult Index()
{
IEnumerable<SelectListItem> items = db.Department
.Select(c => new SelectListItem
{
Value = c.ID.ToString(),
Text = c.Name
});
var selectList = new SelectList(items,"Value","Text");
ViewBag.Department = selectList;
return View();
}
[HttpPost]
public ActionResult Index(Person model, HttpPostedFileBase photo)
{
if (ModelState.IsValid)
{
Person Person = new Person();
Person.DeparmentID = model.Department.ID;
Person.FirmID = model.Firm.ID;
Person.GraduationDate = model.GraduationDate;
Person.HomeTel = model.HomeTel;
Person.MobileTel = model.MobileTel;
Person.Mail = model.Mail;
Person.Name = model.Name;
Person.Surname = model.Surname;
Person.Position = model.Position;
Person.WorkingSituation = model.WorkingSituation;
if (photo != null && photo.ContentLength > 0)
{
if (photo.ContentLength > 10240)
{
ModelState.AddModelError("photo", "Resim boyutu 10 KB'ı aşamaz.");
return View();
}
var supportedTypes = new[] { "jpg", "jpeg", "png" };
var fileExt = System.IO.Path.GetExtension(photo.FileName).Substring(1);
if (!supportedTypes.Contains(fileExt))
{
ModelState.AddModelError("photo", "Yalnızca jpg, jpeg, png veri tipleri desteklenmektedir.");
return View();
}
var fileName = Path.GetFileName(photo.FileName);
photo.SaveAs(Server.MapPath("~/Upload/") + photo.FileName);
Person.Img = fileName;
}
db.Person.Add(Person);
db.SaveChanges();
ViewBag.ShowConfirmation = "The item was created.";
return RedirectToAction("Index");
}
else
{
return View(model);
}
}
Maybe if you use DropDownListFor instead of DropDownList? You would need to include the listitems in your viewmodel instead of in a viewbag.
Along these lines
#Html.DropDownListFor(m => m.Department, new SelectList(Model.Departments, "Value", "Text", Model.Department), new { #style = "..." })
The second and third parameter of the SelectList constructor define the names of the Value and Text parameters in your SelectListItem list.

better way to load 2 dropdown in mvc

This is how i am loading on page load state and city dropdown:
My Controller method:
This is the first method which is calling when page is loaded.
public ActionResult Index()
{
var states = GetStates();
var cities = Enumerable.Empty<SelectListItem>();
ViewBag.States = states;
ViewBag.Cities = cities;
}
private IEnumerable<SelectListItem> GetStates()
{
using (var db = new DataEntities())
{
return db.States.Select(d => new SelectListItem { Text = d.StateName, Value =d.Id.ToString() });
}
}
[HttpGet]
public ActionResult GetCities(int id)
{
using (var db = new DataEntities())
{
var data = db.Cities.Where(d=>d.StateId==id).Select(d => new { Text = d.CityName, Value = d.Id }).ToList();
return Json(data, JsonRequestBehavior.AllowGet);
}
}
My View:
IEnumerable<SelectListItem> States = ViewBag.States;
IEnumerable<SelectListItem> Cities = ViewBag.Cities;
#Html.DropDownList("State", States, "Select State", new { onchange="loadCities(this)"})
#Html.DropDownListFor(m => m.CityId, Cities, "Select City", new { id="ddlCity"})
function loadCities(obj) {
$.ajax({
url: "/Home/GetCities",
data: { id: $(obj).val() },
contentType:"application/json",
success:function(responce){
var html = '<option value="0">Select City</option>';
$(responce).each(function () {
html += '<option value="'+this.Value+'">'+this.Text+'</option>'
});
$("#ddlCity").html(html);
}
});
}
Any better way then this to load state and city dropdown?
public class HomeController : Controller
{
public ActionResult Index(int id=0)
{
Person model = null;
var states = GetStates().ToList();
var cities = Enumerable.Empty<SelectListItem>();
if (id > 0)
{
using (var db = new DataEntities())
{
model = db.People.Include("City").FirstOrDefault(d => d.Id == id);
if (model == null)
model = new Person();
else
{
states.First(d => d.Value == model.City.StateId.ToString()).Selected = true;
cities = db.Cities.Where(d => d.StateId == model.City.StateId).ToList().Select(d => new SelectListItem { Text = d.CityName,Value=d.Id.ToString(),Selected=d.Id==model.CityId });
}
}
}
else
{
model = new Person();
}
ViewBag.States = states;
ViewBag.Cities = cities;
ViewBag.Persons = GetPersons();
return View(model);
}
[HttpGet]
public ActionResult GetCities(int id)
{
using (var db = new DataEntities())
{
var data = db.Cities.Where(d=>d.StateId==id).Select(d => new { Text = d.CityName, Value = d.Id }).ToList();
return Json(data, JsonRequestBehavior.AllowGet);
}
}
public ActionResult SavePersonDetail([Bind(Exclude = "Id")] Person model)
{
// var employeeDal= new Emploee();
//employee.firstname=model.
if (ModelState.IsValid)
{
var Id = model.Id;
int.TryParse(Request["Id"], out Id);
using (var db = new DataEntities())
{
if (Id > 0)
{
var person = db.People.FirstOrDefault(d => d.Id == Id);
if (person != null)
{
model.Id = Id;
db.People.ApplyCurrentValues(model);
}
}
else
{
db.People.AddObject(model);
}
db.SaveChanges();
}
}
if (!Request.IsAjaxRequest())
{
ViewBag.States = GetStates();
ViewBag.Persons = GetPersons();
ViewBag.Cities = Enumerable.Empty<SelectListItem>();
return View("Index");
}
else
{
return PartialView("_personDetail",GetPersons());
}
}
public ActionResult Delete(int id)
{
using (var db = new DataEntities())
{
var model = db.People.FirstOrDefault(d => d.Id == id);
if (model != null)
{
db.People.DeleteObject(model);
db.SaveChanges();
}
}
if (Request.IsAjaxRequest())
{
return Content(id.ToString());
}
else
{
ViewBag.States = GetStates();
ViewBag.Persons = GetPersons();
ViewBag.Cities = Enumerable.Empty<SelectListItem>();
return View("Index");
}
}
private IEnumerable<SelectListItem> GetStates()
{
using (var db = new DataEntities())
{
return db.States.ToList().Select(d => new SelectListItem { Text = d.StateName, Value =d.Id.ToString() });
}
}
private IEnumerable<Person> GetPersons()
{
using (var db = new DataEntities())
{
return db.People.Include("City").Include("City.State").ToList();
}
}
public ActionResult HomeAjax()
{
ViewBag.States = GetStates();
ViewBag.Cities = Enumerable.Empty<SelectListItem>();
using (var db = new DataEntities())
{
var data = db.States.Include("Cities").Select(d => new { Id = d.Id, Name = d.StateName, Cities = d.Cities.Select(x => new { Id=x.Id,Name=x.CityName}) }).ToList();
ViewBag.CityStateJson = new System.Web.Script.Serialization.JavaScriptSerializer().Serialize(data);
}
ViewBag.Persons = GetPersons();
return View();
}
}
#model IEnumerable<Person>
<div>
<table>
<tr>
<th>
First Name
</th>
<th>
Last Name
</th>
<th>
Email
</th>
<th>
City
</th>
<th>
State
</th>
<th>
Edit
</th>
</tr>
#if (Model.Count() == 0)
{
<tr>
<td colspan="6">
<h3>No data available</h3>
</td>
</tr>
}
else {
foreach (var item in Model) {
<tr data-id="#item.Id">
<td data-id="fn">#item.FirstName</td>
<td data-id="ln">#item.LastName</td>
<td data-id="email">#item.Email</td>
<td data-id="cn">#item.CityName<input type="hidden" value="#item.CityId" /></td>
<td>#item.StateName</td>
<td>
#if (ViewBag.Title == "Home Ajax" || Request.IsAjaxRequest())
{
Update
<span>#Ajax.ActionLink("Delete", "Delete", new { id = item.Id }, new AjaxOptions {OnSuccess="deleteSuccess",OnBegin="showLoader",OnComplete="hideLoader" })</span>
}
else {
<span>#Html.ActionLink("Update", "Index", new { id = item.Id })</span>
<span>#Html.ActionLink("Delete", "Delete", new { id = item.Id })</span>
}
</td>
</tr>
}
}
</table>
</div>
#model Person
#{
ViewBag.Title = "Home Ajax";
IEnumerable<Person> persons = ViewBag.Persons;
IEnumerable<SelectListItem> States = ViewBag.States;
IEnumerable<SelectListItem> Cities = ViewBag.Cities;
IEnumerable<State> fullStates=ViewBag.CityStates;
}
#section featured {
<section class="featured">
<div class="content-wrapper">
<hgroup class="title">
<h1>#ViewBag.Title.</h1>
</hgroup>
</div>
</section>
}
#section styles{
<style type="text/css">
td,th {
border:1px solid;
padding:5px 10px;
}
select {
padding:5px 2px;
width:310px;
font-size:16px;
}
</style>
}
#section scripts{
#Scripts.Render("~/bundles/jqueryval")
<script type="text/javascript">
var jsonArray = #Html.Raw(ViewBag.CityStateJson)
function clearValues() {
$("input[type='text'],select").val('');
$("input[type='hidden'][name='Id']").val(0);
}
function loadCities(obj) {
for (var i = 0; i < jsonArray.length; i++) {
if (jsonArray[i].Id == parseInt($(obj).val())) {
fillCity(jsonArray[i].Cities);
break;
}
}
}
function Edit(obj, Id) {
// alert("hi")
$("input[type='hidden'][name='Id']").val(Id);
var tr = $(obj).closest("tr");
$("#txtfirstName").val($("td[data-id='fn']", tr).text().trim());
$("#txtlastName").val($("td[data-id='ln']", tr).text().trim());
$("#txtemail").val($("td[data-id='email']", tr).text().trim());
var city = $("td[data-id='cn'] input[type='hidden']", tr).val();
var state;
for (var i = 0; i < jsonArray.length; i++) {
for (var j = 0; j < jsonArray[i].Cities.length; j++) {
if (jsonArray[i].Cities[j].Id == parseInt(city)) {
state = jsonArray[i].Id;
break;
}
}
if (state) {
fillCity(jsonArray[i].Cities);
break;
}
}
$("#ddlState").val(state);
$("#ddlCity").val(city);
}
function fillCity(obj) {
var html = '<option value="0">Select City</option>';
$(obj).each(function () {
html += '<option value="' + this.Id + '">' + this.Name + '</option>'
});
$("#ddlCity").html(html);
}
function deleteSuccess(responce) {
alert("record deleted successfully");
$("tr[data-id='" + responce + "']").remove();
}
function insertSuccess() {
alert("Record saved successfully");
clearValues();
}
function showLoader() {
$("#overlay").show();
}
function hideLoader() {
$("#overlay").hide();
}
</script>
}
<h3>Add Personal Detail</h3>
#using (Ajax.BeginForm("SavePersonDetail", "Home", new AjaxOptions { HttpMethod = "POST", UpdateTargetId = "personList" ,OnSuccess="insertSuccess",OnBegin="showLoader",OnComplete="hideLoader"}))
{
#Html.HiddenFor(m => m.Id);
<ol class="round">
<li>
#Html.LabelFor(m => m.FirstName)
#Html.TextBoxFor(m => m.FirstName, new { id = "txtfirstName" })
#Html.ValidationMessageFor(m => m.FirstName)
</li>
<li>
#Html.LabelFor(m => m.LastName)
#Html.TextBoxFor(m => m.LastName, new { id = "txtlastName" })
#Html.ValidationMessageFor(m => m.LastName)
</li>
<li>
#Html.LabelFor(m => m.Email)
#Html.TextBoxFor(m => m.Email, new { id = "txtemail" })
#Html.ValidationMessageFor(m => m.Email)
</li>
<li>
#Html.Label("State")
#Html.DropDownList("State", States, "Select State", new { onchange = "loadCities(this)", id = "ddlState" })
</li>
<li>
#Html.LabelFor(m => m.CityId)
#Html.DropDownListFor(m => m.CityId, Cities, "Select City", new { id = "ddlCity" })
#Html.ValidationMessageFor(m => m.CityId)
</li>
</ol>
<input type="submit" value="Save" />
<input type="button" value="Cancel" onclick="clearValues();"/>
}
<h2>
Person List
</h2>
<div style="position:fixed;text-align:center;top:0;bottom:0;left:0;right:0;z-index:10;background-color:black;opacity:0.6;display:none;" id="overlay">
<img style="position:relative;top:370px" src="~/Images/ajax-loader.gif" />
</div>
<div id="personList">
#Html.Partial("_personDetail", persons)
</div>
You approach using ajax is fine although I would recommend a few better practices including using a view model with properties for StateID, CityID StateList and CityList, and using Unobtrusive JavaScript rather than polluting you markup with behavior, and generating the first ("please select") option with a null value rather than 0 so it can be used with the [Required] attribute
HTML
#Html.DropDownList(m => m.StateID, States, "Select State") // remove the onchange
#Html.DropDownListFor(m => m.CityID, Cities, "Select City") // why change the default ID?
SCRIPT
var url = '#Url.Action("GetCities", "Home")'; // use the helper (dont hard code)
var cities = $('#CityID'); // cache the element
$('#StateID').change(function() {
$.getJSON(url, { id: $(this).val() }, function(response) {
// clear and add default (null) option
cities.empty().append($('<option></option>').val('').text('Please select'));
$.each(response, function(index, item) {
cities.append($('<option></option>').val(item.Value).text(item.Text));
});
});
});
If you were rendering multiple items (say you were asking the user to select their last 10 cities they visited), you can cache the result of the first call to avoid repeated calls where their selections may include cities from the same state.
var cache = {};
$('#StateID').change(function() {
var selectedState = $(this).val();
if (cache[selectedState]) {
// render the options from the cache
} else {
$.getJSON(url, { id: selectedState }, function(response) {
// add to cache
cache[selectedState] = response;
.....
});
}
});
Finally, in response to your comments regarding doing it without ajax, you can pass all the cities to the view and assign them to a javascript array. I would only recommend this if you have a few countries, each with a few cities. Its a matter of balancing the slight extra initial load time vs the slight delay in making the ajax call.
In the controller
model.CityList = db.Cities.Select(d => new { City = d.CountryID, Text = d.CityName, Value = d.Id }).ToList();
In the view (script)
// assign all cities to javascript array
var allCities= JSON.parse('#Html.Raw(Json.Encode(Model.CityList))');
$('#StateID').change(function() {
var selectedState = $(this).val();
var cities = $.grep(allCities, function(item, index) {
return item.CountryID == selectedState;
});
// build options based on value of cities
});
This is a correct approach, but you can simplify your javascript:
function loadCities(obj) {
$.getJSON("/Home/GetCities", function (data) {
var html = '<option value="0">Select City</option>';
$(data).each(function () {
html += '<option value="'+this.Value+'">'+this.Text+'</option>'
});
$("#ddlCity").html(html);
});
}
Further possible simplification:
Add the default item (Select City) server-side, so your javascript will be smaller.
Here's how I'd do it without the page refresh, assuming the list of cities isn't too long.
I'm assuming you can create a GetStatesAndCities method to return a Dictionary.
public ActionResult Index()
{
Dictionary<string, List<String>> statesAndCities = GetStatesAndCities();
ViewBag.StatesAndCities = Json(statesAndCities);
}
Then in the view:
var states = JSON.parse(#ViewBag.StatesAndCities);
function loadCities(obj) {
var cities = states[$(obj).val()];
var html = '<option value="0">Select City</option>';
$(cities).each(function () {
html += '<option value="'+this.Value+'">'+this.Text+'</option>'
});
$("#ddlCity").html(html);
}
This way when the state is changed the cities field with update immediately with no need for callback.
disclaimer: This is not a code answer, there are plenty other answers.
I think best way to keep yourself happy to seperate UI pages from data => turn them into API calls:
/GetCities
/GetStates
Now you can simply leave the select's empty on Razor rendering the page. And use a Jquery/Bootstrap plugin to create an AJAX select box.
This way when the user stops typing his search, this search string can than be send with the AJAX call (eg: /GetStates?search=test) and then a small result set can be send back to the website.
This gives:
Better separation in serveside code
Better User eXperience.
Smaller page loads (since you no longer send all the options to user when he requests the page, only when he opens the select box).
How about using Knockout?
Knockout is a JavaScript library that helps you to create rich, responsive display and editor user interfaces with a clean underlying data model
You have to use ajax for your cities. But with knockout you dont need to write
var html = '<option value="0">Select City</option>';
$(responce).each(function () {
html += '<option value="'+this.Value+'">'+this.Text+'</option>'});
$("#ddlCity").html(html);
in your javascript.Knockout makes it simple.
You can simply write:
function CityModel() {
var self = this; // that means this CityModel
self.cities = ko.observableArray([]);
self.getCities = function () {
$.ajax({
url: "/Home/GetCities",
data: { id: $(obj).val() },
contentType: "application/json",
success: self.cities
});
}
}
ko.applyBindings(new CityModel());
thats all. But you have to bind your data into html elements.
Instead of using :
#Html.DropDownListFor(m => m.CityId, Cities, "Select City", new { id="ddlCity"})
You can use:
<select data-bind="options:cities,optionsValue:"Id",optionsText:"CityName",optionsCaption:"Select City""></select>
or you can mix razor and knockout:
#Html.DropDownListFor(m => m.CityId, Cities, "Select City", new { id="ddlCity",data_bind:"options:cities,optionsValue:\"Id\",optionsText:\"CityName\""})
One more thing you have to call GetCities when State changes, you can :
#Html.DropDownList("State", States, "Select State", new {data_bind:"event:\"change\":\"$root.GetCities\""})
Dont be scare with \"\" things this because " is an escape character and we have to say to razor i want to use " by using \ before it.
You can find more info about knockout :Knockout
And mixing with razor: Razor and Knockout
Ps: yes using knockout is suspend us from Razor and Mvc. You have to write another ViewModel . But like this situations ko is helpful. Mixing razor and knockout is another option for you.

Categories

Resources