Issue with Model Binding - asp.net-mvc

I have created a View Model called CompetitionRoundModel which is partially produced below:
public class CompetitionRoundModel
{
public IEnumerable<SelectListItem> CategoryValues
{
get
{
return Enumerable
.Range(0, Categories.Count())
.Select(x => new SelectListItem
{
Value = Categories.ElementAt(x).Id.ToString(),
Text = Categories.ElementAt(x).Name
});
}
}
[Display(Name = "Category")]
public int CategoryId { get; set; }
public IEnumerable<Category> Categories { get; set; }
// Other parameters
}
I have structured the model this way because I need to populate a dropdown based on the value stored in CategoryValues. So for my view I have:
#using (Html.BeginForm())
{
<div class="form-group">
#Html.LabelFor(model => model.CategoryId, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownListFor(model => model.CategoryId, Model.CategoryValues, new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.CategoryId, "", new { #class = "text-danger" })
</div>
</div>
// Other code goes here
}
I have selected model.CategoryId in the DropDownListFor() method since I want to bind the selected value to CategoryId. I really don't care for CategoryValues, I just need it to populate the DropDown.
My problem now is that when my Controller receives the values for my Model in the action method, CategoryValues is null which causes the system to throw a ArgumentNullException (the line that is highlighted is the return Enumerable line.
I have even tried [Bind(Exclude="CategoryValues")] but no change at all. Any help would be much appreciated.

Your not (and should not be) creating form controls for each property of each Category in your IEnumerable<Category> collection so in your POST method, the value of Categories is null (it never gets initialized). As soon as you attempt CategoryValues and exception is thrown by your .Range(0, Categories.Count()) line of code in the getter.
Change you view model to give CategoryValues a simple geter/setter, and delete the Categories property
public class CompetitionRoundModel
{
public IEnumerable<SelectListItem> CategoryValues { get; set; }
[Display(Name = "Category")]
public int CategoryId { get; set; }
.... // Other properties
}
and populate the SelectList in the controller methods, for example
var categories db.Categories; // your database call
CompetitionRoundModel model = new CompetitionRoundModel()
{
CategoryValues = categories.Select(x => new SelectListItem()
{
Value = x.Id.ToString(),
Text = x.Name
},
....
};
return View(model);
or alternatively
CompetitionRoundModel model = new CompetitionRoundModel()
{
CategoryValues = new SelectList(categories, "Id", "Name" ),
Note also that if you return the view (because ModelState is invalid, the you need to repopulate the value of CategoryValues (refer The ViewData item that has the key 'XXX' is of type 'System.Int32' but must be of type 'IEnumerable' for more detail)

Since CategoryValues just populates the drop down, it will never post back to the server and you'll need to rebuild the list from the database before using it in the GET or POST operation. The CategoryId property is the value that will be posted back to the server from the DropDownList.

Related

Set The selected Value of a Multiselect from Controller to View after submitting an invalid Model

I am using a Multiselect DropDownList to generate a multiple <select>
I was able to generate it and was working fine.
But If I try to submit it using the parameters:
Name = null
ObjAOption = [1,2] // assume I selected 2 options in my multiselect
ObjAOption will just select option value='1' instead of select options 1, and 2.
Is there any way I can get back the selected options and pass it back to my view by setting it in my controller? I would love to use HTML helper and not to use jQuery or javascript on this part.
Controller:
public ActionResult AddObjectA(AddModel am){
if(ModelState.IsValid){
//Save
}
else {
am.ObjA = // new List of ObjectA with atleast 4 option
return View("MyView",am);
}
}
View:
#Html.LabelFor(model => model.ObjA, "Object A")
#Html.DropDownList("ObjAOption", new MultiSelectList(Model.ObjA, "Key", "Name"), "-- Select Object A--", new { #class = "custom-select custom-select-sm", multiple="" })
#Html.ValidationMessageFor(m => m.ObjAOption, "", new { #class = "text-danger" })
Model:
public class AddModel {
[Required]
public String Name {get;set;}
public IEnumerable<ObjectA> ObjA{ get; set; }
[Required(ErrorMessage = "Please select at least one option")]
public List<int>ObjAOption{ get; set; }
}
public class ObjectA {
public int Key {get;set;}
public string Name {get;set;}
}
Have you tried to use the helper Hiddenfor ? It generate a field that keep your element value, name and attribute :
View:
#Html.LabelFor(model => model.ObjA, "Object A")
#Html.DropDownList("ObjAOption", new MultiSelectList(Model.ObjA, "Key", "Name"), "-- Select Object A--", new { #class = "custom-select custom-select-sm", multiple="" })
#Html.ValidationMessageFor(m => m.ObjAOption, "", new { #class = "text-danger" })
#Html.HiddenFor(m => m.ObjAOption)
Solution:
I scrapped out my DropDownList and tried using ListBoxFor as discussed here

binding dropdown list in MVC

Hi I am trying to Bind my dropdown list in MVC from a model.
Here is my model
[Table("FileConfig")]
public class FileConfigModel
{
[Key]
[Display(Name = "File Congif ID")]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int FileConfigId { get; set; }
....
[Display(Name = "Description")]
public string Description { get; set; }
}
Here is my getall method in the controller:
public List<FileConfigModel> GetAll()
{
return db.FileConfigModels.ToList();
}
Then I am calling it from my another controller
public ActionResult Create()
{
var fileConfigListEntries = new FileConfigController().GetAll()
.Select(fc => new SelectListItem
{
Value = fc.FileConfigId.ToString(),
Text = fc.Description,
Selected = false
});
ViewBag.FileConfigEntires = fileConfigListEntries;
return View();
}
And here is my view:
#Html.LabelFor(model => model.FileConfigId, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownListFor(model => model.FileConfigId, ViewBag.FileConfigEntires as SelectList, "-Select File Config")
#Html.ValidationMessageFor(model => model.FileConfigId)
</div>
However, I've been keep getting error saying
"There is no ViewData item of type 'IEnumerable' that has the key 'FileConfigId'.."
Could someone please help me and tell me what I've missed.
Your query for fileConfigListEntries (i.e. ..Select(fc => new SelectListItem{ .. }) returns IEnumerable<SelectListItem>
In the view, you then try and convert that to typeof SelectList using ViewBag.FileConfigEntires as SelectList
SelectList is IEnumerable<SelectListItem>, but IEnumerable<SelectListItem> is not SelectList, therefore the conversion fails, and the 2nd parameter of DropDownListFor() is null. When the 2nd parameter is null, the method expects the 1st parameter to be IEnumerable<SelectListItem> which it is not, hence the exception.
Change your code to
#Html.DropDownListFor(m => m.FileConfigId, ViewBag.FileConfigEntires as IEnumerable<SelectListItem>, ... )
or
#Html.DropDownListFor(model => model.FileConfigId, (IEnumerable<SelectListItem>)ViewBag.FileConfigEntires, ... )
Side note There is no point setting Selected = false in the .Select clause - its false by default, but in any case, its ignored when binding to a model property (its the value of the property which determines what is selected)
Possible reason of above error could be variable fileConfigListEntries is null or not got the data from dbcontext.
As drop down list is bounded with null or collection with no elements error coming as "There is no ViewData item of type 'IEnumerable' that has the key 'FileConfigId'.."
I suggest replace fileConfigListEntries view bag data with hard-coded data and see error disappears.

Dropdownlist value is null after posting to controller in ASP.NET MVC

I can get all Roles plus actually Role for chosed user, but then When I posting to EditUser action, then Dropdownlist sends null.
I mean When the form posts to my controller, I get null from DropDownList.
Here is my Model
public class EditUserViewModel
{
public string Id { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public List<SelectListItem> ApplicationRoles { get; set; }
public string ApplicationRoleId { get; set; }
}
Here is Action
[HttpGet]
public async Task<ActionResult> EditUser(string id)
{
EditUserViewModel model = new EditUserViewModel();
model.ApplicationRoles = RoleManager.Roles.Select(r => new SelectListItem
{
Text = r.Name,
Value = r.Id
}).ToList();
if (!String.IsNullOrEmpty(id))
{
ApplicationUser user = await UserManager.FindByIdAsync(id);
if (user != null)
{
var role = await UserManager.GetRolesAsync(user.Id);
var existingRole = role.First();
string existingRoleId = RoleManager.Roles.Single(r => r.Name == existingRole).Id;
model.Id = user.Id;
model.FirstName = user.FirstName;
model.ApplicationRoleId = existingRoleId;
ViewBag.RoleId = new SelectList(RoleManager.Roles, "Id", "Name", model.ApplicationRoleId);
}
}
return PartialView("_EditUser", model);
}
And here is DropDownlist from _EditUser.cshtml
<div class="form-group">
#Html.Label("Role typ", htmlAttributes: new { #class = "control-label col-md-6" })
<div class="col-md-12" title="Ange antal datorer som finns i lager">
#Html.DropDownList("RoleId", null, new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.ApplicationRoles, "", new { #class = "text-danger" })
</div>
</div>
Getting null Only from DropDownList, not from #Html.EditorFor
/Thanks in advance!
Forms post back the name/value pairs of their successful form controls. Your generating a <select> element with name="RoleId" but you model does not contain a property named RoleId. Since you want to bind the selected option to the ApplicationRoleId role property, then you view needs to be
#Html.LabelFor(m => m.ApplicationRoleId)
#Html.DropDownListFor(m => m.ApplicationRoleId, Model.ApplicationRoles)
#Html.ValidationMessageFor(m => m.ApplicationRoleId)
Notes:
Your current #Html.Label(..) code does not create a label
associated with your dropdownlist (clicking on it will not set
focus)
The ValidationMessageFor() need to be applied to the property your
binding to, not the SelectList
Delete you ViewBag.RoleId = new SelectList(..) code. Your have
already assigned the selectlist to the ApplicationRoles property
(and you should never need ViewBag if have a view model anyway)
Because you are declare that only HttpGet methods are allow in that method of the controller. Thats why

Fetching and Adding Checkbox dynamically on selection of Dropdown boxes

I have a form as above. I am trying to work as when user selects Table Name & Permission, it goes back to server side, fetches the columns of the selected table & display all column names as check boxes. When use selects Save btn, HttpPost will execute and when user selects Cancel, to return back to home page.
I have created a ViewModel for this :
// Actual EF Model
public partial class TablePermission
{
public int Id { get; set; }
public int UserId { get; set; }
public int PermissionLevelId { get; set; }
public string TableName { get; set; }
public string RestrictViewFields { get; set; }
public string RestrictEditFields { get; set; }
public virtual PermissionLevel PermissionLevel { get; set; }
public virtual User User { get; set; }
}
// View Model for the View
public class TablePermissionsVM
{
public TablePermissionsVM()
{
TablePermission = new TablePermission();
RestrictViewFields = new List<FieldList>();
// Created for trial to see Checkboxes
RestrictViewFields.Add(new FieldList() { FieldName = "UserId", Selected = false });
RestrictViewFields.Add(new FieldList() { FieldName = "fName", Selected = false });
RestrictViewFields.Add(new FieldList() { FieldName = "lName", Selected = false });
RestrictEditFields = new List<FieldList>();
}
public TablePermission TablePermission { get; set; }
public List<FieldList> RestrictViewFields { get; set; }
public IEnumerable<FieldList> RestrictEditFields { get; set; }
}
// Model to save field names & it's selected status
public class FieldList
{
public string FieldName { get; set; }
public bool Selected { get; set; }
}
}
Controller UPDATED : ADDED THE NEW ACTION (FillFields() ) METHOD that has to called onChange event
[Authorize]
[HttpGet]
public ActionResult TablePermissions(TablePermissionsVM tablePermissionVm)
{
return View(tablePermissionVm);
}
// Action Method to Fill Column names for the List<>.
public ActionResult FillFields(string tableName, string tblPermLevel)
{
// WANT TO RETURN HERE ANOTHER LIST (2 LIST OBJECTS) IN JSON
// restrictView & restrictEdit
var restrictView = DbUtilities.GetColumnNames(tableName);
var restrictEdit = DbUtilities.GetColumnNames(tableName);
return Json(restrictView, JsonRequestBehavior.AllowGet);
}
View - UPDATED CODE : aDDED Bound fields for TableName & TableLevelPermission, Script that I use on the event of change of Table Selected.
UPDATED - aDDED FORM ID, SCRIPT METHOD
#model DataStudio.Models.TablePermissionsVM
using (Html.BeginForm("TablePermissions", "Admin", FormMethod.Post, new { id = "tblPermForm" }) ))
{
#Html.AntiForgeryToken()
<div class="form-group">
#Html.LabelFor(model => model.TablePermission.TableName, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="editor-field">
#Html.DropDownListFor(model => model.TablePermission.TableName,
DbUtilities.GetTableNames(), "Select Table",
new { #class = "form-control", #onchange="FillFields()" })
#Html.ValidationMessageFor(model => model.TablePermission.TableName, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.TablePermission.PermissionLevelId, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="editor-field">
#Html.DropDownListFor(model => model.TablePermission.PermissionLevelId, DbUtilities.GetPermissionLevelList(), "Select Permission Level", new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.TablePermission.PermissionLevelId, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.RestrictViewFields, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="editor-field">
**// NEED TO ADD CHECK BOXES HER RECEIVED THRU SCRIPT**
</div>
</div>
}
<script>
function FillFields() {
var tblName = $('#TablePermission_TableName').val();
var tblPermLevel = $('#TablePermission_PermissionLevelId').val();
//($('tblPermForm').valid()) { ERROR - OBJECT DOESN'T HAVE valid()'
if (tblName != null && tblPermLevel != null) {
$.ajax({
url: '/Admin/FillFields',
type: 'GET',
dataType: "JSON",
data: { TableName: tblName, TablePermLevel: tblPermLevel },
success: function (restrictView) {
$("#RestrictViewFields").html(""); // Clear before appending new ones
$.each(restrictView, function (i, field) {
$("#RestrictViewFields").append(
$('<option></option>').val(field.FieldName).html(field.FieldName))
// WANT TO ADD AS 3 CHECKBOX IN A ROW
});
}
});
}
}
</script>
Their are couple of things that I am not able to figure out & get confused with it. Mainly, on making sure that both the drop down boxes has value, I need to perform again a "Get" and fetch column names for the table selected & display the columns as check boxes.
The way I have implemented Checkboxes, I will get proper selected Values in HttpPost, Right ! Are am I anywhere wrong ?
How to make the Get Request when both the drop down's are selected ??
Any help is highly appreciated. Thanks a lot in advance.
Update I started to try with only TableName selection (I want for both dropdown), but the event doesn't occur and go to FillFields() in script. Where am I going wrong ? I tried this logic from here . Can't get why it doesn't get fired only ???
Btw, this is a full form i mean, their is no partial form in it. I want to fill the check box controls in those 2 RestrictXFields on selection of TableName & Permssion check box & on Save btn, send all to Request & save to db.
UPDATE : THANKS a lot, Stephen & Chethan. With your support, I identified the cause for event not getting triggered. Event is triggered, I am able to retrieve the column names from db, the HTML part on success is not being updated. Stephen, I also added form Id & tried form.valid() as you instructed, but I get error script doesn't identify valid(). Both the fields in Model are marked as Required in MetaData class. Currently, testing both var != null works. But, I liked you valid() option.
Per my understanding, You should fetch the checkboxes using an ajax call.
Create an action method in your controller which accepts selected values of TableName and TableLevelPermisson dropdown. Use these values to fetch
List<FieldList> RestrictViewFields
IEnumerable<FieldList> RestrictEditFields.
and use as data/model to your partial view.
Call this action method using ajax, on change of the dropdown list value.
Get the HTML returned from partial view and use it in your DOM.
How to make the Get Request when both the drop down's are selected ??
If you are using jQuery: Just google for "dropdown change events in jquery" and ajax call examples.

Pass Select into Controller via Response

Hy,
I'm new to ASP.NET MVC 5. I'm trying to get the value of an HTML select with no success.
My View (essential part):
<div class="form-group">
#Html.Label("Country", new { #class = "col-md-2 control-label" })
<div class="col-md-10">
#Html.DropDownList("Countries", (IEnumerable<SelectListItem>)ViewBag.Countries, new { #class = "form-control", id = "Country", name = "Country" })
</div>
</div>
My Controller (essential part):
public ActionResult Index()
{
string country = Request["Country"]; // here I always get null
}
I need a newbie like explanation why this is not working and how I get it to work, please :)
First, I agree with #Maess. Don't use ViewBag. It's horrible and someone at Microsoft should be slapped for ever adding it as an option in the first place.
That said, your error is pretty obvious here. You named your select "Countries" and you're trying to pull "Country" out of the request.
Since you're new, I'll be nice and lay out how to use a view model for this. First, create a model:
public class IndexViewModel
{
public int SelectedCountry { get; set; }
public IEnumerable<SelectListItem> CountryChoices { get; set; }
}
Then in your action:
// GET
public ActionResult Index()
{
var model = new IndexViewModel();
// get your country list somehow
// where `Id` and `Name` are properties on your country instance.
model.CountryChoices = countries.Select(m => new SelectListItem { Value = m.Id, Text = m.Name });
return View(model);
}
And in your view:
#model Namespace.IndexViewModel
...
#Html.DropDownListFor(m => m.SelectedCountry, Model.CountryChoices, new { #class = "form-control" })
And finally, in your POST action:
[HttpPost]
public ActionResult Index(IndexViewModel model)
{
// use model.SelectedCountry
}

Resources