DropDown Population Error in ASP.NET MVC 5 - asp.net-mvc

I have the following in my controller:
public ActionResult Create()
{
ViewBag.PlayerId = new SelectList(db.Players, "Id", "Name");
return View();
}
This is in the view:
<div class="form-group">
#Html.LabelFor(model => model.PlayerId, "PlayerId", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownList("PlayerId", null, htmlAttributes: new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.PlayerId, "", new { #class = "text-danger" })
</div>
</div>
But when I submit the form, it gives me the below error:
System.InvalidOperationException: 'The ViewData item that has the key 'PlayerId' is of type 'System.Int32' but must be of type 'IEnumerable'.'
I googled a lot but were not able to find the solution. Your help is highly appreciated.

Write your #Html.DropDownList as follows:
#Html.DropDownList("PlayerId", ViewBag.PlayerId as SelectList,"Select Player", htmlAttributes: new { #class = "form-control" })
Now it will work!

You have to pass the SelectList into dropdown but actually the model binder is confused between PlayerId as viewmodel property and PlayerId as ViewBag property, hence causing the error.
Better to create a viewmodel property which will store option list with different name:
public class ViewModel
{
public int PlayerId { get; set; }
// other properties
// option list here
public List<SelectListItem> PlayerList { get; set; }
}
Then add the option lists from database into controller action:
public ActionResult Create()
{
var model = new ViewModel();
model.PlayerList = db.Players.Select(x => new SelectListItem { Text = x.Name, Value = x.Id }).ToList();
return View(model);
}
And use strongly-typed helper to bind it afterwards:
#Html.DropDownListFor(model => model.PlayerId, Model.PlayerList, "Select", new { #class = "form-control" })
Related issue:
The ViewData item that has the key is of type 'System.Int32' but must be of type 'IEnumerable<SelectListItem>'

Related

MVC Dynamic View Binding to Dictionary of Properties for Known and Unknown properties

