I am new to MVC and have some difficulties understanding this.
To make it simple, I have a "Person" object and this object has an IEnumerable property called "EmailaddressList".
I have generated an edit page through Visual Studio 2012. The main objects properties, are generated on the edit page with textboxes like Name and LastName.
However the list of e-mail addresses in the IEnumerable list of sub-objects are not generated automatically in my view.
This is OK, I have written that code by hand using a tab for each type of e-mailaddress.
So far so good.
Problem:
When I recieve the model (person object) in my HTTP-Post method, the EmailAddressList is null.
Why is it like this, It was not null when I sent it to the view.
I the tab where the e-mailadresses are listed is in a partial view.
Can anyone give me some tips, is it something I'm missing here?*
View-Code
<div id="tabs">
<ul>
#foreach (var item in Model.EmailAddressList)
{
<li>#Html.Label(item.AddressType)</li>
}
</ul>
#foreach (var item in Model.EmailAddressList)
{
<div id="#item.AddressType">
<p>
#Html.TextBoxFor(s => item.EmailAddress, new { #class = "input-xxlarge" })
</p>
</div>
}
</div>
Controller (recieving method)
Here person.EmailAddressList is null
[HttpPost]
public ActionResult Create(Person person)
{
if (ModelState.IsValid)
{
personRepository.InsertOrUpdate(person);
personRepository.Save();
return RedirectToAction("Index");
}
else
{
return View();
}
}
That's because in order to correctly index your fields (so model binder can do it's work), you have to use a for loop.
First, change your IEnumerable to be a List (so we can use an indexor in the view).
Then change your foreach to be the following for loop:
#for (int i = 0; i < Model.EmailAddressList.Count; i++)
{
<div id="#Model.EmailAddressList[i].AddressType">
<p>
#Html.TextBoxFor(m => m.EmailAddressList[i].EmailAddress, new { #class = "input-xxlarge" })
</p>
</div>
}
Based on your update, the reason this doesn't work is because the default model binder only relies on order for a collection of simple data. When it comes to complex type you need to provide the relevant index per item otherwise it doesn't know which item property your referring to e.g.
#for (int i = 0; i < Model.EmailAddressList.Count; i++) {
Html.TextBoxFor(m => m.EmailAddressList[i].EmailAddress) %>
}
See Phil Haack's article on model binding to a list.
It's due to your elements not being ID'd the correct thing for MVC to pick them up on the post back, what you need is:
#Html.EditorFor(model => model.EmailAddressList);
Then, please refer to my post located here on how to make this look to how you want it to.
Related
I'm trying to access the properties of a class dynamically in ASP.NET Razor when generating an HTML Table. This problem is normally easily solved with reflection, but the #Html.DisplayFor method is giving me issues.
I am attempting to generate an HTML table that has 3 cells per row, with the title of the item in bold as the first line of the cell, and the value of the item in the second line of the cell. The contents of the table should not include cells which are on the 'Excluded Fields' list, and I do not want to have to statically reference each column.
<table class="blpSecurityTable">
<tr>
#{
int _rowCount = 0;
foreach (var property in item.GetType().GetProperties())
{
#if (!Model.ExcludedFields.Contains(#property.Name))
{
dynamic test = #property.GetValue(item);
<td><b>#Html.DisplayFor(m => #property.Name)</b><br />#Html.DisplayFor(m => #test)</td>
_rowCount++;
}
#if (_rowCount % numCols == 0)
{
#:</tr><tr>
}
}
}
</tr>
</table>
I've tried calling #Html.DisplayFor(m => #property.GetValue(item)) but that just creates a runtime error. I can simply call #property.GetValue(item) and the value displays, but this is not ideal because I use display templates to do things like set dates to the ShortDateString format.
I understand that DisplayFor is using reflection to determine the type of the property, and that is why I am trying to use the dynamic variable to facilitate reflection for the method. However, when I run the method, it throws errors indicating the variable is not a generic parameter, and therefore cannot share its attributes. The resulting page has mostly blank values, and some cells filled in with unexpected descriptive information.
I feel like I'm getting close, but I don't know how to proceed. The page won't look right if I don't pass the values into an HTML display method, and I cannot think of any other way to get the type of table I want to be generated. Thoughts?
The issue is solved by creating a Display Template for the Security object, which then allowed me to properly use the #Html.Display Method, because the Model for the Display Template has an entry for the property.
Here is what the page code looks like now:
Display Template
#model Interface.Models.Security
#{int numCols = 3;}
<table class="blpSecurityTable">
<tr>
#{
int _rowCount = 0;
foreach (var property in Model.GetType().GetProperties())
{
#if (!BLPDLModel.ExcludedFields.Contains(#property.Name))
{
<td><b>#Html.DisplayFor(m => #property.Name)</b><br />#Html.Display(property.Name)</td>
_rowCount++;
}
#if (_rowCount % numCols == 0)
{
#:</tr><tr>
}
}
}
</tr>
</table>
Razor Page
#foreach (var item in Model.Security)
{
<div class="blpSecurityItem">
<button type="button" class="collapsible">{button text}</button>
#{
<div class="collapsible-content">
<hr />
#Html.DisplayFor(m => item)
</div>
}
</div>
Replace #Html.DisplayFor(m => #test) with #Html.Display(property.Name)
and your model property for date should have [DisplayFormat(DataFormatString = "{0:yyyy/MM/dd}")] or anything you like
Hello Mighty Stackoverflowers,
I'm currently working on an ASP.NET MVC 4.5 application. I need to map the input values from my partial view to my main View Model, when I submit the create form.
In my View "Create.cshtml" I call a partial view "_SwotPart.cshtml". I pass a part of my ViewModel to the Partial View, like this:
Create.cshtml:
#model MyCoolApp.BLL.Models.MainVm
#foreach (var swot in Model.Swots)
{
<tr>
#foreach (var swotPart in swot.SwotParts)
{
#Html.Partial("~/Views/Shared/_SwotPart.cshtml", swotPart)
}
</tr>
}
My partial View looks as follows, _SwotPartial.cshtml :
<td class="form-group">
#Html.TextAreaFor(model => model.Label, htmlAttributes: new { Name = nameField, ID = nameField, #class = "form-control", placeholder = Model.SwotTypeId.GetLabel() })
</td>
Anyways, when I submit my form, the values from the partial view never arrive in the controller.
Do you have any ideas how to map this properly?
Thanks!
The problem is in the input names that will be generated the way you're currently trying to achieve this. Razor needs the context of the entire list, or at least the item's position in it, in order to generate correct input names. In other words, the easiest way to solve your issue (with a caveat) is:
#for (var i = 0; i < Model.Swots.Count(); i++)
{
...
#for (var j = 0; j < Model.Swots[i].SwotParts.Count(); j++)
{
if (Model.Swots[i].SwotParts[j].SwotTypeId == SwotType.InternalHelpful || Model.Swots[i].SwotParts[j].SwotTypeId == SwotType.InternalHarmful)
{
#Html.Partial("~/Views/Shared/_SwotPart.cshtml", Model.Swots[i].SwotParts[j])
}
}
...
Then, the partial has the correct context to work with and your inputs will be named like Swots[0].SwotParts[0].Label, which the modelbinder will be able to work with.
However, the caveat here is that you're splitting this list into two loops. That's still not going to work, as you're effectively messing with the overall context of the item(s) position within the model. To fix that, you should split your list in your model, which is better anyways, as you can remove this business logic from your view:
public class SwotVm
{
...
public List<SwotPartVm> InternalSwotParts { get; set; }
public List<SwotPartVm> ExternalSwotParts { get; set; }
}
Then, you can simply iterate over each list individually, and the values will naturally post back to the appropriate list.
Given that you're using a partial to render fields for a particular class type, though, you'd be better served by creating an editor template. If you simply move your partial code to the view: Views\Shared\EditorTemplates\SwotPartVm.cshtml, then in your main view, you can just do:
#for (var i = 0; i < Model.Swots.Count(); i++)
{
...
<tr>
<th class="swot-heading">Internal</th>
#Html.EditorFor(m => m.Swots[i].InternalSwotParts)
</tr>
<tr>
<th class="swot-heading">External</th>
#Html.EditorFor(m => m.Swots[i].ExternalSwotParts)
</tr>
}
That's obvious much cleaner, and you can take this concept even further by adding a SwotVm.cshtml editor template, allowing you replace even this little bit of code with just:
#Html.EditorFor(m => m.Swots)
Note: In your SwotVm.cshtml editor template, you would only include the code for a single SwotVm. In other words, not including the for statement.
In order for your application to parse the posted values and properly and bind it to your view model. Names of posted form data needs to be like.
swots[x1].swotParts[x2].label
Where x1 is a number ranging from 0 and up for each swot.
Where x2 is a number ranging from 0 and up for each swot part in swots.
Now when you are posting, the form data names is just label.
Instead of :
#Html.TextAreaFor(model => model.Label, htmlAttributes: new { Name = nameField, ID = nameField, #class = "form-control", placeholder = Model.SwotTypeId.GetLabel() })
try :
<textarea name="swots[x1].swotParts[x2].label" class="form-control" placeholder="#Model.SwotTypeId.GetLabel()" >#Model.Label</textarea>
Don't forget to replace the x1 and x2 with a number.
You can read more about model bindings to collections here.
http://www.hanselman.com/blog/ASPNETWireFormatForModelBindingToArraysListsCollectionsDictionaries.aspx
I have a list of class object and which is bind with view like this
#model List<Rep.Models.ContactReportSettingViewModel>
var accountArr = Model.Select(x => new { x.AccountId, x.CarrierId, x.AccountNumber, x.CarrierName, x.ClientId, x.ContactId }).Distinct();
I have a loop here on var object
#foreach (var accountRow in accountArr)
{
#Html.LabelFor(x => accountRow.AccountNumber, accountRow.AccountNumber, new { #id = accountRow.AccountId })
but when I click on save it is returning null or values or not set with the class properties I am accessing this in controller like this:
public RESULT method(List<ContactReportSettingViewModel> model)
{
model is null here
// return View(model);
}
But in model I am getting null. What I am doing wrong?
When I use this
public RESULT method(ContactReportSettingViewModel model)
{
// return View(model);
}
Then in model object I can see all the properties but values does not set to those properties
You cannot use a foreach loop to generate form controls for a collection because your generating duplicate name attributes that have no relationship to your model (and duplicate id attributes which is invalid html). You can use either a for loop in the view, or use an EditorTemplate for your model.
Note you need to remove your Linq .Select() code and do the filtering in the controllers GET method.
Using a for loop in the main view (note the model must be IList<T>)
#model List<Rep.Models.ContactReportSettingViewModel>
#using (Html.BeginForm())
{
#for (int i = 0; i < Model.Count; i++)
{
#Html.LabelFor(m => m[i].AccountNumber)
#Html.TextBoxFor(m => m[i].AccountNumber)
#Html.ValidationMessageFor(m => m[i].AccountNumber)
.....
}
<input type="submit" .../>
}
Using an EditorTemplate. Create a partial view in /Views/Shared/EditorTemplates/ContactReportSettingViewModel.cshtml (note the name of the file must match the model class name)
#model Rep.Models.ContactReportSettingViewModel
#Html.LabelFor(m => m.AccountNumber)
#Html.TextBoxFor(m => m.AccountNumber)
#Html.ValidationMessageFor(m => m.AccountNumber)
.....
and then in the main view (note the model can be IEnumerable<T>)
#model IEnumerable<Rep.Models.ContactReportSettingViewModel>
#using (Html.BeginForm())
{
#Html.EditorFor(m => m)
<input type="submit" .../>
}
In both cases the generated html will include the correct name attributes with indexers which will be bound to your model in the POST method
<input type="text" name="[0].AccountNumber" .. />
<input type="text" name="[1].AccountNumber" .. />
Follow the following checklist
1) Make sure you added thing binding statement on top of your view
#model List<ClassName>
2) Then check your is being submitted to the function your mentioned in question and also check the parameter type is same as you mentioned while binding the page.
if you are using html table type structure to display list items then you also need to bind your list with each row. like
for Cell[0][0] bind yourList[0].EmployeeId, Cell[0][1] bind yourList[0].EmployeeName and so on for all the column and then rows by using loop.
Here is my code :
ViewModel
public class FooViewModel{
public Guid BarId { set;get }
}
View :
#model IEnumerable<FooViewModel>
#foreach (var c in Model)
{
<div>
#Html.DropDownListFor(o => c.BarId , (List<SelectListItem>)ViewBag.BarCollection)
</div>
}
the problem is DropDownListFor create the options completely but binding doesn't work.
You cannot use a foreach loop to generate controls for items in a collection. If you inspect the html you will see that you have duplicate name attributes without indexers (and also duplicate id attributes which is invalid html). You need a for loop of a custom EditorTemplate for FooViewModel. Using a for loop (your model must implement IList<T>)
#model IList<FooViewModel>
for (int i = 0; i < Model.Count; i++)
{
#Html.DropDownListFor(m => m[i].BarId, ....)
}
Note the html will now be
<select name="[0].BarId" ..>
<select name="[1].BarId" ..>
etc.
I have a table where each row contains fields for a form.
I iterate in the view using this:
#foreach (var item in Model) { }
Within the foreach, I create a new table row with various fields contained inside. e.g.:
<tr><td> #Html.DropDownList("granny", "") </td></tr>
"granny" is being passed via the controller, and looks a bit like this:
ViewData["granny"] = new SelectList(db.getGrannies(), "grannyid", "grannyname");
Everything's working pretty well. The view is getting its grannies, and everything looks good. I noticed however that the name property of the field { e.g....
<select id="granny" name="granny">
} is the exact same for EVERY row created. This is a problem. I want to toss this data back to the controller in the form of a FormCollection and do fun stuff with all these grannies. I can't do that if they're not all getting passed.
That is, I'd like the selects to read, instead, like this:
<select id="granny1" name="granny1">
<select id="granny2" name="granny2">
I researched the problem a bit, and tried using
new { #Name="xyz" + n }
But Visual Studio didn't like that much.
The short and sweet of it all is this:
How do I give ViewData-generated Html.DropDownLists their own unique ids/names?
How do I give ViewData-generated Html.DropDownLists their own unique ids/names?
You cannot change the name of the generated input field and this is by design. Only the id could be changed. This being said you shouldn't need to do that. You could do the following:
#for (var i = 0; i < Model.Count; i++)
{
<tr>
<td>
#Html.DropDownList(
"selectedGranny",
(IEnumerable<SelectListItem>)ViewData["granny"]
)
</td>
</tr>
}
It seems like you are either trying to databind a collection, or you just need to manually name your selects (as they are really different controls on the web form).
You can use the following overload to pass html parameters to the drop down :
#Html.DropDownList(
"CategoryID",
(SelectList)ViewBag.CategoryId,
"--Select One--",
new{ //anonymous type
name = "granny1",
#class = "myCssClass",
onchange = "someFunction();"
})