I have a list of objects say Employee, and I am trying to get the dropdown list of all employee names with a custom parameter with their employee id. The Model is as follows
public class Emp
{
public string EmployeeName {get;set;}
public int employeeId {get;set;}
}
I am looking to create a Dropdownlist with HTML.DropdownlistFor as below
<select>
<option employeeid="101"> Emp1 <option>
<option employeeid="102"> Emp2 <option>
<option employeeid="103"> Emp3 <option>
</select>
Your HTML is invalid, a <select> tag does not have an attribute called employeeid. Since you're clearly trying to bind the employeeId to a the value of an option, you should use the classic name and value attributes which can be used as follows:
<select name="employeeid">
<option value="101">Gary</option>
<option value="102">John</option>
<!-- etc -->
</select>
Now in your controller action in your MVC project, you recieve a parameter called employeeid which is of the type int. The model binder (look it up) will automatically bind the value you selected in the 'employeeid' select input element when you submit the form.
The answer #daniell89 provided shows you a nice way of populating your select tag using the Razor template language pretty much everyone uses for c# MVC projects. Note how a ViewModel you use in your view does not have to be the same as the ViewModel you submit to the action you post to, so the public int SelectedEmployee { get; set; } is actually not necessary in the ViewModel, as long as you have the 'employeeid' as a parameter for the action you submit to.
EDIT
In order to use custom attributes you will kind of have to move away from classic MVC, because the post you will do will be done by an AJAX Post. You will need to import the jQuery library (actually you can use vanilla javascript or any library of your choosing, I prefer jQuery for this kind of stuff), look up how to import this if you need to.
Use a ViewModel in your code alike this:
public class Employee {
public string Name { get; set; }
public string EmployeeId { get; set; }
}
Then use this model in your view as such (first line of code of your view, unless you have using statements):
#model IEnumerable<Employee>
Then right at the place you want to have your select button create this code:
<select id="my-select-element">
#foreach(var employee in Model) {
<text>
<option employeeid="#employee.EmployeeId">#employee.Name</option>
</text>
}
</select>
<button id="submit-employee">Send!</button>
This will create your select tag with all the employees you've put in your viewmodel, together with a button you will use to send your data to your server.
Then include the following jQuery script on your page, that will collect your data and send it to the server.
<script>
$("#submit-employee").on("click", function(event) {
event.preventDefault(); //will prevent default behaviour done by clicking the button
var selecttag = $("#my-select-element");
var id = $(selecttag).find("option").prop("selected").attr("employeeid");
$.post("<your URL>", { employeeid: id }, function(result) {
console.log(result);
}
}
</script>
Then in your controller on your server code, have an action corresponding with and have it parameterised with an int emplyeeid as such:
[HttpPost]
public ActionResult SubmitEmployee (int employeeid) {
return new Json().Body(new { message = "I saw " + employeeid })
}
If all goes right, your console window in your browser will be showing you a returned object that has a 'message' key with a value 'I saw 101' if you selected 101 as the employee before sending it.
I typed all this code right in the stackoverflow editor, so I may have made some syntax errors or mistakes in the way types are called (Im not sure if you can just do new Json().Body() for instance, though I know you can do something similar for sure.
I hope this answers your question.
I think you need another class- EmpViewModel:
public class EmpViewModel
{
public int SelectedEmployee { get; set; }
public IEnumerable<Employee> EmployeeList { get; set; }
}
And then you can pass instance of this class to the view and create dropdown list like this:
#Html.DropDownListFor(m => m.SelectedEmployee, new SelectList(Model.EmployeeList, "employeeId", "EmployeeName"));
Related
I have a list of items on a razor page which can be updated by user on front-end with javascript:
<select asp-for="Answers" asp-items="Model.Answers" multiple="multiple"></select>
I also have a bind property for this list:
[BindProperty]
public List<SelectListItem> Answers { get; set; } = new List<SelectListItem> { new SelectListItem("Test1", "Test1") };
The binding works properly when I pass data on page loading, but the list is empty when I submit my form.
Here is HTML code before pressing Submit button:
<select id="answersList" multiple="multiple" data-val="true" name="Answers">
<option value="Test1">Test1</option>
<option value="Test2">Test2</option>
<option value="Test3">Test3</option>
</select>
and the screenshot from IDE:
As you can see, the list in the code is empty. How can I pass the items a user added to the back-end?
The other bind properties have the correct data. So, the problem is on this list only.
If you capture the request and check the actual data that you post in browser F12 developer tool Network tab, you would find it looks like below.
Which can not be bound to List<SelectListItem> type property Answers.
How can I pass the items a user added to the back-end?
To achieve your requirement, you can try to modify the code like below.
Define a List<string> type property SelectedAnswers in PageModel class
public List<SelectListItem> Answers { get; set; } = new List<SelectListItem> { new SelectListItem("Test1", "Test1") };
[BindProperty]
public List<string> SelectedAnswers { get; set; }
Update select tag helper with asp-for="SelectedAnswers"
<form method="post">
<select asp-for="SelectedAnswers" asp-items="Model.Answers" multiple="multiple"></select>
<input type="submit" value="Submit" />
</form>
Test Result
Update:
I didn't select any options from the select list, so I didn't get any items. However, I don't want to ask a user to select options; I want to post all of them. Is there a simple way to do it, or should I use javascript code to select all items before submitting?
You can try to set all SelectListItem to selected by default while you add new SelectListItem to List<SelectListItem> Answers, like below.
//...
Answers.Add(new SelectListItem("Test2", "Test2", true));
Answers.Add(new SelectListItem("Test3", "Test3", true));
//...
Besides, as you mentioned, you can also achieve same to set all items/options to selected using JavaScript before submitting the form.
$("form").first().submit(function () {
$('#myList option').prop('selected', true);
});
I am new to ASP.NET MVC. I have a simple form with a submit button and an html select element, populated with two items. When the form posts to my controller, all the form values are null. I've even tried doing it with $.POST instead and the id variable I am sending is null when it gets to the controller. Here is my code:
HTML
#using (Html.BeginForm("SetOptionForUser", "MyController", FormMethod.Post, new { #class="form-inline" }))
{
#Html.AntiForgeryToken()
<div class="text-center">
<div class="form-group">
<select id="ddlSelect" class="form-control">
#foreach (var item in Model.Persons)
{
<option value="#item.Id">#item.Name</option>
}
</select>
</div>
<button type="submit" class="btn btn-primary" id="btnEnter">Go</button>
</div>
}
MVC Controller
[ValidateAntiForgeryToken]
[HttpPost]
public ActionResult SetOptionForUser(FormCollection form)
{
string option = form["ddlSelect"].ToString(); //null error
return RedirectToAction("AnotherAction", "AnotherController");
}
It seems nothing in the form is being sent. I also tried this:
JS
$("#btnEnter").click(function (e) {
var optionId = $("#ddlSelect").val(); //this get val correctly
$.post(#Url.Action("SetOptionForUser", "MyController"), optionId);
});
MVC Controller for JS method
**MVC Controller**
[ValidateAntiForgeryToken]
[HttpPost]
public ActionResult SetOptionForUser(int optionId) //null
{
string option = optionId.ToString(); //null error
return RedirectToAction("AnotherAction", "AnotherController");
}
What I am doing wrong?
Normal form submit
Your normal form submit should work if your select element name and your http post action method parameter name is same.
<select name="selectedPerson" id="selectedPerson" class="form-control">
<!-- Options goes here -->
</select>
and your action method
[HttpPost]
public ActionResult SetOptionForUser(string selectedPerson)
{
string option = selectedPerson;
return RedirectToAction("AnotherAction", "AnotherController");
}
You might also consider using the Html.DropDownListFor or Html.DropDownList helper methods to generate the SELECT element from a list of items ( instead of manually writing the loop to render the option items).
Sending data with Ajax
Now if you want this to be ajaxified, you can send the data in either querystring or the request body
$(function(){
$("#btnEnter").click(function (e) {
e.preventDefault();
var optionId = $("#selectedPerson").val();
$.post('#Url.Action("SetOptionForUser", "MyController")', { selectedPerson:optionId});
});
});
Now there is no point in returning a RedirectResult for an ajax call. You might consider returning a json response (indicating whether your action was successful or not) from your action methodo and in the success callback of $.post you can inspect the value and do needed.
[HttpPost]
public ActionResult SetOptionForUser(string selectedPerson)
{
string option = selectedPerson;
return Json(new {status="success"});
}
And you can check the response in the callback
var optionId = $("#selectedPerson").val();
var url="#Url.Action("SetOptionForUser", "MyController")";
$.post(url, { selectedPerson:optionId}, function(response){
if(response.status==="success")
{
alert("Success");
}
else
{
alert("Some trouble!");
}
});
The only thing that I see missing in your code is the name property of your dropdown.
<select id="ddlSelect" name="ddlSelect" class="form-control">
#foreach (var item in Model.Persons)
{
<option value="#item.Id">#item.Name</option>
}
</select>
for your first example your using FormCollection
string option = form["ddlSelect"].ToString();
but your DropDown doesn't have a name property and form collection is using the name property as reference that is why you are getting a null value.
same goes with your second example reference the DropDown name property instead of ID in your Parameter
public ActionResult SetOptionForUser(int myDropDownName) //null
{
string option = myDropDownName.ToString(); //null error
return RedirectToAction("AnotherAction", "AnotherController");
}
The FormCollection in ASP.NET MVC is tied to the name attribute of input elements, add the name attribute to your select HTML element, like this:
<select id="ddlSelect" name="ddlSelect" class="form-control">
Note: The id attribute is how to find things in the DOM and the name attribute is for what gets included/submitted in the form
As for the JavaScript jQuery AJAX POST issue, I believe the issue is that jQuery is making an intelligent guess that your optionId is text. You should create an object literal to hold the JSON data and then stringify that value to send to the controller, like this:
var dataForServer = {
optionId: optionId
};
$.post(#Url.Action("SetOptionForUser", "MyController"),
JSON.stringify(optionId),
null,
json);
The FormCollection in ASP.NET MVC is getting to the name attribute of input elements
so add this attribute name="ddlSelect"
MattoMK,
Please post code for your Model.
Typically, a model will hold all the data required for your webform to run and your post to send the data back. In your view, you should be selecting to a Model field. You seem to have Model.Persons, which is likely an enumerable of a class describing people.
MODEL
You did not describe your model, so a fake one is shown with just two properties, a string to hold the list selection and your list of Persons. Hopefully, your HttpGet Action populates your model correctly.
public class MyModel
{
public string ddSelect { get ; set; }
public List<Person> Persons { get ; set; }
}
HTML (often called the View)
We explicitly tell the world that this view uses this Model.
#model MyModel // Tell MVC what model we are using for this view
#using (Html.BeginForm("SetOptionForUser", "MyController", FormMethod.Post, new { #class="form-inline" }))
{
#Html.AntiForgeryToken()
<div class="text-center">
<div class="form-group">
#Html.DropDownListFor(m => m.ddSelect, Model.PersonList, new {id = "ddlSelect"})
</div>
<button type="submit" class="btn btn-primary" id="btnEnter">Go</button>
</div>
}
MVC Controller
Your controller is expecting your view to return a model of type MyModel and thus will try to push the form variables into a copy of your model (if it can). Since your form does not fill out a model property, you get nulls back.
[ValidateAntiForgeryToken]
[HttpPost]
public ActionResult SetOptionForUser(MyModel theModel, FormsCollection collection)
{
var selectedName = theModel.ddSelect;
return RedirectToAction("AnotherAction", "AnotherController");
}
This should return the value you selected from the dropdown list. Pay attention to the generated HTML from your site (Press F12 in browser and view source) to see how MVC does things. It's easier to play along than to fight it. In the above POST Action, see the included parameter collection... That holds all of the form variables. It can be gone through to find non model values if required. It is not necessary and can be removed, but poking around in that collection affords a nice view of how data is structured upon POST.
Hope this Helps.
I have tried this is RC1 and then upgraded to RC2 which did not resolve the issue.
// in my controller
ViewData["UserId"] = new SelectList(
users,
"UserId",
"DisplayName",
selectedUserId.Value); // this has a value
result: the SelectedValue property is set on the object
// in my view
<%=Html.DropDownList("UserId", (SelectList)ViewData["UserId"])%>
result: all expected options are rendered to the client, but the selected attribute is not set. The item in SelectedValue exists within the list, but the first item in the list is always defaulted to selected.
How should I be doing this?
Update
Thanks to John Feminella's reply I found out what the issue is. "UserId" is a property in the Model my View is strongly typed to. When Html.DropDownList("UserId" is changed to any other name but "UserId", the selected value is rendered correctly.
This results in the value not being bound to the model though.
This is how I fixed this problem:
I had the following:
Controller:
ViewData["DealerTypes"] = Helper.SetSelectedValue(listOfValues, selectedValue) ;
View
<%=Html.DropDownList("DealerTypes", ViewData["DealerTypes"] as SelectList)%>
Changed by the following:
View
<%=Html.DropDownList("DealerTypesDD", ViewData["DealerTypes"] as SelectList)%>
It appears that the DropDown must not have the same name has the ViewData name :S weird but it worked.
Try this:
public class Person {
public int Id { get; set; }
public string Name { get; set; }
}
And then:
var list = new[] {
new Person { Id = 1, Name = "Name1" },
new Person { Id = 2, Name = "Name2" },
new Person { Id = 3, Name = "Name3" }
};
var selectList = new SelectList(list, "Id", "Name", 2);
ViewData["People"] = selectList;
Html.DropDownList("PeopleClass", (SelectList)ViewData["People"])
With MVC RC2, I get:
<select id="PeopleClass" name="PeopleClass">
<option value="1">Name1</option>
<option selected="selected" value="2">Name2</option>
<option value="3">Name3</option>
</select>
You can still name the DropDown as "UserId" and still have model binding working correctly for you.
The only requirement for this to work is that the ViewData key that contains the SelectList does not have the same name as the Model property that you want to bind. In your specific case this would be:
// in my controller
ViewData["Users"] = new SelectList(
users,
"UserId",
"DisplayName",
selectedUserId.Value); // this has a value
// in my view
<%=Html.DropDownList("UserId", (SelectList)ViewData["Users"])%>
This will produce a select element that is named UserId, which has the same name as the UserId property in your model and therefore the model binder will set it with the value selected in the html's select element generated by the Html.DropDownList helper.
I'm not sure why that particular Html.DropDownList constructor won't select the value specified in the SelectList when you put the select list in the ViewData with a key equal to the property name. I suspect it has something to do with how the DropDownList helper is used in other scenarios, where the convention is that you do have a SelectList in the ViewData with the same name as the property in your model. This will work correctly:
// in my controller
ViewData["UserId"] = new SelectList(
users,
"UserId",
"DisplayName",
selectedUserId.Value); // this has a value
// in my view
<%=Html.DropDownList("UserId")%>
The code in the previous MVC 3 post does not work but it is a good start. I will fix it. I have tested this code and it works in MVC 3 Razor C# This code uses the ViewModel pattern to populate a property that returns a List<SelectListItem>.
The Model class
public class Product
{
public string Name { get; set; }
public decimal Price { get; set; }
}
The ViewModel class
using System.Web.Mvc;
public class ProductListviewModel
{
public List<SelectListItem> Products { get; set; }
}
The Controller Method
public ViewResult List()
{
var productList = new List<SelectListItem>();
foreach (Product p in Products)
{
productList.Add(new SelectListItem
{
Value = p.ProductId.ToString(),
Text = "Product: " + p.Name + " " + p.Price.ToString(),
// To set the selected item use the following code
// Note: you should not set every item to selected
Selected = true
});
}
ProductListViewModel productListVM = new ProductListViewModeld();
productListVM.Products = productList;
return View(productListVM);
}
The view
#model MvcApp.ViewModels.ProductListViewModel
#using (Html.BeginForm())
{
#Html.DropDownList("Products", Model.Products)
}
The HTML output will be something like
<select id="Products" name="Products">
<option value="3">Product: Widget 10.00</option>
<option value="4">Product: Gadget 5.95</option>
</select>
depending on how you format the output. I hope this helps. The code does work.
If we don't think this is a bug the team should fix, at lease MSDN should improve the document. The confusing really comes from the poor document of this. In MSDN, it explains the parameters name as,
Type: System.String
The name of the form field to return.
This just means the final html it generates will use that parameter as the name of the select input. But, it actually means more than that.
I guess the designer assumes that user will use a view model to display the dropdownlist, also will use post back to the same view model. But in a lot cases, we don't really follow that assumption.
Use the example above,
public class Person {
public int Id { get; set; }
public string Name { get; set; }
}
If we follow the assumption,we should define a view model for this dropdownlist related view
public class PersonsSelectViewModel{
public string SelectedPersonId,
public List<SelectListItem> Persons;
}
Because when post back, only the selected value will post back, so it assume it should post back to the model's property SelectedPersonId, which means Html.DropDownList's first parameter name should be 'SelectedPersonId'. So, the designer thinks that when display the model view in the view, the model's property SelectedPersonId should hold the default value of that dropdown list. Even thought your List<SelectListItem> Persons already set the Selected flag to indicate which one is selected/default, the tml.DropDownList will actually ignore that and rebuild it's own IEnumerable<SelectListItem> and set the default/selected item based on the name.
Here is the code from asp.net mvc
private static MvcHtmlString SelectInternal(this HtmlHelper htmlHelper, ModelMetadata metadata,
string optionLabel, string name, IEnumerable<SelectListItem> selectList, bool allowMultiple,
IDictionary<string, object> htmlAttributes)
{
...
bool usedViewData = false;
// If we got a null selectList, try to use ViewData to get the list of items.
if (selectList == null)
{
selectList = htmlHelper.GetSelectData(name);
usedViewData = true;
}
object defaultValue = (allowMultiple) ? htmlHelper.GetModelStateValue(fullName, typeof(string[])) : htmlHelper.GetModelStateValue(fullName, typeof(string));
// If we haven't already used ViewData to get the entire list of items then we need to
// use the ViewData-supplied value before using the parameter-supplied value.
if (defaultValue == null && !String.IsNullOrEmpty(name))
{
if (!usedViewData)
{
defaultValue = htmlHelper.ViewData.Eval(name);
}
else if (metadata != null)
{
defaultValue = metadata.Model;
}
}
if (defaultValue != null)
{
selectList = GetSelectListWithDefaultValue(selectList, defaultValue, allowMultiple);
}
...
return tagBuilder.ToMvcHtmlString(TagRenderMode.Normal);
}
So, the code actually went further, it not only try to look up the name in the model, but also in the viewdata, as soon as it finds one, it will rebuild the selectList and ignore your original Selected.
The problem is, in a lot of cases, we don't really use it that way. we just want to throw in a selectList with one/multiple item(s) Selected set true.
Of course the solution is simple, use a name that not in the model nor in the viewdata. When it can not find a match, it will use the original selectList and the original Selected will take affect.
But i still think mvc should improve it by add one more condition
if ((defaultValue != null) && (!selectList.Any(i=>i.Selected)))
{
selectList = GetSelectListWithDefaultValue(selectList, defaultValue, allowMultiple);
}
Because, if the original selectList has already had one Selected, why would you ignore that?
Just my thoughts.
This appears to be a bug in the SelectExtensions class as it will only check the ViewData rather than the model for the selected item. So the trick is to copy the selected item from the model into the ViewData collection under the name of the property.
This is taken from the answer I gave on the MVC forums, I also have a more complete answer in a blog post that uses Kazi's DropDownList attribute...
Given a model
public class ArticleType
{
public Guid Id { get; set; }
public string Description { get; set; }
}
public class Article
{
public Guid Id { get; set; }
public string Name { get; set; }
public ArticleType { get; set; }
}
and a basic view model of
public class ArticleModel
{
public Guid Id { get; set; }
public string Name { get; set; }
[UIHint("DropDownList")]
public Guid ArticleType { get; set; }
}
Then we write a DropDownList editor template as follows..
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %>
<script runat="server">
IEnumerable<SelectListItem> GetSelectList()
{
var metaData = ViewData.ModelMetadata;
if (metaData == null)
{
return null;
}
var selected = Model is SelectListItem ? ((SelectListItem) Model).Value : Model.ToString();
ViewData[metaData.PropertyName] = selected;
var key = metaData.PropertyName + "List";
return (IEnumerable<SelectListItem>)ViewData[key];
}
</script>
<%= Html.DropDownList(null, GetSelectList()) %>
This will also work if you change ArticleType in the view model to a SelectListItem, though you do have to implement a type converter as per Kazi's blog and register it to force the binder to treat this as a simple type.
In your controller we then have...
public ArticleController
{
...
public ActionResult Edit(int id)
{
var entity = repository.FindOne<Article>(id);
var model = builder.Convert<ArticleModel>(entity);
var types = repository.FindAll<ArticleTypes>();
ViewData["ArticleTypeList"] = builder.Convert<SelectListItem>(types);
return VIew(model);
}
...
}
The problems is that dropboxes don't work the same as listboxes, at least the way ASP.NET MVC2 design expects: A dropbox allows only zero or one values, as listboxes can have a multiple value selection. So, being strict with HTML, that value shouldn't be in the option list as "selected" flag, but in the input itself.
See the following example:
<select id="combo" name="combo" value="id2">
<option value="id1">This is option 1</option>
<option value="id2" selected="selected">This is option 2</option>
<option value="id3">This is option 3</option>
</select>
<select id="listbox" name="listbox" multiple>
<option value="id1">This is option 1</option>
<option value="id2" selected="selected">This is option 2</option>
<option value="id3">This is option 3</option>
</select>
The combo has the option selected, but also has its value attribute set. So, if you want ASP.NET MVC2 to render a dropbox and also have a specific value selected (i.e., default values, etc.), you should give it a value in the rendering, like this:
// in my view
<%=Html.DropDownList("UserId", selectListItems /* (SelectList)ViewData["UserId"]*/, new { #Value = selectedUser.Id } /* Your selected value as an additional HTML attribute */)%>
In ASP.NET MVC 3 you can simply add your list to ViewData...
var options = new List<SelectListItem>();
options.Add(new SelectListItem { Value = "1", Text = "1" });
options.Add(new SelectListItem { Value = "2", Text = "2" });
options.Add(new SelectListItem { Value = "3", Text = "3", Selected = true });
ViewData["options"] = options;
...and then reference it by name in your razor view...
#Html.DropDownList("options")
You don't have to manually "use" the list in the DropDownList call. Doing it this way correctly set the selected value for me too.
Disclaimer:
Haven't tried this with the web forms view engine, but it should work too.
I haven't tested this in the v1 and v2, but it might work.
I managed to get the desired result, but with a slightly different approach. In the Dropdownlist i used the Model and then referenced it. Not sure if this was what you were looking for.
#Html.DropDownList("Example", new SelectList(Model.FeeStructures, "Id", "NameOfFeeStructure", Model.Matters.FeeStructures))
Model.Matters.FeeStructures in above is my id, which could be your value of the item that should be selected.
I've got an MVC 3 application with a page where users can request more information about our service.
When the user selects their state from a dropdown list, I want to use jQuery ajax to fetch a list of Products we offer in that state. For each Product, I'd like to display a check box next to the Product name. Then the user can select which Products they are interested in.
I'd like to bind the selected Products to a List property on the model for this view. So the model will have a property for name, email, etc. and a List property. Here's an example of the model class I'm trying to bind to:
public class RequestInformationModel
{
public string Name { get; set; }
public string Email { get; set; }
public List<Product> Products { get; set; }
}
Here is the controller method I will post the form to:
public ActionResult RequestInformation(RequestInformationModel model)
{
// do stuff
}
Any advice on how to accomplish this? Thanks very much.
If you prefix the name of the fields in your product object with the name of the list and their index in square brackets, then MVC will bind the list for you
E.g.
<input type="checkbox" name="Products[0].InterestedInCheckbox" />
<input type="checkbox" name="Products[1].InterestedInCheckbox" />
In this case the Product object needs a bool property called InterestedInCheckbox
You should have sequencial indexes starting at 0 for this method to work (you would need a hidden field and a slightly different technique if this is not the case)
Finally, I'd use the Html Checkbox helper for creating the checkboxes since this will output a hidden field after the checkboxes required:
(following code is untested)
#for (var i = 0; i < Model.Count; i++)
{
#Html.CheckBox(string.Format("Products[{0}]", i), Model[i])
}
I think what you want is something like this:
/* Handles ajax request */
[HttpPost]
public ContentResult(string selectedListBoxItem)
{
string content = "";
/* TODO: Query DB to get product suggestions based on selectedListBoxItem */
content = "<input type='checkbox' id='productId1' name='productSuggestion' value='productId1'>";
content += "<input type='checkbox' id='productId2' name='productSuggestion' value='productId2'>";
content += "<input type='checkbox' id='productId2' name='productSuggestion' value='productId2'>";
return Content(content);
}
/* Handles final submit */
[HttpPost]
public ActionResult HandleSubmit(string nameOfCheckboxCollection)
{
string[] arrayChecked = nameOfCheckboxCollection.Split(",");
foreach(string s in arrayChecked ) {
Model.addProduct(s);
}
}
I have a simple form that I would like to validate on form submission. Note I have stripped out the html for ease of viewing
<%=Html.TextBox("LastName", "")%> //Lastname entry
<%=Html.ValidationMessage("LastName")%>
<%=Html.TextBox("FirstName", "")%>//Firstname entry
<%=Html.ValidationMessage("FirstName")%>
<%=Html.DropDownList("JobRole", Model.JobRoleList)%> //Dropdownlist of job roles
<% foreach (var record in Model.Courses) // Checkboxes of different courses for user to select
{ %>
<li><label><input type="checkbox" name="Courses" value="<%=record.CourseName%>" /><%= record.CourseName%></label></li>
<% } %>
On submission of this form I would like to check that both FirstName and LastName are populated (i.e. non-zero length).
In my controller I have:
public ActionResult Submit(string FirstName, string LastName)
{
if (FirstName.Trim().Length == 0)
ModelState.AddModelError("FirstName", "You must enter a first name");
if (LastName.Trim().Length == 0)
ModelState.AddModelError("LastName", "You must enter a first name");
if (ModelState.IsValid)
{
//Update database + redirect to action
}
return View(); //If ModelState not valid, return to View and show error messages
}
Unfortunately, this code logic produces an error that states that no objects are found for JobRole and Courses.
If I remove the dropdownlist and checkboxes then all works fine.
The issue appears to be that when I return the View the view is expecting objects for the dropwdownlist and checkboxes (which is sensible as that is what is in my View code)
How can I overcome this problem?
Things I have considered:
In my controller I could create a JobRoleList object and Course object to pass to the View so that it has the objects to render. The issue with this is that it will overwrite any dropdownlist / checkbox selections that the user has already made.
In the parameters of my controller method Submit I could aslo capture the JobRoleList object and Course object to pass back to the View. Again, not sure this would capture any items the user has already selected.
I have done much googling and reading but I cannot find a good answer. When I look at examples in books or online (e.g. Nerddinner) all the validation examples involve simple forms with TextBox inputs and don't seems to show instances with multiple checkboxes and dropdownlists.
Have I missed something obvious here? What would be best practice in this situation?
Thanks
The best practice would be to accept a view-specific model. You could have a shared model that has both the properties that are needed to render the page and the properties required on post or separate models for rendering and accepting the post parameters. I usually go with a shared model as that means I can simply return the model I've received, suitably re-populated with any data needed to generate menus (such as JobList and Courses). Often I will have a method that takes a model of this type and returns a view with the menu properties populated.
public ActionResult JobsView( JobsViewModel model )
{
model.JobList = db.Jobs.Select( j => new SelectListItem
{
Text = j.Name,
Value = j.ID.ToString()
});
...
return View( model );
}
Then call this method from any of my actions that require a view with this type of model, to ensure that the model has the proper menu data.
// on error, return the view
return JobsView( model );
When using a model, you can also use DataAnnotations to decorate your model properties and let the model binder do validation for you, as well as enable client-side validation based on the model. Any validation not supported by the existing attributes can be implemented by creating your own attributes or done within the controller action.
public class JobsViewModel
{
[Required]
public string FirstName { get; set; }
[Required]
public string LastName { get; set; }
public int JobRole { get; set; }
[ScaffoldColumn(false)]
public IEnumerable<SelectListItem> JobRoleList { get; set; }
...
}
public ActionResult Submit( JobsViewModel model )
{
if (ModelState.IsValid)
{
... convert model to entity and save to DB...
}
return JobsView( model );
}
Then enable validation in your HTML
...
<script type="text/javascript" src="<%= Url.Content( "~/scripts/MicrosoftMvcValidation.js" ) %>"></script>
<% Html.EnableClientValidation(); %>
... begin form...
<%=Html.TextBoxFor( m=> m.LastName )%>
<%=Html.ValidationMessageFor( m => m.LastName )%>
<%=Html.TextBoxFor( m=> m.FirstName )%>
<%=Html.ValidationMessageFor( m => m.FirstName )%>
<%=Html.DropDownListFor( m=> m.JobRole, Model.JobRoleList)%>
<% foreach (var record in Model.Courses) // Checkboxes of different courses for user to select
{ %>
<li><label><input type="checkbox" name="Courses" value="<%=record.CourseName%>" /><%= record.CourseName%></label></li>
<% } %>
In simple MVC validation your supposed to pass the strongly typed object in other words your views model.
Example:
public ActionResult Update(Employees employee)
{
if (employee.Name.Trim().Length == 0)
ModelState.AddModelError("Name", "Name is required.");
// ETC....
}