I have been trying to find a solution to a situation that I'm busy designing, however I have not managed to get to it.
Imagine having the following model
public enum InputType
{
TextInput,
LookupInput
}
public struct AdditionalProperty
{
public string Key {get;set;}
public string Value {get;set;}
public InputType Type {get;set;}
}
public class Person
{
public string FirstName {get;set;}
public List<AdditionalProperty> AdditionalProperties {get;set;}
}
Then, having the following controller
public class HomeController
{
public ActionResult Index()
{
var model = new Person { FirstName = "MyName" };
model.AdditionalProperties = new List<AdditionalProperty>();
var listItem = new AdditionalProperty
{
Key = "Surname",
Value = "MySurname"
};
model.AdditionalProperties.Add(listItem);
return View(model)
}
}
What I'm looking for is the Razor view code on how to "dynamically" create the properties with the correct input type, bound to something in order for me to be able to still use the model when the form gets posted back to the controller for a Save function.
So the property that is known, would be something like this:
<div class="form-group">
<div class="form-row">
<div class="col-md-6">
#Html.LabelFor(model => model.FirstName, new { #class = "control-label" })
<div>
#Html.TextBoxFor(model => model.FirstName, new { #class = "form-control", placeholder = "Enter Group Name", type = "text" })
#Html.ValidationMessageFor(model => model.FirstName, "", new { #class = "text-danger" })
</div>
</div>
</div>
</div>
The Idea would then be to have the following. Obviously the below isn't sufficient, and this is where I need the help.
I would like to show the additional properties, one below the other, each on a separate line (using bootstrap row) based on the property.InputType
#foreach (var property in Model.Properties)
{
#Html.LabelFor(model => property.Key, new { #class = "control-label" })
<div>
#if (property.InputType == TextInput)
{
#Html.TextBoxFor(model => property.Value, new { #class = "form-control", placeholder = "Enter Group Name", type = "text" })
}
#Html.ValidationMessageFor(model => property.Key, "", new { #class = "text-danger" })
</div>
}
Thus, I would like to see my view as:
| <label> | <input>
Known Property | FirstName | MyFirstName
Unknown Property | Surname | MySurname
In terms of completeness, I am posting the following answer.
I am going to post the Model, View (Index & EditorTemplates) & Controller to show the complete working solution that I used to test the answer that was given to me.
My Model class for this test
Person.cs
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public List<AdditionalProperty> AdditionalProperties { get; set; }
}
AdditionalProperty.cs
public struct AdditionalProperty
{
public string Key { get; set; }
public object Value { get; set; }
public DateTime? DateValue
{
get
{
DateTime dateValue;
if (DateTime.TryParse(Value?.ToString(), out dateValue))
{
return dateValue;
}
return null;
}
set => Value = value;
}
public InputType InputType { get; set; }
public List<SelectListItem> ValueLookupItems { get; set; }
}
The reason I have a separate DateValue property here is to assist the browser when doing DateTime binding otherwise the DateTimePicker doesn't show.
I used an enum to determine what type of input type this specific property should make use of.
InputType.cs
public enum InputType
{
TextBox,
DropdownBox,
TextArea,
DateSelection,
}
In order to keep the views as simple as possible, Stephen provided me with a sample for the Index View as well as an EditorTemplate for the AdditionalProperty object. The EditorTemplate is used for separation of concerns and to ensure that all the logic behind what input type is being used is in one place.
I have found that the DateTime property doesn't work well, so an additional EditorTemplate was required. I got this from this post.
DateTime.cshtml
Note: Location of template -> /Views/Shared/EditorTemplates
#model DateTime
#{
IDictionary<string, object> htmlAttributes;
object objAttributes;
if (ViewData.TryGetValue("htmlAttributes", out objAttributes))
{
htmlAttributes = objAttributes as IDictionary<string, object> ?? HtmlHelper.AnonymousObjectToHtmlAttributes(objAttributes);
}
else
{
htmlAttributes = new RouteValueDictionary();
}
htmlAttributes.Add("type", "date");
String format = (Request.UserAgent != null && Request.UserAgent.Contains("Chrome")) ? "{0:yyyy-MM-dd}" : "{0:d}";
#Html.TextBox("", Model, format, htmlAttributes)
}
AdditionalProperty.cshtml
Note: Location of template -> /Views/Shared/EditorTemplates
Note: The location of my AdditionalProperty formed part of the DynamicViewExample.Models namespace
#model DynamicViewExample.Models.AdditionalProperty
<div>
#Html.HiddenFor(m => m.Key)
#Html.LabelFor(m => m.Key, Model.Key, new {#class = "control-label"})
#if (Model.InputType == DynamicViewExample.Models.InputType.TextBox)
{
#Html.TextBoxFor(m => m.Value, new {#class = "form-control"})
}
else if (Model.InputType == DynamicViewExample.Models.InputType.TextArea)
{
#Html.TextAreaFor(m => m.Value, new {#class = "form-control"})
}
else if (Model.InputType == DynamicViewExample.Models.InputType.DropdownBox)
{
#Html.DropDownListFor(m => m.Value, Model.ValueLookupItems, new {#class = "form-control"})
}
else if (Model.InputType == DynamicViewExample.Models.InputType.DateSelection)
{
#Html.EditorFor(m => m.DateValue, new {#class = "form-control"})
}
else
{
#Html.HiddenFor(m => m.Value) // we need this just in case
}
</div
This would be how the Index.cshtml file would look
#model DynamicViewExample.Models.Person
#{
ViewBag.Title = "Home Page";
}
#using (Html.BeginForm())
{
<div class="row">
#Html.LabelFor(model => model.FirstName, new { #class = "control-label" })
#Html.TextBoxFor(model => model.FirstName, new { #class = "form-control", placeholder = "Enter Group Name", type = "text" })
#Html.ValidationMessageFor(model => model.FirstName, "", new { #class = "text-danger" })
</div>
<div class="row">
#Html.LabelFor(model => model.LastName, new { #class = "control-label" })
#Html.TextBoxFor(model => model.LastName, new { #class = "form-control", placeholder = "Enter Group Name", type = "text" })
#Html.ValidationMessageFor(model => model.LastName, "", new { #class = "text-danger" })
</div>
<div class="row">
#Html.EditorFor(m => m.AdditionalProperties, new { htmlAttributes = new { #class = "form-control"}})
</div>
<input type="submit" class="btn btn-primary" />
}
And then finally, the HomeController.cs file contains a Get and Post that allows the ability to manipulate the data as you please. What is missing here is the "dynamic" way of populating the model, but that will naturally happen once a DB has been introduced into the mix.
[HttpGet]
public ActionResult Index()
{
var model = new Person
{
FirstName = "Gawie",
LastName = "Schneider",
AdditionalProperties = new List<AdditionalProperty>
{
new AdditionalProperty {Key = "Identification Number", Value = "1234567890123456", InputType = InputType.TextBox},
new AdditionalProperty {Key = "Date Of Birth", Value = DateTime.Today, InputType = InputType.DateSelection},
new AdditionalProperty {Key = "Age", Value = "31", InputType = InputType.TextBox},
new AdditionalProperty {Key = "Gender", Value = "Male", InputType = InputType.DropdownBox,
ValueLookupItems = new List<SelectListItem>
{
new SelectListItem{Text = "Male", Value = "Male"},
new SelectListItem{Text = "Female", Value = "Female"}
}},
}
};
return View(model);
}
[HttpPost]
public ActionResult Index(Person model)
{
//Do some stuff here with the model like writing it to a DB perhaps
return RedirectToAction("Index");
}
So if I would have to sum up what I was trying to do here.
The goal I wanted to achieve was to be able to make use of Strongly Typed / Known Properties in conjunction with Dynamic / Unknown Properties to create a system that would allow the user to create new inputs on the fly without the need for a developer to be involved.
I honestly hope that this might help someone else as well some day.
Enjoy the coding experience
Gawie

Can not retrieve DropdownList value in model using mvc & ado.net

I am sending selectedlist to view using ViewBag. Here is the get method that i pass through ViewBag
public List<Dept> GetDept()
{
connection();
List<Dept> deptList = new List<Dept>();
SqlCommand com = new SqlCommand("Sp_GetDept", con);
com.CommandType = CommandType.StoredProcedure;
SqlDataAdapter da = new SqlDataAdapter(com);
DataTable dt = new DataTable();
con.Open();
da.Fill(dt);
con.Close();
//Bind EmpModel generic list using dataRow
foreach (DataRow dr in dt.Rows)
{
deptList.Add(
new Dept
{
DeptId = Convert.ToInt32(dr["DeptId"]),
Name = Convert.ToString(dr["Name"])
}
);
}
return deptList;
}
public ActionResult Create()
{
DeptRepo repo = new DeptRepo();
ViewBag.Dept = new SelectList(repo.GetDept(), "DeptId", "Name");
return View();
}
View Code:
<div class="form-group">
#Html.LabelFor(model => model.Dept, "Department", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownList("Dept", null, "--Select--", htmlAttributes: new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.Dept, "", new { #class = "text-danger" })
</div>
</div>
Student model:
public class Student
{
public int StudentId { get; set; }
public string Name { get; set; }
public string Roll { get; set; }
public int DeptId { get; set; }
public virtual Dept Dept { get; set; }
}
post method:
[HttpPost]
public ActionResult Create(Student std)
{
try
{
if (ModelState.IsValid)
{
StudentRepo repo = new StudentRepo();
repo.AddStudent(std);
}
return RedirectToAction("Index");
}
catch
{
return View();
}
}
In post method dropdownlist id value found null in student object.
Can anybody tell me how i can retrieve foreignkey Id using mvc and ado.net.
Any kind of help would be greatly appreciated.
Your current code,
#Html.DropDownList("Dept", null, "--Select--",
htmlAttributes: new { #class = "form-control" })
will generate HTML markup for a SELECT element with name attribute value set to Dept
<select class="form-control" id="Dept" name="Dept">
<option value="">--Select--</option>
</select>
Since you are using the Student class as your httppost action method parameter, for model binding to properly map the selected option value to DeptId property of the Student object, you need to make sure that your select element name is also DeptId
If your view is strongly typed to the Student class, you can use DropDownListFor helper method
#Html.DropDownListFor(a => a.DeptId, ViewBag.Dept as IEnumerable<SelectListItem>,
"--Select--", htmlAttributes: new { #class = "form-control" })
Or
You can use DropDownList method and give DeptId as the first param (name of the control) and explicitly specify the collection to use for building the options as the second param.
#Html.DropDownList("DeptId", ViewBag.Dept as IEnumerable<SelectListItem>,
"--Select--", htmlAttributes: new { #class = "form-control" })
This will render the SELECT element with name attribute value set to DeptId and when the form is submitted, model binder will be able to use the selected option value to set it to the DeptId property of the Student object(which is your httppost action method parameter)

How to save dropdownlist selected value to the database int asp.net MVC 5

I am currently new to Asp.net MVC .In one of the view I add a dropdownlist and I bind this dropdownlist with my database like this
Controller CollegeController
[HttpGet]
public ActionResult Create()
{
IEnumerable<SelectListItem> items = db.College_Names.Select(c => new SelectListItem { Value = c.id.ToString(), Text = c.Name });
IEnumerable<SelectListItem> item = db.Stream_Names.Select(c => new SelectListItem { Value = c.id.ToString(), Text = c.Stream });
ViewBag.CollName=items;
ViewBag.StreamName = item;
return View();
}
[HttpPost]
public ActionResult Create(College college)
{
try
{
if(ModelState.IsValid)
{
db.Colleges.Add(college);
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.CollName = db.Colleges;
return View(college);
}
catch
{
return View();
}
}
This is my model
public class College
{
[Required]
public int Id { get; set; }
[Required]
[Display(Name="College Name")]
public int CollegeName { get; set; }
[Required]
public int Stream { get; set; }
[Required]
[Column(TypeName="varchar")]
public string Name { get; set; }
....
public virtual College_Name College_Name { get; set; }
public virtual Stream_Name Stream_Name { get; set; }
}
This is My View
<div class="form-group">
#Html.LabelFor(model => model.CollegeName, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownList("CollName", (IEnumerable<SelectListItem>)ViewBag.CollName, "Select College", new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.CollegeName, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Stream, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownList("StreamName", (IEnumerable<SelectListItem>)ViewBag.StreamName, "Select Stream", new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.Stream, "", new { #class = "text-danger" })
</div>
</div>
Now when I check my database after I save the CollegeName and Stream in the database is zero from the dropdownlist.
You have multiple problems with your code. Firstly you dropdownlists are binding to a properties named CollName and StreamName which do not even exist in your model.
Next you cannot name the property your binding to the same as the ViewBag property.
Your view code would need to be (and always use the strongly typed xxxFor() HtmHelper methods
#Html.DropDownListFor(m => m.CollegeName, (IEnumerable<SelectListItem>)ViewBag.CollName, "Select College", new { #class = "form-control" })
....
#Html.DropDownListFor(m => m.Stream, (IEnumerable<SelectListItem>)ViewBag.StreamName, "Select Stream", new { #class = "form-control" }
and in your POST method, the values of college.CollegeName and college.Stream will contain the ID's of the selected options.
You also need to repopulate the ViewBag properties when you return the view in the POST method (as you did in the GET method) or an exception will be thrown (and note that your current use of ViewBag.CollName = db.Colleges; will also throw an exception)
I also strongly suggest you start learning to use view models (views for editing should not use data models - refer What is ViewModel in MVC?) - and use naming conventions that reflect what your properties are, for example CollegeNameList, or CollegeNames, not CollName

Validation message won't appear - accepts 'please select' as an entry

I am submiting a blank form and all of the other fields display their correct validation error message stating that the field cannot be empty, except for one.
Here is the model:
public enum Medium
{
[Description("Teleconference & Report")]
Teleconference_Report,
[Description("Email & Telephone")]
Email_Telephone
}
[Required]
[Display(Name = "Medium")]
public Medium Medium { get; set; }
Here is the field in the form:
<div class="form-group">
#Html.LabelFor(model => model.Medium, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-5">
#Html.DropDownList("MediumID", null, "Please select...", htmlAttributes: new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.Medium, "", new { #class = "text-danger" })
</div>
</div>
The "MediumID" DropDownList is populated using a viewbag which is set to whatever the following returns:
// Puts all of the mediums of communication into a user friendly dropdownlist.
public List<SelectListItem> GetMediumList()
{
List<SelectListItem> mediumList = new List<SelectListItem>();
foreach (Medium state in EnumToList<Medium>())
{
mediumList.Add(new SelectListItem
{
Text = GetEnumDescription(state),
Value = state.ToString(),
});
}
return mediumList;
}
The issue is, is that when the default display of the drop down list of 'please select...' is left, this is accepted and the form gets submitted, which doesn't happen for any of my other fields. When looking at the new entry in the database, the 'Medium' field gets set to the first thing in the DropDownList which is 'Teleconference & Report'.
Any help is greatly appreciated.
EDIT:
Below shows the form section for another enum called 'Frequency', but these are not changed to user friendly strings.
<div class="form-group">
#Html.LabelFor(model => model.Frequency, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-5">
#Html.EnumDropDownListFor(model => model.Frequency, "Please select...", htmlAttributes: new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.Frequency, "", new { #class = "text-danger" })
</div>
</div>
Below here, shows the two methods which turn the enums into user friendly strings:
// Returns a 'user friendly', readable version of the enum.
public static string GetEnumDescription(Enum value)
{
FieldInfo fi = value.GetType().GetField(value.ToString());
DescriptionAttribute[] attributes =
(DescriptionAttribute[])fi.GetCustomAttributes(typeof(DescriptionAttribute), false);
if (attributes != null && attributes.Length > 0)
return attributes[0].Description;
else
return value.ToString();
}
// Puts all of the same enums into a list.
public static IEnumerable<T> EnumToList<T>()
{
Type enumType = typeof(T);
// Can't use generic type constraints on value types,
// so have to do check like this.
if (enumType.BaseType != typeof(Enum))
throw new ArgumentException("T must be of type System.Enum");
Array enumValArray = Enum.GetValues(enumType);
List<T> enumValList = new List<T>(enumValArray.Length);
foreach (int val in enumValArray)
{
enumValList.Add((T)Enum.Parse(enumType, val.ToString()));
}
return enumValList;
}
Add the EnumDataType attribute to the property on your view model:
[EnumDataType(typeof(Medium), ErrorMessage = "Submitted value is not valid for the enum")]
public Medium Medium { get; set; }
You should add this anyway, because an Enum can represent any numeric value and won't be validated automatically by the MVC framework.
I hope this helps!

MVC5 problems with DropDownList and view model

So I have a simple database table in the form of ID, EmployeeID, date etc. Which creates a normal model:
public partial class WorkItem
{
public int ID { get; set; }
public int EmployeeID { get; set; }
public int LocationID { get; set; }
[DataType( DataType.Date )]
[Display( Name = "Start date" )]
public System.DateTime StartDate { get; set; }
My problem occurs when I need to augment the functionality of this model and so I create a view model to group work items on a weekly basis.
public class WeeklyWorkItemsViewModel
{
public WorkItem WorkItemMonday { get; set; }
public WorkItem WorkItemTuesday { get; set; }
All works perfectly well for the DateTime field in my view (which is bound to the view model):
<div class="form-group">
#Html.LabelFor( model => model.WorkItemMonday.StartDate, "Week start date", htmlAttributes: new { #class = "control-label col-md-2" } )
<div class="col-md-10">
#Html.EditorFor( model => model.WorkItemMonday.StartDate, new { htmlAttributes = new { #class = "form-control" } } )
#Html.ValidationMessageFor( model => model.WorkItemMonday.StartDate, "", new { #class = "text-danger" } )
</div>
</div>
The problem occurs trying to bind the dropdownlilst, it gets populated correctly but the changes are not seen in the controller.
<div class="form-group">
#Html.LabelFor( model => model.WorkItemMonday.EmployeeID, "EmployeeID", htmlAttributes: new { #class = "control-label col-md-2" } )
<div class="col-md-10">
#Html.Hidden( "selectedEmployee" )
#Html.DropDownList( "EmployeeID", null, htmlAttributes: new { #class = "form-control" } )
#Html.ValidationMessageFor( model => model.WorkItemMonday.EmployeeID, "", new { #class = "text-danger" } )
</div>
</div>
The StartDate is updated in the controller.
After mucho head scratching, I finally had to get around this using:
#Html.Hidden( "selectedEmployee" )
And updating this in JQuery. I did try using #html.DropDownListFor but no joy so far.
Can anyone see what's wrong before I pull ALL my hair out.
You model does not contain a property named EmployeeID. But it does have ones named WorkItemMonday.EmployeeID and WorkItemTuesday.EmployeeID.
Stop using DropDownList() and use the strongly typed DropDownListFor() method so that you correctly bind to your model properties.
Modify you view model to include a property for the SelectList
public IEnumerable<SelectListItem> EmployeeList { get; set; }
and populate it in the GET method before you pass the model to the view. Then in the view use
#Html.DropDownListFor(m => m.WorkItemMonday.EmployeeID, Model.EmployeeList, new { #class = "form-control" })
....
#Html.DropDownListFor(m => m.WorkItemTuesday.EmployeeID, Model.EmployeeList, new { #class = "form-control" })
which will correct generate the name="WorkItemMonday.EmployeeID" and name="WorkItemTuesday.EmployeeID" attributes so that they will bind to your model when you post.

Resources