I am trying to post model which contains List<> item looks like below:
public class AddSubscriptionPlanModel
{
public AddSubscriptionPlanModel()
{
AllFeatures = new List<Feature>();
}
public int Id { get; set; }
public int SubscriptionPlanId { get; set; }
public int SubscriptionId { get; set; }
public string Name { get; set; }
public bool IsActive { get; set; }
[Display(Name = "No Of Users")]
public int? NoOfUsers { get; set; }
[Display(Name = "Duration Type")]
public DurationType DurationType { get; set; }
public int Duration { get; set; }
public decimal Amount { get; set; }
public int SubscriptionPlanTrailId { get; set; }
public int FeatureId { get; set; }
public DateTime CreatedDateUtc { get; set; }
public DateTime LastUpdatedUtc { get; set; }
public int CreatedBy { get; set; }
public int LastUpdatedBy { get; set; }
public List<Feature> AllFeatures { get; set; }
}
I am populating all required fields on get method and feeding List from database
public ActionResult Mapping(int id)
{
var features = FeatureManager.GetAllFeatures();
var model = new Ejyle.DevAccelerate.Web.App.Models.SubscriptionPlan.AddSubscriptionPlanModel();
model.AllFeatures = features;
model.Id = id;
model.SubscriptionId = id;
model.NoOfUsers = 20;
model.SubscriptionPlanId = 1;
model.SubscriptionPlanTrailId = 1;
model.Amount = 1000;
model.CreatedBy = 1;
model.LastUpdatedBy = 1;
model.FeatureId = 1;
model.Duration = 20;
return View(model);
}
And my view code looks like below :
@using (Html.BeginForm("Mapping", "SubscriptionPlans", FormMethod.Post))
{
@Html.HiddenFor(model => model.Id)
@Html.HiddenFor(model => model.Amount)
@Html.HiddenFor(model => model.Duration)
@Html.HiddenFor(model => model.FeatureId)
@Html.HiddenFor(model => model.CreatedBy)
@Html.HiddenFor(model => model.LastUpdatedBy)
@Html.HiddenFor(model => model.NoOfUsers)
@Html.HiddenFor(model => model.SubscriptionId)
@Html.HiddenFor(model => model.SubscriptionPlanId)
@Html.HiddenFor(model => model.SubscriptionPlanTrailId)
@Html.HiddenFor(model => model.AllFeatures)
<table class="table table-striped">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.Name)
</th>
<th>
@Html.DisplayNameFor(model => model.IsActive)
</th>
</tr>
</thead>
<tbody>
@for (int i=0; i < Model.AllFeatures.Count; i++)
{
<tr>
@Html.HiddenFor(model => model.AllFeatures[i].Id)
@Html.HiddenFor(model => model.AllFeatures[i].Name)
@Html.HiddenFor(model => model.AllFeatures[i].IsActive)
<td>
@Html.TextBoxFor(model => model.AllFeatures[i].Id)
</td>
<td>
@Html.TextBoxFor(model => model.AllFeatures[i].Name)
</td>
<td>
@Html.EditorFor(model => model.AllFeatures[i].IsActive)
</td>
</tr>
}
</tbody>
</table>
<input type="submit" class="btn btn-success" value="Submit" name="Submit"/>
}
On View, the list items rendered properly and even I am able to edit those fields.
But On post, all properties have the value except AllFeatures list item property which always shows count = 0.
Edit 1: This is how my view looks like while rendering features
Edit 2:
Signature of the method posting to:
[HttpPost]
public ActionResult Mapping(AddSubscriptionPlanModel model)
{
}
AllFeatures
is a generic list ofFeature
. When you do this:The Razor view engine will render this:
In other words,
HiddenFor
calls theToString()
on the item and simply puts that into theinput
tag'svalue
attribute.What happens upon POST
When you post the form, the
DefaultModelBinder
is looking for a property namedAllFeatures
but of typestring
so it can assign this to it:It does not find one since your model does not have
AllFeatures
of typestring
, so it simply ignores it and binds all the other properties.Yes, it will and this is not the
AllFeatures
list which you posted but the one from the constructor which clearly is an empty list with a count 0:I am not sure why you are sending all the features to the client (browser) and then you need to post it back to the server.
Solution
To fix the issue, simply remove this line:
Now it will not cause any confusion during binding and MVC will bind the items in the loop to the
AllFeatures
property.In fact, the only code you really need, as far as I can tell from your question is this(I could be wrong if you need the hidden fields for some other reason. But if you just want the user to edit the AllFeatures, you do not need any of the hidden fields):
I think your issue might be the nested property. You may want to review the posted form and see what it looks like.
Your markup looks correct assuming that the model binding would work for a nested property - which I am not sure it does. (Just to make sure I searched for and found this old Haack post that indicates you're doing it right: Haack Post)
As a workaround (and POC), you can try the following: Add a new parameter to your action for your features, post it like your doing now but not nested (so you can't use the Model For HTML helpers) and then in the controller, you can set it back to your main object's features property.
This how to do it, as in the context of binding to a list from a view is incorrect.
View:
Controller: