In the EditorTemplates, I have a Template which accepts and Model of Type List<string>. This should create an Textbox for each string in the list.
In the Model, the Property has the [UIHint("EditList")]. Now when I render it to the Page, the Template is called correctly, but the index is set wrong. When I submit the form I get:
MyList.[0]:test123
Instead of
MyList[0]:test123
I'm using MVC 3!, the same code workd in my test project which uses MVC 5
View:
<div class="col-md-10">
#Html.EditorFor(model => model.MyList)
#Html.ValidationMessageFor(model => model.MyList)
</div>
Model:
public class FormTest
{
[UIHint("EditListWithAddButton")]
public List<string> MyList { get; set; }
}
EditorForTemplate:
#model List<string>
<div class="EditListWithAddButton">
<ul>
#for (int i = 0; i < Model.Count(); i++)
{
<li>#Html.EditorFor(model => Model[i])</li>
}
</ul>
</div>
Brute force solution but, don't have the time to debug it any futher...
#Html.Raw(#Html.EditorFor(model => Model[i])
.ToString().Replace("__", "_").Replace(".[", "["))
this replaces the Editfortempalt in away, that the index is correct again...
If anybody has a better solution / can explain the problem please tell me
Related
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 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.
I have created a display template in ~/Views/Shared/DisplayTemplates named ImpactMatrix.cshtml. It accepts a nullable int and renders a two-dimensional matrix with the selected number highlighted:
#model int?
#{
var matrix = ImpactMatrix.GetMatrix();
}
<div class="impactmatrix">
<table>
#for (int i = 0; i < matrix.GetLength(0); i++)
{
<tr>
#for (int j = 0; j < matrix.GetLength(1); j++)
{
var cell = matrix[i, j];
<td data-color="#cell.Color"
class="matrix #(Model == cell.Value ? cell.Color.ToString() : "")">
#cell.Value
</td>
}
</tr>
}
</table>
</div>
It's easily reusable and works great. I can invoke it within my view like so:
#Html.DisplayFor(m=> m.ImpactFactor, "ImpactMatrix")
Now I've decided to extend that and make it an editor as well. The idea is to add a hidden input for the selected number and wrap the input along with the matrix template with a div. From there it should be a simple matter to use Javascript to interact with my display grid and populate the hidden input.
I've created an editor template, also named ImpactMatrix.cshtml, within my ~/Views/Shared/EditorTemplates folder. Here's the code:
#model int?
<div class="impactmatrix-editor">
#Html.HiddenFor(m => m)
#Html.DisplayFor(m => m, "ImpactMatrix")
</div>
My problem is that the hidden input renders correctly, but the nested display template does not render inside my editor template. Is what I am trying to do possible?
It seems that it is not currently supported.
However, I have found a solution using the Html.Partial in this article: Nested #Html.DisplayFor(model => baseClass, "BaseClass") for base class template not rendering
Rewrite you editor template like this:
#model int?
<div class="impactmatrix-editor">
#Html.HiddenFor(m => m)
#Html.Partial("~/Views/Shared/DisplayTemplates/ImpactMatrix.cshtml", Model)
</div>
Note: #Paul Hadfield commented on this issue in the article I have mentioned above, that this issue has been fixed for ASP MVC 4. But even though I run this version on my PC, I was not able to make nested templates working.