MVC3 POST model binding not working for particular complex model - c#

For some reason, when I post this view model back to the controller and add in the model for binding, it ends up being null. The application that I am working with is a massive one. Also I haven't written much of the code so this model is massive so I will just add the parts that matter, but could other properties be preventing the model binding?
I do know that it has been working but in the last little bit it started not. Maybe it's not even something with the model, would just love some help debugging it.
POST Action:
[HttpPost]
public ActionResult Categories(int applicationId, SqsApplicationViewModel model)
{
// Save away the ids they chose
_sqsApplicationCategoryService.SaveCategories(applicationId, model.Display_Categories.Where(i => i.Selected).Select(i => i.CategoryId).ToList());
// Complete the step
_sqsApplicationStepService.CompleteStep(applicationId, SqsStep.Categories);
return RedirectToAction("Documents");
}
View Model:
public class SqsApplicationViewModel : IMappable
{
public int Id { get; set; }
public int SupplierId { get; set; }
public int? SqsApprovalLevelId { get; set; }
// Other properties .....
public List<SqsChosenCategoryViewModel> Display_Categories { get; set; }
// Other properties .....
}
public class SqsChosenCategoryViewModel
{
public int CategoryId { get; set; }
public string Name { get; set; }
public string CategoryAmountString { get; set; }
public bool Selected { get; set; }
public IList<SqsDocumentComplianceViewModel> Documents { get; set; }
}
View:
#using (Html.BeginForm())
{
#Html.HiddenFor(m => m.Id)
#if (Model.Display_Categories != null && Model.Display_Categories.Count() > 0)
{
for (var i = 0; i < Model.Display_Categories.Count; i++)
{
#Html.HiddenFor(m => m.Display_Categories[i].CategoryId)
#Html.CheckBoxFor(m => m.Display_Categories[i].Selected)
#Model.Display_Categories[i].Name
}
}
}
Also, the values being sent back in firebug are:
Id:1061
Display_Categories[0].CategoryId:4
Display_Categories[0].Selected:true
Display_Categories[0].Selected:false
Display_Categories[1].CategoryId:1
Display_Categories[1].Selected:false
Display_Categories[2].CategoryId:2
Display_Categories[2].Selected:false
Display_Categories[3].CategoryId:3
Display_Categories[3].Selected:false
Display_Categories[4].CategoryId:6
Display_Categories[4].Selected:true
Display_Categories[4].Selected:false
Display_Categories[5].CategoryId:8
Display_Categories[5].Selected:false
Display_Categories[6].CategoryId:10
Display_Categories[6].Selected:false
Display_Categories[7].CategoryId:7
Display_Categories[7].Selected:false
Display_Categories[8].CategoryId:9
Display_Categories[8].Selected:false
Display_Categories[9].CategoryId:11
Display_Categories[9].Selected:false
Display_Categories[10].CategoryId:5
Display_Categories[10].Selected:true
Display_Categories[10].Selected:false
-------------EDIT----------------
I tried using the following test models and it worked. Is it possible that another property in the Model could be hindering the binding? I added some random ones in these too and it still worked.
public class TestViewModel
{
public int Id { get; set; }
public IList<TestSubViewModel> Display_Categories { get; set; }
public string TestProp { get { return "asdfasdfasdf"; } }
public TestSubViewModel TestGetFirst { get { return this.Display_Categories.FirstOrDefault(); } }
}
public class TestSubViewModel
{
public int CategoryId { get; set; }
public string Name { get; set; }
public string CategoryAmountString { get; set; }
public bool Selected { get; set; }
public IList<SqsDocumentComplianceViewModel> Documents { get; set; }
}

So I'm just going to answer my own question, though it isn't solved as much as there is another way to do it.
I believe that when you typehint the model and it binds it, in the background it uses "TryUpdateModel()" and so I just called this in the controller and for some reason it worked. Not sure if I miss out on anything else by doing it this way, but it has worked for me.
Also you can debug what might be the issue by doing it this way by the following:
var model = new ViewModel();
var isSuccess = TryUpdateModel(model);
if (!isSuccess)
{
foreach (var modelState in ModelState.Values)
{
foreach (var error in modelState.Errors)
{
Debug.WriteLine(error.ErrorMessage);
}
}
}
Taken from this post: How to find the exceptions / errors when TryUpdateModel fails to update model in asp.net mvc 3

Related

Get selected CheckBox values in controller

I have a dormitory adding page and this dormitories can have features so I want to use CheckBox list for this.
There is a list of all features a dormitory can have.
public class DormFeatureModel
{
[Key]
public int FeatureID { get; set; }
public string FeatureName { get; set; }
public List<DormHasFeatureModel> DormHasFeature { get; set; }
}
Here, too, are features that a dormitory has.
public class DormHasFeatureModel
{
[Key]
public int HasFeatureID { get; set; }
[Required]
public int FeatureID { get; set; }
[Required]
public int DormID { get; set; }
public virtual DormModel Dorm { get; set; }
public virtual DormFeatureModel DormFeature { get; set; }
}
I can get features list in razor as checkbox
but I can't get selected checkboxes id list(so, FeatureID)
How can I get list in controller ?
First, add a ViewModel that correlates the Checked boolean with the FeatureId.
public class SelectedFeatureViewModel {
public bool Checked { get; set; } // to be set by user
public int FeatureID { get; set; } // to be populated by GET action
public string FeatureName { get; set; } // to be populated by GET action
}
The GET action creates the main ViewModel and initializes the list of available features (DormOptions).
public class CreateDormViewModel {
// used to render the checkboxes, to be initialized in GET controller action
// also used to bind the checked values back to the controller for POST action
public ICollection<SelectedFeatureViewModel> DormOptions { get; set; }
}
In the Razor markup, bind the checkboxes to the DormOptions collection:
#model CreateDormViewModel
#using (Html.BeginForm("CreateDorm", "DormAdministration", FormMethod.Post)) {
// use for loop so modelbinding to collection works
#for (var i = 0; i < Model.DormOptions.Count; i++) {
<label>#Model.DormOptions[i].FeatureName</label>
#Html.CheckBoxFor(m => m.DormOptions[i].Checked)
// also post back FeatureId so you can access it in the controller
#Html.HiddenFor(m => m.DormOptions[i].FeatureID)
// post back any additional properties that you need to access in the controller
// or need in order to redraw the view in an error case
#Html.HiddenFor(m => m.DormOptions[i].FeatureName)
}
}
In the CreateDorm POST action, the checkbox values are bound to the ViewModel property you gave in the CheckBoxFor lambda, i.e. the Checked property in the DormOptions collection.
[HttpPost]
public ActionResult CreateDorm(CreateDormViewModel postData) {
var selectedFeatureIds = new List<int>();
foreach (var option in postData.DormOptions) {
if (option.Checked) {
selectedFeatureIds.Add(option.FeatureID);
}
}
// ...
}
You can get the list by using the name of the checkboxes, let say your name of the checkboxes is chklstfeatureid then in controller you can get list like below
public actionresult createdorm(list<int> chklstfeatureid)
{
}
Thanks

Trouble with MVC 4 DropDownListFor

I am having trouble with displaying Dropdownlist in MVC 4 application.
It looks straightforward, However something is still missing which is not getting hold of.
ViewModel
public class vendorModel
{
public int vendorID { get; set; }
public int vendorTypeID { get; set; }
public string vendorName { get; set; }
public IEnumerable<vendorTypeModel> vendorTypes { get; set; }
}
public class vendorTypeModel
{
public int vendorTypeID { get; set; }
public string vendorTypeName { get; set; }
}
Controller
[HttpGet]
public ActionResult Vendor() {
vendorModel oVendor = new vendorModel();
oVendor.vendorTypes = blVendor.getAllVendorTypes();
return View(oVendor);
}
getAllVendorTypes Method
This is where I am fetching records from database and building and returning a List<vendorTypeModel> object.
and Finally on View am using following code.
#Html.DropDownListFor(m => m.vendorTypeID, new SelectList(Model.vendorTypes,"vendorTypeID","vendorTypeName"))
What am I doing wrong? I am getting following results on screen.
Instead of names of vendors.
Please suggest whats wrong with my code.
I suspect that oVendor.vendorTypes = blVendor.getAllVendorTypes(); may return a wrong list back. Set a breakpoint and check the oVendor.vendorTypes please.
According to https://stackoverflow.com/a/15195354/6741868, you could try alternative syntax such as:
#Html.DropDownListFor(m => m.vendorTypeID, Model.vendorTypes.Select(vendor => new SelectListItem()
{
Text = vendor.vendorTypeName,
Value = vendor.vendorTypeID
})

Error An object with the same key already exists in the ObjectStateManager. with ViewModel

i know there are quite a few questions to this error, but i couldnt solve my problem with them.
So i get the error:
InvalidOperationException
An object with the same key already exists in the ObjectStateManager.
The ObjectStateManager cannot track multiple objects with the same key.
I dont even know which key is the same? Can i look that up somehow?
My Controller:
[HttpPost]
public ActionResult Meeting(ViewModel ViewModel)
{
var ALL = db.Sites.Where(p => p.Content.Any(a => a.Date.CompareTo(DateTime.Now) <= 0)).OrderBy(l => l.Customer.Service).ToList();
//Adding informations that arnt added by user
ViewModel.Changing.LastUpdate = DateTime.Now;
ViewModel.Changing.LastUpdaterId = UpdaterID;
Site current = ViewModel.Changing;
if (ModelState.IsValid)
{
db.Entry(current).State = EntityState.Modified; //Here is the error
db.SaveChanges();
}
//...
}
My ViewModel
public class ViewModel
{
public managementtool.Models.Site Changing { get; set; }
public int[] AvailableSelected { get; set; }
public int[] RequestedSelected { get; set; }
public string SavedRequested { get; set; }
public List<managementtool.Models.Issue> OpenIssue { get; set; }
public List<managementtool.Models.Issue> ClosedIssue { get; set; }
public managementtool.Models.Site Site { get; set; }
public int ID { get; set; }
}
I would be grateful for youre help.
Unfortunately i used the Site Model in that Action before like this:
[HttpPost]
public ActionResult Meeting(ViewModel ViewModel)
{
//The Error appears if the following part isnt commented out -->
//var ALL = db.Sites.Where(p => p.Content.Any(a => a.Date.CompareTo(DateTime.Now) <= 0)).OrderBy(l => l.Customer.Service).ToList();
//Adding informations that arnt added by user
ViewModel.Changing.LastUpdate = DateTime.Now;
ViewModel.Changing.LastUpdaterId = UpdaterID;
Site current = ViewModel.Changing;
if (ModelState.IsValid)
{
db.Entry(current).State = EntityState.Modified; //Here is the error
db.SaveChanges();
}
//...
}
So there was the second key, so the ObjectStateManager couldnt track multiple objects with the same key.

Access many to one Entity Framework model properties in RenderAction view using lambda expressions

I tried to access the property of model (FilePath) in my Render action, but without success.
I have these models:
public class Offer
{
public int OfferID { get; set; }
public string Reference { get; set; }
public string Title { get; set; }
public virtual ICollection<FilePath> FilePaths { get; set; }
}
public class FilePath
{
public int FilePathId { get; set; }
public string FileName { get; set; }
public virtual Offer Offer { get; set; }
}
In controller offer:
public PartialViewResult Category()
{
var offers = db.Offers
.Include(i => i.FilePaths)
// i tried with .Include(“FilePaths”) with no sucess
.ToList();
return PartialView("_OfferBoxList", offers);
}
Showing with:
#{Html.RenderAction("Category", "Offer");}
The problem is in partial action view:
#foreach (var item in Model)
{
#item.Title // This work
#item.FilePath.FileName // This NOT work
}
Output error:
'System.Data.Entity.DynamicProxies.Offer_278BF6A1F89FB9514329AC922E992AEBD19368D66A4460B6BEBA1BB2256CAFC3' does not contain a definition for 'FilePath'
Thanks for help.
Each Offer has a list of FilePath instances (ICollection<FilePath> FilePaths), so you can't just access the Offer.FilePath.FileName property, you have to get for instance the first one (depending on what you need), using something like:
#foreach (var item in Model)
{
#item.Title // This work
#item.FilePaths.First().FileName // Take the first FilePath object from the collection
}
You should really not be loading your entities into views..
I generally create Data Transfer Objects (DTOs) for a multitude of purposes. Not only does it help me shape the query expression (IQueryable<T>), but I also use so that I have concert types (instead of generated dynamic proxy class types - like the type you're seeing in your runtime exception) in control properties, like dataItem and dataSource, which are often used in runtime bindable dynamic/reflective controls (grids, listviews)..
Note
It is possible to disable the generated dynamic proxy types by declaring and passing your own DbContextConfiguration instance to EntityFramework; However, doing so will affect EntityFramework supporting features.
public class OfferDTO
{
public int OfferID { get; set; }
public string Reference { get; set; }
public string Title { get; set; }
public IEnumerable<string> FileNames { get; set; }
}
you controller function would then look like:
public PartialViewResult Category()
{
var offers = db.Offers.Select<Offer, OfferDTO>( (entity) => new OfferDTO() {
OfferID = entity.OfferID,
Reference = entity.Reference,
Title = entity.Title,
FileNames = entity.FilePaths.Select<FilePath, string>( filePath => filePath.FileName).AsEnumerable()
});
return PartialView("_OfferBoxList", offers.ToList());
}
then,
#{Html.RenderAction("Category", "OfferDTO");}
#foreach (var item in Model) // Model is IEnumerable<OfferDTO>
{
#item.Title
#item.FileNames.First()
}
Additionally,
you can create an IQueryable<IQueryable<TEntityDTO>> to suit your purposes and perform a SelectMany on it to flatten the results.
public class OfferTitleFilenameDTO
{
public string Title {get;set;}
public string Filename {get;set;}
}
public PartialViewResult Category()
{
var offers = db.Offers.Select<Offer, IQueryable<OfferTitleFilenameDTO>>( (entity) => entity.FilePaths.Select<FilePath, OfferTitleFilenameDTO>(filePath => new OfferTitleFilenameDTO() {
Filename = filePath.FileName,
Title = entity.Title
})
});
return PartialView("_OfferBoxList", offers.SelectMany(dtos => dtos));
}
then,
#{Html.RenderAction("Category", "OfferTitleFilenameDTO");}
#foreach (var item in Model) // Model is IEnumerable<OfferTitleFilenameDTO>
{
#item.Title
#item.Filename
}

MVC Add and remove items from subcollection

I'm having trouble binding data to a collection item's collection (I'm also having trouble wording my problem correctly). Let's just make thing easier on everyone by using an example with psudo models.
Lets say I have the following example models:
public class Month()
{
public int ID { get; set; }
public string Name { get; set; }
public virtual ICollection<Week> Weeks { get; set; }
}
public class Week()
{
public int ID { get; set; }
public int MonthID { get; set; }
public String Name { get; set; }
public virtual ICollection<Day> Days { get; set; }
}
public class Day()
{
public int ID { get; set; }
public String Name { get; set; }
}
...and an example viewmodel:
public class EditMonthViewModel()
{
public Month Month { get; set; }
public List<Week> Weeks { get; set; }
public List<Day> AllDays { get; set; }
}
The purpose of the Edit Action/View is to enable users to edit a month, the weeks assigned to the month, and add and remove days from weeks of a certain month. A view might help.
#model myProject.ViewModels.EditMonthViewModel
//...
#using (Html.BeginForm())
{
//Edit Month Stuff...
#for(int i = 0; i < Model.Weeks.Count(); i++)
{
<h2>#Model.Weeks[i].Name</h2>
#Html.EditorFor(model => Model.Weeks[i].Name)
//loop through all possible days
//Select only days that are assigned to Week[i]
#for(int d = 0; d < Model.AllDays.Count(); d ++)
{
//This is the focus of this question.
//How do you bind the data here?
<input type="checkbox"
name="I have no idea"
#Html.Raw(Model.Weeks[i].Days.Contains(Model.AllDays[d]) ? "checked" : "") />
}
}
}
Controller Action methods
public ActionResult Edit(int id)
{
var viewModel = new EditMonthViewModel();
viewModel.Month = db.Months.Find(id);
viewModel.Weeks = db.Weeks.Where(w => w.MonthID == id).ToList();
viewModel.AllDays = db.Days.ToList();
}
[HttpPost]
public ActionResult Edit(EditMonthViewModel viewModel)
{
var monthToUpdate = db.Months.Find(viewModel.Month.ID);
//...
if(viewModel.Weeks != null)
{
foreach (var week in viewModel.Weeks)
{
var weekToUpdate = monthToUpdate.Weeks.Single(w => w.ID == week.ID);
//...
/*So, I have a collection of weeks that I can grab,
but how do I know what was selected? My viewModel only has a
list of AllDays, not the days selected for Week[i]
*/
}
}
How can I ensure that when I submit the form the selected days will bind to the week?
It looks like the easiest thing to do is to make it a goal for your form to populate a data structure of the type IEnumerable<DayModel>, where DayModel is defined as:
public class DayModel
{
public int WeekId { get; set; }
public int DayId { get; set; }
public bool IsIncluded { get; set; }
}
You could keep your Razor code as is for the most part, but then when it comes to rendering the checkboxes, you can do something like this:
#{
var modelIdx = 0;
}
// ...
<input type="hidden" name="days[#modelIdx].WeekId" value="#Model.Weeks[i].Id" />
<input type="hidden" name="days[#modelIdx].DayId" value="#Model.AllDays[d].Id" />
<input type="checkbox" name="days[#modelIdx].IsIncluded" value="#(Model.Weeks[i].Days.Contains(Model.AllDays[d]) ? "checked" : "")" />
#{ modelIdx++; }
Then, your controller action you post to could have this signature:
[HttpPost]
public ActionResult Edit(IEnumerable<DayModel> days)
{
//...
}
Something that helps me is to never confuse view models, which should only be used for the model for views (GET actions generally) and non-view models (what we call plain models). Avoid having your POST actions try to bind to view models, and it will simplify your life greatly.

Categories

Resources