I'm working in ASP.Net, and have an enumeration of viewmodels I want to display in a page. They contain a dropdown, and when said dropdown is updated, I have to take that enumeration of viewmodels, change the hirearchy inside it, and post it back with the changes. However, when I make my post, instead of sending the contained viewmodels, it sends an empty enumeration.
So far, I've tried switching my "foreach" in my view for a "for". I've also tried changing some names, seeing if the model binder was being the issue, but it doesn't seem so.
Here is the code for, respectively, the view, the viewmodel and the controller.
View
#using (Html.BeginForm())
{
#Html.AntiForgeryToken();
for (int i = 0; i < Model.Count(); i++)
{
#Html.DropDownListFor(model => model.ElementAt(i).PickedNormId, Model.ElementAt(i).PickableNorms, UiText.Prosyn.N_A, htmlAttributes: new { #class = "form-control dropdown_search", #name="NormPicker"#*, #onchange = "this.form.submit();"*# })
}
<div>
<input name="selectedNormList" type="submit">
</div>
}
ViewModel
public class NormPickerViewModel : ViewModel
{
public SelectList PickableNorms { get; set; }
public int PickedNormId { get; set; }
}
Controller
public virtual ActionResult Index()
{
List<NormPickerViewModel> viewModels = new List<NormPickerViewModel>();
viewModels.Add(new NormPickerViewModel()
{
PickableNorms = new SelectList(
_normRepository.GetAll().Where(x => x.Depth == 1).ToList(),
"Id",
"Description"
)
});
return View(viewModels);
}
[HttpPost]
public virtual ActionResult Index(IEnumerable<NormPickerViewModel> selectedNormList)
{
// selectedNormList's count is always zero.
}
Normally, I'd expect to find the dropdowns I passed in the "Get" with their updated selected values, but what I'm getting is an empty enumeration.
I tried reproducing your source code, you need update 2 items and it worked:
Change from IEnumerable<NormPickerViewModel> selectedNormList to IEnumerable<NormPickerViewModel> normPickerViewModel in controller.
Add name for select tag (DropDownListFor) Name = "normPickerViewModel[" + i + "].PickedNormId" in cshtml file.
Here is Controller code
[HttpGet]
public virtual ActionResult Index()
{
List<NormPickerViewModel> viewModels = new List<NormPickerViewModel>();
viewModels.Add(new NormPickerViewModel()
{
PickableNorms = new SelectList(
new List<dynamic> { new { Id = 1, Description = "Test1" }, new { Id = 2, Description = "Test2" } },
"Id",
"Description"
)
});
viewModels.Add(new NormPickerViewModel()
{
PickableNorms = new SelectList(
new List<dynamic> { new { Id = 3, Description = "Test3" }, new { Id = 4, Description = "Test4" } },
"Id",
"Description"
)
});
return View(viewModels);
}
[HttpPost]
public virtual ActionResult Index(IEnumerable<NormPickerViewModel> normPickerViewModel)
{
// selectedNormList's count is always zero.
return null;
}
and cshtml code
#using (Html.BeginForm())
{
#Html.AntiForgeryToken();
for (int i = 0; i < Model.Count(); i++)
{
#Html.DropDownListFor(model => model.ElementAt(i).PickedNormId, Model.ElementAt(i).PickableNorms, "NA", htmlAttributes: new
{
#class = "form-control dropdown_search",
Name = "normPickerViewModel[" + i + "].PickedNormId"#*, #onchange = "this.form.submit();"*#
})
}
<div>
<input name="selectedNormList" type="submit">
</div>
}
Related
This is related to an earlier question on binding a dropdownlist from a database. The dropdownlist is being bound and populated but is throwing an "Object reference not set to an instance of an object" error in the browser when submitting the form.
When debugging, I added a quick watch on the m => m.SelectedDepartment parameter of the Html.DropDownListFor(...) helper method and got: "Cannot convert lambda expression to type 'object' because it is not a delegate type".
View:
#model BudgetDemo.Models.BudgetsActualsViewModel
#using (Html.BeginForm("GetBudgetsActuals", "BudgetsActuals", FormMethod.Post))
{
#Html.DropDownListFor(m => m.SelectedDepartment, Model.Departments,
"Select Department", new { #class = "form-control" })
}
ViewModel:
public class BudgetsActualsViewModel
{
[Display(Name = "Cost Center/Department")]
[Required(ErrorMessage = "Cost Center/Department is required.")]
[StringLength(62)]
public string SelectedDepartment { get; set; }
public List<SelectListItem> Departments { get; set; }
}
Controller:
// GET: Render view with dropdowns
public ActionResult GetBudgetsActuals()
{
repo = new BudgetDemoRepository();
ModelState.Clear();
try
{
return View(repo.GetBudgetsActuals());
}
catch
{
return View("Error");
}
}
Repository:
public BudgetsActualsViewModel GetBudgetsActuals()
{
...
BudgetsActualsViewModel budgetsActuals = new BudgetsActualsViewModel()
{ Departments = new List<SelectListItem>() };
// Query returning correct data from DB here
for (int i = 0; i < ds.Tables[0].Rows.Count; i++)
{
budgetsActuals.Departments.Add(
new SelectListItem
{
Text = ds.Tables[0].Rows[i]["Department"].ToString(),
Value = ds.Tables[0].Rows[i]["ListValue"].ToString()
}
);
}
return budgetsActuals;
}
UPDATE
It's working now. The following code was updated/added to get the form re-rendering after a postback with a success message containing the value of the selected department.
View:
#model BudgetDemo.Models.BudgetsActualsViewModel
#Html.AntiForgeryToken()
#using (Html.BeginForm("GetBudgetsActuals", "BudgetsActuals",
FormMethod.Post))
{
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
#if (TempData["SuccessMessage"] != null)
{
<p class="alert alert-success"
id="successMessage">#TempData["SuccessMessage"]</p>
}
#Html.DropDownListFor(m => m.SelectedDepartment, Model.Departments,
"Select Department", new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.SelectedDepartment, "",
new { #class = "text-danger" })
}
Controllers:
// GET
public ActionResult GetBudgetsActuals()
{
Populate Department dropdown from DB and Year dropdown (static) here
repo = new BudgetDemoRepository();
//ModelState.Clear();
try
{
return View(repo.GetBudgetsActuals());
}
catch
{
return View("Error");
}
}
// POST
[HttpPost]
public ActionResult GetBudgetsActuals(BudgetsActualsViewModel model)
{
try
{
repo = new BudgetDemoRepository();
if (ModelState.IsValid)
{
TempData["SuccessMessage"] = "Value of SelectedDepartment is: "
+ model.SelectedDepartment;
return View(repo.GetBudgetsActuals(model));
}
else
{
model.Departments = repo.GetBudgetsActuals().Departments;
return View(model);
}
}
catch
{
return View("Error");
}
}
Repository - New overloaded method to handle POST
public BudgetsActualsViewModel GetBudgetsActuals(BudgetsActualsViewModel model)
{
....
// Get the data again
....
budgetsActuals.SelectedDepartment = model.SelectedDepartment;
for (int i = 0; i < ds.Tables[0].Rows.Count; i++)
{
budgetsActuals.Departments.Add(
new SelectListItem
{
Text = ds.Tables[0].Rows[i]["Department"].ToString(),
Value = ds.Tables[0].Rows[i]["ListValue"].ToString()
}
);
}
return budgetsActuals;
}
Modify the controller to have a new api that supports a POST call. Do as follows.
Add this method a get method that is called when the view is loaded, like this:
[HttpGet]
public ActionResult GetBudgetsActuals()
{
repo = new BudgetDemoRepository();
ModelState.Clear();
try
{
return View(repo.GetBudgetsActuals());
}
catch
{
return View("Error");
}
}
Add a post method that is called when the form is submitted:
[HttpPost] // Decorate with this attribute
public ActionResult PostBudgetsActuals(BudgetDemo.Models.BudgetsActualsViewModel model)
{
var selectedDepartment = model.SelectedDepartment;
// dostuff with the model
return somethingFromHere;
}
And change the view like this:
#model BudgetDemo.Models.BudgetsActualsViewModel
#using (Html.BeginForm("PostBudgetsActuals", "BudgetsActuals", FormMethod.Post))
{
#Html.DropDownListFor(m => m.SelectedDepartment, Model.Departments,
"Select Department", new { #class = "form-control" })
}
If you are getting an issue with the overload of DropDownListFor method, then you can also try DropDownList method like below:
#using (Html.BeginForm("PostBudgetsActuals", "BudgetsActuals", FormMethod.Post))
{
#Html.DropDownList("SelectedDepartment", Model.Departments,
"Select Department", new { #class = "form-control" })
}
UPDATE
// POST
[HttpPost]
public ActionResult GetBudgetsActuals(BudgetsActualsViewModel model)
{
try
{
repo = new BudgetDemoRepository();
if (model.SelectedDepartment != null)
{
TempData["SuccessMessage"] = "Value of SelectedDepartment is:
" + model.SelectedDepartment;
}
model.Departments = repo.GetBudgetsActuals().Departments;
return View(model);
}
catch
{
return View("Error");
}
}
I am trying to pass a complex data structure from Controller to View and Back to Controller which contains Lists. I can see the list items in View. I want to edit those and send it back to the controller. I am able to edit some properties but for lists, I am getting null value in the controller.
Here is an example (simulation) of what I am trying to achieve:
Consider Model -
using System.Collections.Generic;
namespace WebApplication1.Models
{
public class StudentViewModel
{
public string StudentId { get; set; }
public string FeedBack { get; set; }
public List<ScoreCard> ScoreCards;
}
public class ScoreCard
{
public string Subject { get; set; }
public string Marks { get; set; }
}
}
Controller -
public class StudentController : Controller
{
public ActionResult Index()
{
var model = new StudentViewModel();
model.StudentId = "Some Student";
model.ScoreCards = new List<ScoreCard>
{
new ScoreCard()
{
Marks = "0",
Subject = "English"
},
new ScoreCard()
{
Marks = "0",
Subject = "Maths"
}
};
return View("View", model);
}
public ActionResult SubmitScore(StudentViewModel model)
{
/* Some Code */
}
}
View -
#model WebApplication1.Models.StudentViewModel
#{
ViewBag.Title = "Title";
}
#using (Html.BeginForm("SubmitScore", "Student", FormMethod.Post))
{
#Html.DisplayName(#Model.StudentId)<br />
<label>Comment:</label><br/>
#Html.EditorFor(m => m.FeedBack, new { htmlAttributes = new { #type = "text", id = #Model.FeedBack} })<br />
for (var i = 0; i < #Model.ScoreCards.Count; i++)
{
#Html.DisplayName(#Model.ScoreCards[i].Subject) <br/>
#Html.EditorFor(m => m.ScoreCards[i].Marks, new { htmlAttributes = new { #type = "number", #min = 0, id = #Model.ScoreCards[i].Marks} })<br />
}
<input class="btn btn-primary" type="submit" value="Submit" />
}
When I run the application -
When I click submit, I am able to see model.FeedBack but the list is set to null.
Something similar is achieved in this question & it's answer; I am not sure what exactly I am missing here.
you send no input for the Subject . You'll need a hidden input for that along with any other value you want returned to the controller when the form is posted. Also a simple text box should work for the marks.
#using (Html.BeginForm("SubmitScore", "Student", FormMethod.Post))
{
#Html.DisplayName(#Model.StudentId)<br />
#Html.HiddenFor(m => m.StudentId)
<label>Comment:</label><br/>
#Html.EditorFor(m => m.FeedBack, new { htmlAttributes = new { #type = "text", id = #Model.FeedBack} })<br />
for (var i = 0; i < #Model.ScoreCards.Count; i++) {
#Html.HiddenFor(m => m.ScoreCards[i].Subject)
#Html.DisplayName(#Model.ScoreCards[i].Subject) <br/>
#Html.TextBoxFor(m => m.ScoreCards[i].Marks, new { htmlAttributes = new { #type = "number", #min = 0} })<br />
}
<input class="btn btn-primary" type="submit" value="Submit" />
}
Finally you need to use properties in order for the model binding to work. You currently have ScoreCards as a field so update it to a property.
public class StudentViewModel {
public string StudentId { get; set; }
public string FeedBack { get; set; }
public List<ScoreCard> ScoreCards { get; set; }
}
I have a web app that is written using ASP.NET MVC framework. In my Homecontroller I have an action called Index which responds to a Get request. In this action, I create pages using IPagedList library to break the records into multiple pages. My Index#HttpGet looks something like this
public ActionResult Index(int? id)
{
using(var connection = new Context())
{
int pageNumber = (id ?? 1);
var presenter = new Presenter
{
Presenter = pageNumber,
Tasks = connection.Tasks.ToPagedList(pageNumber, 30),
Form = new TasksFiltersViewModel()
}
return View(presenter);
}
}
I also have an action called Index that respond to the Post request which apply some filters. So in the Post request I do something like this
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Index(Presenter model)
{
int pageNumber = (id ?? 1);
if (ModelState.IsValid)
{
using(var connection = new Context())
{
model.Tasks = connection.Tasks
.Where(task => task.Status == 5)
.ToPagedList(pageNumber, 30);
}
}
return View(model);
}
This also work fine unless the user changed the page then the filters are rest.
Here is how my presentation class looks like
public class Presenter
{
public IPagedList<Task> Tasks { get; set; }
public TasksFiltersViewModel Form { get; set; }
public int PageNumber { get; set; }
public IEnumerable<SelectListItem> Statuses { get; set; }
}
How can I allow the users to use the pages while keeping the filters?
Here is my filters VM
public class TasksFiltersViewModel
{
public int Status { get; set; }
}
The view looks like this
#using (Html.BeginForm("Index", "Tasks", FormMethod.Post, new { #class = "form-horizontal" }))
{
#Html.AntiForgeryToken()
<div class="form-group">
#Html.LabelFor(m => m.Form.Status, new { #class = "control-label col-sm-3" })
<div class="col-sm-9">
#Html.DropDownListFor(m => m.Form.Status, Model.Statuses, new { #class = "form-control" })
#Html.ValidationMessageFor(m => m.Form.Status, "", new { #class = "text-danger" })
</div>
</div>
<div class="row">
<div class="col-sm-9 col-md-push-3">
<div>
<button type="submit" class="btn btn-default">Filter</button>
</div>
</div>
</div>
}
foreach (var task in Model.Tasks)
{
<tr>
<td>#task.Name</td>
<td>#task.Type</td>
<td>#Html.ActionLink("Edit", "Details", "Task", new { #id = task.Id }, new { #class = "btn btn-primary btn-sm" })</td>
</tr>
}
#Html.PagedListPager(Model.Tasks, id => Url.Action("Index", new { id }))
Your form needs to post back to the GET method, and that method needs to include parameters for your filter properties. Your PagedListPager code in the view also need to include those filter properties so they are retained when you navigate to the next/previous page. Note that the Index() POST method is not used and can be deleted.
Having your model contain a complex object to the filter properties and extra complexity when binding, so start by changing your model to
public class Presenter
{
public IPagedList<Task> Tasks { get; set; }
public int? Status { get; set; } // note nullable
... // add any other properties of TasksFiltersViewModel
public int PageNumber { get; set; }
public IEnumerable<SelectListItem> Statuses { get; set; }
}
Then change the Index() method to
public ActionResult Index(int? id, int? status) // add any other parameters your filtering on
{
int pageNumber = (id ?? 1);
var tasks = db.Tasks; // IQueryable<Task>
if (status.HasValue)
{
tasks = tasks.Where(x => x.Status == status.Value)
}
if (otherParametersHaveValue)
{
tasks = tasks.Where(....);
}
Presenter model = new Presenter()
{
PageNumber = id ?? 1,
Status = status,
.... // set any other filter properties from the parameters
Statuses = new SelectList(...),
Tasks = tasks.ToPagedList(pageNumber, 30)
};
return View(model );
}
and change the view to
// Make the form a GET
#using (Html.BeginForm("Index", "Tasks", FormMethod.Get, new { #class = "form-horizontal" }))
{
....
// Modify expression based on revised model properties
#Html.DropDownListFor(m => m.Status, Model.Statuses, ...)
}
....
// Add filter parameters to url so they are retained
#Html.PagedListPager(Model.Tasks, id => Url.Action("Index", new { id, status = Model.Status })) // add other filter properties as required
I think a better way should be passing back to the view the filters in ViewBag.
You can make something like below:
#Html.PagedListPager(
Model.Tasks, id =>
Url.Action("Index", new { id,
Status = ViewBag.Status , AnotherFilterValue = ViewBag.AnotherFilterValue, ... }))
But keep in mind to test ViewBag.Status for null value. If it does have a value put it in the route parameter list else set a default ActionLink.
Then inside the POST action you expect a nullable int like below:
public ActionResult Index(int? id, int? status, ...)
{
int pageNumber = (id ?? 1);
if (ModelState.IsValid)
{
using(var connection = new Context())
{
if(status != null)
{
ViewBag.Status = status.value;
model.Tasks = connection.Tasks
.Where(task => task.Status == status.value)
.ToPagedList(pageNumber, 30);
}
else
{
model.Tasks = connection.Tasks
.ToPagedList(pageNumber, 30);
}
}
}
return View(model);
}
Two ways to do this.
The quick way: Cookies.
Just set a cookie for the filter options. On the actions where filtering is required, all you need to do is read the cookies and filter accordingly.
I dislike cookies and sessions, and would avoid them. But sometimes it could turn out to be what you need.
Use GET parameters
In your example you've used POST to filter. Each time you click on a link, it fires a GET request, not a POST. So the filtering doesn't happen. The tricky part is making sure the GET parameters are set each time. Could create a custom extension similar to Html.Action() to start with. If you'll be checking the filter options in multiple actions, consider using an action Filter.
I have a web app that is written using ASP.NET MVC framework. In my Homecontroller I have an action called Index which responds to a Get request. In this action, I create pages using IPagedList library to break the records into multiple pages. My Index#HttpGet looks something like this
public ActionResult Index(int? id)
{
using(var connection = new Context())
{
int pageNumber = (id ?? 1);
var presenter = new Presenter
{
Presenter = pageNumber,
Tasks = connection.Tasks.ToPagedList(pageNumber, 30),
Form = new TasksFiltersViewModel()
}
return View(presenter);
}
}
I also have an action called Index that respond to the Post request which apply some filters. So in the Post request I do something like this
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Index(Presenter model)
{
int pageNumber = (id ?? 1);
if (ModelState.IsValid)
{
using(var connection = new Context())
{
model.Tasks = connection.Tasks
.Where(task => task.Status == 5)
.ToPagedList(pageNumber, 30);
}
}
return View(model);
}
This also work fine unless the user changed the page then the filters are rest.
Here is how my presentation class looks like
public class Presenter
{
public IPagedList<Task> Tasks { get; set; }
public TasksFiltersViewModel Form { get; set; }
public int PageNumber { get; set; }
public IEnumerable<SelectListItem> Statuses { get; set; }
}
How can I allow the users to use the pages while keeping the filters?
Here is my filters VM
public class TasksFiltersViewModel
{
public int Status { get; set; }
}
The view looks like this
#using (Html.BeginForm("Index", "Tasks", FormMethod.Post, new { #class = "form-horizontal" }))
{
#Html.AntiForgeryToken()
<div class="form-group">
#Html.LabelFor(m => m.Form.Status, new { #class = "control-label col-sm-3" })
<div class="col-sm-9">
#Html.DropDownListFor(m => m.Form.Status, Model.Statuses, new { #class = "form-control" })
#Html.ValidationMessageFor(m => m.Form.Status, "", new { #class = "text-danger" })
</div>
</div>
<div class="row">
<div class="col-sm-9 col-md-push-3">
<div>
<button type="submit" class="btn btn-default">Filter</button>
</div>
</div>
</div>
}
foreach (var task in Model.Tasks)
{
<tr>
<td>#task.Name</td>
<td>#task.Type</td>
<td>#Html.ActionLink("Edit", "Details", "Task", new { #id = task.Id }, new { #class = "btn btn-primary btn-sm" })</td>
</tr>
}
#Html.PagedListPager(Model.Tasks, id => Url.Action("Index", new { id }))
Your form needs to post back to the GET method, and that method needs to include parameters for your filter properties. Your PagedListPager code in the view also need to include those filter properties so they are retained when you navigate to the next/previous page. Note that the Index() POST method is not used and can be deleted.
Having your model contain a complex object to the filter properties and extra complexity when binding, so start by changing your model to
public class Presenter
{
public IPagedList<Task> Tasks { get; set; }
public int? Status { get; set; } // note nullable
... // add any other properties of TasksFiltersViewModel
public int PageNumber { get; set; }
public IEnumerable<SelectListItem> Statuses { get; set; }
}
Then change the Index() method to
public ActionResult Index(int? id, int? status) // add any other parameters your filtering on
{
int pageNumber = (id ?? 1);
var tasks = db.Tasks; // IQueryable<Task>
if (status.HasValue)
{
tasks = tasks.Where(x => x.Status == status.Value)
}
if (otherParametersHaveValue)
{
tasks = tasks.Where(....);
}
Presenter model = new Presenter()
{
PageNumber = id ?? 1,
Status = status,
.... // set any other filter properties from the parameters
Statuses = new SelectList(...),
Tasks = tasks.ToPagedList(pageNumber, 30)
};
return View(model );
}
and change the view to
// Make the form a GET
#using (Html.BeginForm("Index", "Tasks", FormMethod.Get, new { #class = "form-horizontal" }))
{
....
// Modify expression based on revised model properties
#Html.DropDownListFor(m => m.Status, Model.Statuses, ...)
}
....
// Add filter parameters to url so they are retained
#Html.PagedListPager(Model.Tasks, id => Url.Action("Index", new { id, status = Model.Status })) // add other filter properties as required
I think a better way should be passing back to the view the filters in ViewBag.
You can make something like below:
#Html.PagedListPager(
Model.Tasks, id =>
Url.Action("Index", new { id,
Status = ViewBag.Status , AnotherFilterValue = ViewBag.AnotherFilterValue, ... }))
But keep in mind to test ViewBag.Status for null value. If it does have a value put it in the route parameter list else set a default ActionLink.
Then inside the POST action you expect a nullable int like below:
public ActionResult Index(int? id, int? status, ...)
{
int pageNumber = (id ?? 1);
if (ModelState.IsValid)
{
using(var connection = new Context())
{
if(status != null)
{
ViewBag.Status = status.value;
model.Tasks = connection.Tasks
.Where(task => task.Status == status.value)
.ToPagedList(pageNumber, 30);
}
else
{
model.Tasks = connection.Tasks
.ToPagedList(pageNumber, 30);
}
}
}
return View(model);
}
Two ways to do this.
The quick way: Cookies.
Just set a cookie for the filter options. On the actions where filtering is required, all you need to do is read the cookies and filter accordingly.
I dislike cookies and sessions, and would avoid them. But sometimes it could turn out to be what you need.
Use GET parameters
In your example you've used POST to filter. Each time you click on a link, it fires a GET request, not a POST. So the filtering doesn't happen. The tricky part is making sure the GET parameters are set each time. Could create a custom extension similar to Html.Action() to start with. If you'll be checking the filter options in multiple actions, consider using an action Filter.
I have dropdownlist, which I have filled from database. Now I need to get the selected value in Controller do some manipulation. But not getting the idea. Code which I have tried.
Model
public class MobileViewModel
{
public List<tbInsertMobile> MobileList;
public SelectList Vendor { get; set; }
}
Controller
public ActionResult ShowAllMobileDetails()
{
MobileViewModel MV = new MobileViewModel();
MV.MobileList = db.Usp_InsertUpdateDelete(null, "", "", null, "", 4, MergeOption.AppendOnly).ToList();
MV.Vendor = new SelectList(db.Usp_VendorList(), "VendorId", "VendorName");
return View(MV);
}
[HttpPost]
public ActionResult ShowAllMobileDetails(MobileViewModel MV)
{
string strDDLValue = ""; // Here i need the dropdownlist value
return View(MV);
}
View
<table>
<tr>
<td>Mobile Manufacured</td>
<td>#Html.DropDownList("ddlVendor", Model.Vendor, "Select Manufacurer") </td>
</tr>
<tr>
<td>
</td>
<td>
<input id="Submit1" type="submit" value="search" />
</td>
</tr>
</table>
1st Approach (via Request or FormCollection):
You can read it from Request using Request.Form , your dropdown name is ddlVendor so pass ddlVendor key in the formCollection to get its value that is posted by form:
string strDDLValue = Request.Form["ddlVendor"].ToString();
or Use FormCollection:
[HttpPost]
public ActionResult ShowAllMobileDetails(MobileViewModel MV,FormCollection form)
{
string strDDLValue = form["ddlVendor"].ToString();
return View(MV);
}
2nd Approach (Via Model):
If you want with Model binding then add a property in Model:
public class MobileViewModel
{
public List<tbInsertMobile> MobileList;
public SelectList Vendor { get; set; }
public string SelectedVendor {get;set;}
}
and in View:
#Html.DropDownListFor(m=>m.SelectedVendor , Model.Vendor, "Select Manufacurer")
and in Action:
[HttpPost]
public ActionResult ShowAllMobileDetails(MobileViewModel MV)
{
string SelectedValue = MV.SelectedVendor;
return View(MV);
}
UPDATE:
If you want to post the text of selected item as well, you have to add a hidden field and on drop down selection change set selected item text in the hidden field:
public class MobileViewModel
{
public List<tbInsertMobile> MobileList;
public SelectList Vendor { get; set; }
public string SelectVendor {get;set;}
public string SelectedvendorText { get; set; }
}
use jquery to set hidden field:
<script type="text/javascript">
$(function(){
$("#SelectedVendor").on("change", function {
$("#SelectedvendorText").val($(this).text());
});
});
</script>
#Html.DropDownListFor(m=>m.SelectedVendor , Model.Vendor, "Select Manufacurer")
#Html.HiddenFor(m=>m.SelectedvendorText)
Model
Very basic model with Gender field. GetGenderSelectItems() returns select items needed to populate DropDownList.
public enum Gender
{
Male, Female
}
public class MyModel
{
public Gender Gender { get; set; }
public static IEnumerable<SelectListItem> GetGenderSelectItems()
{
yield return new SelectListItem { Text = "Male", Value = "Male" };
yield return new SelectListItem { Text = "Female", Value = "Female" };
}
}
View
Please make sure you wrapped your #Html.DropDownListFor in a form tag.
#model MyModel
#using (Html.BeginForm("MyController", "MyAction", FormMethod.Post)
{
#Html.DropDownListFor(m => m.Gender, MyModel.GetGenderSelectItems())
<input type="submit" value="Send" />
}
Controller
Your .cshtml Razor view name should be the same as controller action name and folder name should match controller name e.g Views\MyController\MyAction.cshtml.
public class MyController : Controller
{
public ActionResult MyAction()
{
// shows your form when you load the page
return View();
}
[HttpPost]
public ActionResult MyAction(MyModel model)
{
// the value is received in the controller.
var selectedGender = model.Gender;
return View(model);
}
}
Going further
Now let's make it strongly-typed and enum independent:
var genderSelectItems = Enum.GetValues(typeof(Gender))
.Cast<string>()
.Select(genderString => new SelectListItem
{
Text = genderString,
Value = genderString,
}).AsEnumerable();
MVC 5/6/Razor Pages
I think the best way is with strongly typed model, because Viewbags are being aboused too much already :)
MVC 5 example
Your Get Action
public async Task<ActionResult> Register()
{
var model = new RegistrationViewModel
{
Roles = GetRoles()
};
return View(model);
}
Your View Model
public class RegistrationViewModel
{
public string Name { get; set; }
public int? RoleId { get; set; }
public List<SelectListItem> Roles { get; set; }
}
Your View
<div class="form-group">
#Html.LabelFor(model => model.RoleId, htmlAttributes: new { #class = "col-form-label" })
<div class="col-form-txt">
#Html.DropDownListFor(model => model.RoleId, Model.Roles, "--Select Role--", new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.RoleId, "", new { #class = "text-danger" })
</div>
</div>
Your Post Action
[HttpPost, ValidateAntiForgeryToken]
public async Task<ActionResult> Register(RegistrationViewModel model)
{
if (ModelState.IsValid)
{
var _roleId = model.RoleId,
MVC 6 It'll be a little different
Get Action
public async Task<ActionResult> Register()
{
var _roles = new List<SelectListItem>();
_roles.Add(new SelectListItem
{
Text = "Select",
Value = ""
});
foreach (var role in GetRoles())
{
_roles.Add(new SelectListItem
{
Text = z.Name,
Value = z.Id
});
}
var model = new RegistrationViewModel
{
Roles = _roles
};
return View(model);
}
Your View Model will be same as MVC 5
Your View will be like
<select asp-for="RoleId" asp-items="Model.Roles"></select>
Post will also be same
Razor Pages
Your Page Model
[BindProperty]
public int User User { get; set; } = 1;
public List<SelectListItem> Roles { get; set; }
public void OnGet()
{
Roles = new List<SelectListItem> {
new SelectListItem { Value = "1", Text = "X" },
new SelectListItem { Value = "2", Text = "Y" },
new SelectListItem { Value = "3", Text = "Z" },
};
}
<select asp-for="User" asp-items="Model.Roles">
<option value="">Select Role</option>
</select>
I hope it may help someone :)
If you want to use #Html.DropDownList , follow.
Controller:
var categoryList = context.Categories.Select(c => c.CategoryName).ToList();
ViewBag.CategoryList = categoryList;
View:
#Html.DropDownList("Category", new SelectList(ViewBag.CategoryList), "Choose Category", new { #class = "form-control" })
$("#Category").on("change", function () {
var q = $("#Category").val();
console.log("val = " + q);
});
If you're looking for something lightweight, I'd append a parameter to your action.
[HttpPost]
public ActionResult ShowAllMobileDetails(MobileViewModel MV, string ddlVendor)
{
string strDDLValue = ddlVendor; // Of course, this becomes silly.
return View(MV);
}
What's happening in your code now, is you're passing the first string argument of "ddlVendor" to Html.DropDownList, and that's telling the MVC framework to create a <select> element with a name of "ddlVendor." When the user submits the form client-side, then, it will contain a value to that key.
When MVC tries to parse that request into MV, it's going to look for MobileList and Vendor and not find either, so it's not going to be populated. By adding this parameter, or using FormCollection as another answer has suggested, you're asking MVC to specifically look for a form element with that name, so it should then populate the parameter value with the posted value.
Use SelectList to bind #HtmlDropdownListFor and specify selectedValue parameter in it.
http://msdn.microsoft.com/en-us/library/dd492553(v=vs.108).aspx
Example : you can do like this for getting venderid
#Html.DropDownListFor(m => m.VendorId,Model.Vendor)
public class MobileViewModel
{
public List<tbInsertMobile> MobileList;
public SelectList Vendor { get; set; }
public int VenderID{get;set;}
}
[HttpPost]
public ActionResult Action(MobileViewModel model)
{
var Id = model.VenderID;
I was having the same issue in asp.NET razor C#
I had a ComboBox filled with titles from an EventMessage, and I wanted to show the Content of this message with its selected value to show it in a label or TextField or any other Control...
My ComboBox was filled like this:
#Html.DropDownList("EventBerichten", new SelectList(ViewBag.EventBerichten, "EventBerichtenID", "Titel"), new { #class = "form-control", onchange = "$(this.form).submit();" })
In my EventController I had a function to go to the page, in which I wanted to show my ComboBox (which is of a different model type, so I had to use a partial view)?
The function to get from index to page in which to load the partial view:
public ActionResult EventDetail(int id)
{
Event eventOrg = db.Event.Include(s => s.Files).SingleOrDefault(s => s.EventID == id);
// EventOrg eventOrg = db.EventOrgs.Find(id);
if (eventOrg == null)
{
return HttpNotFound();
}
ViewBag.EventBerichten = GetEventBerichtenLijst(id);
ViewBag.eventOrg = eventOrg;
return View(eventOrg);
}
The function for the partial view is here:
public PartialViewResult InhoudByIdPartial(int id)
{
return PartialView(
db.EventBericht.Where(r => r.EventID == id).ToList());
}
The function to fill EventBerichten:
public List<EventBerichten> GetEventBerichtenLijst(int id)
{
var eventLijst = db.EventBericht.ToList();
var berLijst = new List<EventBerichten>();
foreach (var ber in eventLijst)
{
if (ber.EventID == id )
{
berLijst.Add(ber);
}
}
return berLijst;
}
The partialView Model looks like this:
#model IEnumerable<STUVF_back_end.Models.EventBerichten>
<table>
<tr>
<th>
EventID
</th>
<th>
Titel
</th>
<th>
Inhoud
</th>
<th>
BerichtDatum
</th>
<th>
BerichtTijd
</th>
</tr>
#foreach (var item in Model)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.EventID)
</td>
<td>
#Html.DisplayFor(modelItem => item.Titel)
</td>
<td>
#Html.DisplayFor(modelItem => item.Inhoud)
</td>
<td>
#Html.DisplayFor(modelItem => item.BerichtDatum)
</td>
<td>
#Html.DisplayFor(modelItem => item.BerichtTijd)
</td>
</tr>
}
</table>
VIEUW: This is the script used to get my output in the view
<script type="text/javascript">
$(document).ready(function () {
$("#EventBerichten").change(function () {
$("#log").ajaxError(function (event, jqxhr, settings, exception) {
alert(exception);
});
var BerichtSelected = $("select option:selected").first().text();
$.get('#Url.Action("InhoudByIdPartial")',
{ EventBerichtID: BerichtSelected }, function (data) {
$("#target").html(data);
});
});
});
</script>
#{
Html.RenderAction("InhoudByIdPartial", Model.EventID);
}
<fieldset>
<legend>Berichten over dit Evenement</legend>
<div>
#Html.DropDownList("EventBerichten", new SelectList(ViewBag.EventBerichten, "EventBerichtenID", "Titel"), new { #class = "form-control", onchange = "$(this.form).submit();" })
</div>
<br />
<div id="target">
</div>
<div id="log">
</div>
</fieldset>
Thanks - this helped me to understand better ansd solve a problem I had.
The JQuery provided to get the text of selectedItem did NOT wwork for me
I changed it to
$(function () {
$("#SelectedVender").on("change", function () {
$("#SelectedvendorText").val($(**"#SelectedVender option:selected"**).text());
});
});
Simple solution not sure if this has been suggested or not. This also may not work for some things. That being said this is the simple solution below.
new SelectListItem { Value = "1", Text = "Waiting Invoices", Selected = true}
List<SelectListItem> InvoiceStatusDD = new List<SelectListItem>();
InvoiceStatusDD.Add(new SelectListItem { Value = "0", Text = "All Invoices" });
InvoiceStatusDD.Add(new SelectListItem { Value = "1", Text = "Waiting Invoices", Selected = true});
InvoiceStatusDD.Add(new SelectListItem { Value = "7", Text = "Client Approved Invoices" });
#Html.DropDownList("InvoiceStatus", InvoiceStatusDD)
You can also do something like this for a database driven select list. you will need to set selected in your controller
#Html.DropDownList("ApprovalProfile", (IEnumerable<SelectListItem>)ViewData["ApprovalProfiles"], "All Employees")
Something like this but better solutions exist this is just one method.
foreach (CountryModel item in CountryModel.GetCountryList())
{
if (item.CountryPhoneCode.Trim() != "974")
{
countries.Add(new SelectListItem { Text = item.CountryName + " +(" + item.CountryPhoneCode + ")", Value = item.CountryPhoneCode });
}
else {
countries.Add(new SelectListItem { Text = item.CountryName + " +(" + item.CountryPhoneCode + ")", Value = item.CountryPhoneCode,Selected=true });
}
}