I am fairly new to MVC5 and C# and I am trying to achieve something that I don't fully understand.
I have a Team Model such as this:
public class Team
{
[Key]
public Guid ID { get; set; }
public string TeamName { get; set; }
public string Coach { get; set; }
public string Conference { get; set; }
}
I also have a Player Model such as this:
public class Player
{
[Key]
public Guid Id { get; set; }
[ForeignKey("Teams")]
public Guid TeamId { get; set; }
public string Name { get; set; }
public virtual Team Teams { get; set; }
}
View Model is
public class TeamViewModel
{
public string TeamName { get; set; }
public string Coach { get; set; }
public string Conference { get; set; }
public List<Player> Players { get; set; }
}
With this structure, you are suppose to be able to add and infinite number of players to each team. As such I have a Teams table with few properties and a Player table that contains the player name as well as the player TeamId so that we know to what team they belong.
My problem comes when I am creating a team. I have Create Controller such as this:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Create(TeamViewModel model)
{
if (ModelState.IsValid)
{
var team = new Team { TeamName = model.TeamName, Coach = model.Coach, Conference = model.Conference, Player = model.Player };
db.Teams.Add(team);
var result = await db.SaveChangesAsync();
return RedirectToAction("Index");
}
return View();
}
And my View is as follows:
#model SoccerTeams.Models.ViewModels.TeamViewModel
#{
ViewBag.Title = "Create";
}
<h2>Create</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Team</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.TeamName, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.TeamName, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.TeamName, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Coach, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Coach, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Coach, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Conference, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Conference, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Conference, "", new { #class = "text-danger" })
</div>
</div>
#if (#Model != null)
{
foreach (var p in Model.Player)
{
<div class="form-group">
#Html.Raw("<label class=\"control-label col-md-2\">" + p.ToString() + "</label>")
<div class="col-md-10">
#Html.Raw("<input class=\"form-control text-box single-line\" name=\"Player\" type-\"text\"")
</div>
</div>
}
}
else
{
<div class="form-group">
#Html.Raw("<label class=\"control-label col-md-2\">Player</label>")
<div class="col-md-10">
#Html.Raw("<input class=\"form-control text-box single-line\" name=\"Player\" type-\"text\"")
</div>
</div>
}
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
</div>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
From my understanding, the View is suppose to be able to convert the input element to a list and pass it on to my ViewModel. However, my ViewModel is always coming up as null.
What am I missing and how would I make this work?
P.S. I understand that I can user Html.EditorFor, but I was not able to get it working, so I just printed it out as Html as I need to solve my other problem first.
Edit
I have altered my View to have the following code
<div class="form-group">
#Html.Raw("<label class=\"control-label col-md-2\">Player</label>")
<div class="col-md-10">
#Html.Raw("<input class=\"form-control text-box single-line\" name=\"model.Players[0].Name\" type-\"text\"")
</div>
</div>
As a result, the model now properly populates the Players Array, however all other values have now become null. If I remove the input element, the values are populated but players array is null again as there are no form fields for it. Do you know what could be the culprit?
In the TeamViewModel I have also renamed Player to Players.
In order for MVC to bind your form data to the Action method's parameters
their names should match.
Supposing your ViewModel has property for List<Player> Players your code should be:
In your case:
foreach (var p in Model.Player)
{
<div class="form-group">
#Html.Raw("<label class=\"control-label col-md-2\">" + p.ToString() + "</label>")
<div class="col-md-10">
#Html.Raw("<input class=\"form-control text-box single-line\" name=\"Player\" type-\"text\"")
</div>
</div>
}
Should be:
for (int i = 0; i < Model.Player.Length; i++)
{
<div class="form-group">
#Html.Raw("<label class=\"control-label col-md-2\">" + p.ToString() + "</label>")
<div class="col-md-10">
#Html.Raw("<input class=\"form-control text-box single-line\" name=\"model.Player[" + i + "].Name\" type-\"text\"")
</div>
</div>
}
Because this is the name of the parameter that you have provided:
Create(TeamViewModel model)
Also be careful because the indexes should not be broken, which means that they should be 0, 1, 2.. etc. without skipping a number.
The way that we read in the properties is by looking for
parameterName[index].PropertyName. The index must be zero-based and
unbroken.
NOTE You can read more about binding collections in Scott Hanselman's post - here
And last I suggest if you have a property that is list of something - in your case list of Player to use the plural form for the property name - Players.
EDIT
Try removing the "model." in front in the name. Make it like this "Players[0].Name". Since you only have one parameter in your Create Action method it should be fine.
I suggest you to use the helper #Html.EditorFor, so to do this you will create a partial view that will be used as template to inputs of the nested property. see the example:
Shared/EditorTemplates/Player.cshtml
#model Player
<div class="form-group">
#Html.HiddenFor(e => e.Id)
#Html.HiddenFor(e => e.TeamId)
<label class="control-label col-md-2" for="player">Player</label>
<div class="col-md-10">
#Html.TextBoxFor(e => e.Name, new { #class = "form-control text-box single-line", id = "player", name = "Player"})
</div>
</div>
Players form on Team view:
#Html.EditorFor(e => e.Player)
Instead of:
foreach (var p in Model.Player)
{
<div class="form-group">
#Html.Raw("<label class=\"control-label col-md-2\">" + p.ToString() + "</label>")
<div class="col-md-10">
#Html.Raw("<input class=\"form-control text-box single-line\" name=\"Player\" type-\"text\"")
</div>
</div>
}
See this article for more information about editor templates: Editor and display templates
Related
Model Binding on Save isn't returning full List of Nested Objects, maximum of one
Trying to understand EditorTemplates, here is a simple example that isn't working at the moment. The Model isn't returning the full IList
of items, it seems to be returning 1 object each time when there should be 2 in my example.
When I Edit ModelA, the example is:
- Name: Model A
List of Model Bs:
- Name: Model B1
List of Model Cs:
- Name: Model C1
- Name: Model C2
- Name: Model B2
This list shows up properly during Edit, but upon Save, some of the information is "lost", added (not found) below.
- Name: Model A (in Model)
List of Model Bs:
- Name: Model B1 (in Model)
List of Model Cs:
- Name: Model C1 (in Model)
- Name: Model C2 (not found)
- Name: Model B2 (not found)
Basically, on Save, the Model is not returning any more than one object from a list.
Classes:
BaseObject:
public class BaseObject
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Key]
public Guid Oid { get; set; }
}
ModelA, ModelB and ModelC:
public class ModelA : BaseObject
{
public string Name { get; set; }
public virtual IList<ModelB> ModelBs { get; set; }
}
public class ModelB : BaseObject
{
public string Name { get; set; }
public Guid? ModelAID { get; set; }
[ForeignKey("ModelAID")]
public virtual ModelA ModelA { get; set; }
public virtual IList<ModelC> ModelCs { get; set; }
}
public class ModelC : BaseObject
{
public string Name { get; set; }
public Guid? ModelBID { get; set; }
[ForeignKey("ModelBID")]
public virtual ModelB ModelB { get; set; }
}
Basic Controller for ModelA, here is the Edit:
public ActionResult Edit(Guid? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
ModelA modelA = db.ModelAs.Find(id);
if (modelA == null)
{
return HttpNotFound();
}
return View(modelA);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(ModelA modelA)
//Removed: [Bind(Include = "Oid,Name,DateCreated,DateUpdated,DateDeleted,IsDeleted")]
{
if (ModelState.IsValid) //Break point to review "modelA"
{
db.Entry(modelA).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(modelA);
}
Views:
Edit.cshtml (for ModelA):
#model x.Models.Nesting.ModelA
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
#Html.EditorForModel(Model)
}
Views in Views/Shared/EditorTemplates:
ModelA.cshtml:
#model x.Models.Nesting.ModelA
<div class="form-horizontal">
<h4>ModelA</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
#Html.HiddenFor(model => model.Oid)
<div class="form-group">
#Html.LabelFor(model => model.Name, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Name, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Name, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.ModelBs, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.ModelBs)
#* Tried the code below as well *#
#*
#for (var i = 0; i < Model.ModelBs.Count(); i++)
{
#Html.EditorFor(m => m.ModelBs[i])
}
*#
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
</div>
ModelB.cshtml:
#model x.Models.Nesting.ModelB
#using (Html.BeginForm())
{
<div class="form-horizontal">
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
#Html.HiddenFor(model => model.Oid)
#Html.HiddenFor(model => model.ModelAID)
<div class="form-group">
#Html.LabelFor(model => model.Name, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Name, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Name, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.ModelCs, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.ModelCs)
#* Tried the code below as well *#
#*
#foreach(var item in Model.ModelCs)
{
#Html.EditorFor(x => item)
}
*#
</div>
</div>
</div>
}
ModelC.cshtml:
#model x.Models.Nesting.ModelC
#using (Html.BeginForm())
{
<div class="form-horizontal">
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
#Html.HiddenFor(model => model.Oid)
#Html.HiddenFor(model => model.ModelBID)
<div class="form-group">
#Html.LabelFor(model => model.Name, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Name, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Name, "", new { #class = "text-danger" })
</div>
</div>
</div>
}
As an example from the HTML (when using #Html.EditorFor(model => model.ModelBs)), the binding for both ModelB objects look look like they would be bound properly (but it only returns the first object):
Model B1:
<input
data-val="true" data-val-required="The Oid field is required."
id="ModelBs_f1aa613f-96f4-427f-a568-c70556ad2117__Oid"
name="ModelBs[f1aa613f-96f4-427f-a568-c70556ad2117].Oid"
type="hidden"
value="7e99950b-62c9-e711-afd4-7cb0c2b5b934">
<input
id="ModelBs_f1aa613f-96f4-427f-a568-c70556ad2117__ModelAID"
name="ModelBs[f1aa613f-96f4-427f-a568-c70556ad2117].ModelAID"
type="hidden"
value="7e99950b-62c9-e711-afd4-7cb0c2b5b935">
<input
class="form-control text-box single-line valid"
id="ModelBs_f1aa613f-96f4-427f-a568-c70556ad2117__Name"
name="ModelBs[f1aa613f-96f4-427f-a568-c70556ad2117].Name"
type="text"
value="Model B1" aria-invalid="false">
Model B2:
<input
data-val="true" data-val-required="The Oid field is required."
id="ModelBs_128318da-af85-46a5-bc0d-71361610d989__Oid"
name="ModelBs[128318da-af85-46a5-bc0d-71361610d989].Oid"
type="hidden"
value="7e99950b-62c9-e711-afd4-7cb0c2b5b936">
<input
id="ModelBs_128318da-af85-46a5-bc0d-71361610d989__ModelAID"
name="ModelBs[128318da-af85-46a5-bc0d-71361610d989].ModelAID"
type="hidden"
value="7e99950b-62c9-e711-afd4-7cb0c2b5b935">
<input
class="form-control text-box single-line valid"
id="ModelBs_128318da-af85-46a5-bc0d-71361610d989__Name"
name="ModelBs[128318da-af85-46a5-bc0d-71361610d989].Name"
type="text"
value="Model B2" aria-invalid="false">
When using the for loop in ModelA, looping ModelB (#for (var i = 0; i < Model.ModelBs.Count(); i++) { #Html.EditorFor(m => m.ModelBs[i]) }), the Indexes are Numbers, but still don't return full Models.
Model B1:
<input
data-val="true" data-val-required="The Oid field is required."
id="ModelBs_0__Oid" name="ModelBs[0].Oid"
type="hidden"
value="7e99950b-62c9-e711-afd4-7cb0c2b5b934">
<input
id="ModelBs_0__ModelAID"
name="ModelBs[0].ModelAID"
type="hidden"
value="7e99950b-62c9-e711-afd4-7cb0c2b5b935">
<input
class="form-control text-box single-line valid"
id="ModelBs_0__Name" name="ModelBs[0].Name"
type="text"
value="Model B1" aria-invalid="false">
Model B2:
<input
data-val="true" data-val-required="The Oid field is required."
id="ModelBs_1__Oid" name="ModelBs[1].Oid"
type="hidden"
value="7e99950b-62c9-e711-afd4-7cb0c2b5b936">
<input
id="ModelBs_1__ModelAID" name="ModelBs[1].ModelAID"
type="hidden"
value="7e99950b-62c9-e711-afd4-7cb0c2b5b935">
<input
class="form-control text-box single-line valid"
id="ModelBs_1__Name" name="ModelBs[1].Name"
type="text"
value="Model B 2" aria-invalid="false">
POST Form Data:
#Html.EditorFor(...) POST (didn't update ModelBs list of ModelCs):
__RequestVerificationToken:1PfXHdYtb5eE-j6g4DWBEZiRa0trOL8UvYGKVjL0pxR1qOjQE52be7UB14VaIJRpp5UA1Iz9WXt4g_7LKixKhK7ah7Hjp6hOLmLa1m7XavI1
Oid:7e99950b-62c9-e711-afd4-7cb0c2b5b935
Name:Model A1
ModelBs.index:4a481093-9bdd-43ae-b84b-144c576ff346
ModelBs[4a481093-9bdd-43ae-b84b-144c576ff346].Oid:7e99950b-62c9-e711-afd4-7cb0c2b5b934
ModelBs[4a481093-9bdd-43ae-b84b-144c576ff346].ModelAID:7e99950b-62c9-e711-afd4-7cb0c2b5b935
ModelBs[4a481093-9bdd-43ae-b84b-144c576ff346].Name:Model B1
ModelBs[4a481093-9bdd-43ae-b84b-144c576ff346].ModelCs[0].Oid:7e99950b-62c9-e711-afd4-7cb0c2b5b936
ModelBs[4a481093-9bdd-43ae-b84b-144c576ff346].ModelCs[0].ModelBID:7e99950b-62c9-e711-afd4-7cb0c2b5b934
ModelBs[4a481093-9bdd-43ae-b84b-144c576ff346].ModelCs[0].Name:Model C 1
ModelBs.index:a1dd130d-a1f7-47ed-90a2-28055a960c9b
For loop POST:
__RequestVerificationToken:x9hpnm-c1g0Cm9gTnSRjCFIVflziqXqiO3iFkzVpMc33gnNlBoDsvwBHMmRT38sWTCGrFSqCqzcuuBZdXLsXTgX1EbkqUSqPuAtwUrR1XXA1
Oid:7e99950b-62c9-e711-afd4-7cb0c2b5b935
Name:Model A1
ModelBs[0].Oid:7e99950b-62c9-e711-afd4-7cb0c2b5b934
ModelBs[0].ModelAID:7e99950b-62c9-e711-afd4-7cb0c2b5b935
ModelBs[0].Name:Model B1
ModelBs[0].ModelCs[0].Oid:7e99950b-62c9-e711-afd4-7cb0c2b5b936
ModelBs[0].ModelCs[0].ModelBID:7e99950b-62c9-e711-afd4-7cb0c2b5b934
ModelBs[0].ModelCs[0].Name:Model C 1
EditorFor has ModelBs.index twice (hopeful this leads to a solution), but not in the For. Something getting cut off> (I double and triple checked that I wasn't missing something further down)
FireFox POST data, looks like the Indexes of 0 and 1 are working to some extent:
__RequestVerificationToken qlrlO7Z0_byGZYLGJ6Tbx5Fzmpd0dd6b-JPac4V-f1U-17v06OQr27dYZPh_VmRI3X4nGj7ZAOHtBdERnuZscJlNlgoHAqdeXaNQN04e2qE1
Oid 7e99950b-62c9-e711-afd4-7cb0c2b5b935
Name Model+A1
ModelBs.index […]
0 3d003e30-d350-4fb0-becd-f65207b033c4
1 be4e35c3-3fee-4c7f-9ee7-580b6e0c8169
ModelBs[3d003e30-d350-4fb0-becd-f65207b033c4].Oid 7e99950b-62c9-e711-afd4-7cb0c2b5b934
ModelBs[3d003e30-d350-4fb0-becd-f65207b033c4].ModelAID 7e99950b-62c9-e711-afd4-7cb0c2b5b935
ModelBs[3d003e30-d350-4fb0-becd-f65207b033c4].Name Model+B1
ModelBs[3d003e30-d350-4fb0-becd-f65207b033c4].ModelCs[0].Oid 7e99950b-62c9-e711-afd4-7cb0c2b5b936
ModelBs[3d003e30-d350-4fb0-becd-f65207b033c4].ModelCs[0].ModelBID 7e99950b-62c9-e711-afd4-7cb0c2b5b934
ModelBs[3d003e30-d350-4fb0-becd-f65207b033c4].ModelCs[0].Name Model+C+1
... the mystery continues
The EditorTemplate files (cshtml) for ModelB and ModelC had #using (Html.BeginForm())
{ ... }, removing this resolved the issue, allowing the POST to contain all the information. Kept the BeginForm on ModelA, that is required.
I figured it out when reviewing the POST information, it showed a(n) .index for the second ModelB, but no associated data, it also has a form tag directly below, everyone knows that many form tags isn't good.
Updated cshtml
ModelB.cshtml:
#model x.Models.Nesting.ModelB
<div class="form-horizontal">
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
#Html.HiddenFor(model => model.Oid)
#Html.HiddenFor(model => model.ModelAID)
<div class="form-group">
#Html.LabelFor(model => model.Name, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Name, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Name, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.ModelCs, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.ModelCs)
#* Tried the code below as well *#
#*
#foreach(var item in Model.ModelCs)
{
#Html.EditorFor(x => item)
}
*#
</div>
</div>
</div>
ModelC.cshtml:
#model x.Models.Nesting.ModelC
<div class="form-horizontal">
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
#Html.HiddenFor(model => model.Oid)
#Html.HiddenFor(model => model.ModelBID)
<div class="form-group">
#Html.LabelFor(model => model.Name, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Name, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Name, "", new { #class = "text-danger" })
</div>
</div>
</div>
I've just started my journey with MVC, but I have a problem and for two days I can't find answer.
I have EditorFor and ListBoxFor in a one site and I want to Post info from EditorFor about Name and from ListBoxFor about EmployeeId.
In listbox I have list of my employees. I can write name in Editor and I can choose one of employee from list.
Everything is ok, but when I mark anything inside ListBox it makes that text inside EditorFor disappears.
View:
#model Models.CreateClient
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div>
<h4>Client</h4>
<hr />
<div class="col-md-6">
#Html.ValidationSummary(true, "", new {#class = "text-danger"})
<div class="form-group">
#Html.LabelFor(model => model.Client.Name, htmlAttributes: new {#class = "control-label col-md-2"})
<div class="col-md-10">
#Html.EditorFor(model => model.Client.Name, new {htmlAttributes = new {#class = "form-control"}})
#Html.ValidationMessageFor(model => model.Client.Name, "", new {#class = "text-danger"})
</div>
</div>
</div>
<div class="col-md-6">
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.SelectedEmployee, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.ListBoxFor(model => model.SelectedtEmployee, Model.EmployeeListItem(), new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.SelectedEmployee, "", new { #class = "text-danger" })
</div>
</div>
</div>
<div class="form-group col-md-6">
<input type="submit" value="CreateNew" class="btn btn-default " />
#Html.ActionLink("Back to List", "Index")
</div>
</div>
}
Model:
public class CreateClient
{
public Client Client { get; set; }
public List<Employee> Employees { get; set; }
public IEnumerable<int> SelectedtEmployee { get; set; }
public IEnumerable<SelectListItem> EmployeeListItem()
{
List<SelectListItem> employeeSelectedListItem = new List<SelectListItem>();
foreach (var employee in Employees)
{
SelectListItem selectListItem = new SelectListItem()
{
Text = employee.FullName,
Value = employee.Id.ToString(),
};
employeeSelectedListItem.Add(selectListItem);
}
return employeeSelectedListItem;
}
}
Controller:
public ActionResult CreateNew()
{
CreateClient createClient = new CreateClient();
createClient.Employees = db.EmployeeContet.ToList();
return View(createClient);
}
I made mistake and my Client's name was int (instead of string).
I'm having following FinanceProductFeatures table , I want show each of this table record as label name for a form.
So I created model class like this
public class ProductFinanceFeatures
{
public IList<AB_FinanceProductFeatures> ListProductFinanceFields { get; set; }
}
public partial class AB_FinanceProductFeatures
{
public string ProductFinanceNameEn { get; set; }
public string ProductFinance_Value_EN { get; set; }
}
then Controller class like this
[HttpGet]
public ViewResult Financing_Product_Feature_Configuration()
{
var model = new ProductFinanceFeatures
{
ListProductFinanceFields = db.FinanceProductFeatures.ToList()
};
return View(model);
}
then Its viewpage like this
#model albaraka.Models.ProductFinanceFeatures
#{
}
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
#for (int i = 0; i < Model.ListProductFinanceFields.Count; i++)
{
<div class="form-group">
#Html.LabelFor(model => model.ListProductFinanceFields[i].ProductFinanceNameEn, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.TextAreaFor(m => m.ListProductFinanceFields[i].ProductFinance_Value_EN, new { #row = 5 })
</div>
</div>
}
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
</div>
}
but here I'm not getting expected result, cannot render the Label
showing like this
Just simply replace TextAreaFor with DisplayFor as below-
<div class="col-md-10">
#Html.DisplayFor(m => m.ListProductFinanceFields[i].ProductFinance_Value_EN, new { #row = 5 })
</div>
Or
<div class="col-md-10">
#Html.DisplayTextFor(m => m.ListProductFinanceFields[i].ProductFinance_Value_EN)
</div>
Hope this works for you..!
I have an object called Job and one of the properties is a List of Steps:
public class Job
{
[Display(Name = "Id")]
public int? JobId { get; set; }
[Required]
public string Name { get; set; }
public List<Step> Steps { get; set; }
public Job()
{
Steps = new List<Step>();
}
}
public class Step
{
public int? StepId { get; set; }
public int JobId { get; set; }
[Required]
public string Name { get; set; }
}
I have a JobController with the following action to perform the update:
// PUT: /Job/Edit/5
[HttpPut]
public ActionResult Edit(Job model)
{
// Logic to update model here
}
Based on a the answer to this question I updated my UI (using the Bootstrap template that comes with MVC5) to:
#using (Html.BeginForm())
{
#Html.HttpMethodOverride(HttpVerbs.Put)
#Html.AntiForgeryToken()
<div class="form-horizontal">
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
#Html.HiddenFor(model => model.JobId)
<div class="form-group">
#Html.LabelFor(model => model.Name, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Name, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Name, "", new { #class = "text-danger" })
</div>
</div>
<h3>Steps</h3>
<div>
#foreach (var item in Model.Steps)
{
<div class="form-group">
#Html.Hidden("Steps[" + stepIndex + "].StepId", item.StepId)
#Html.LabelFor(modelItem => item.Name, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
<input class="form-control text-box single-line" data-val="true" data-val-required="The Name field is required."
id="#String.Format("Steps_{0}__Name", stepIndex)" name="#String.Format("Steps[{0}].Name", stepIndex)" type="text" value="#item.Name" />
#Html.ValidationMessageFor(modelItem => item.Name, "", new { #class = "text-danger" })
</div>
</div>
stepIndex += 1;
<hr />
}
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
</div>
}
As you can see I have to manually build the input tag opposed to using Html.EditorFor. The reason is that I need to control name of the id so that it passes the Index into the id and name. I would assume there is a better approach that would allow MVC to render the correct values using labelFor, EditorFor and ValidationMessageFor.
The questions I have are:
Is there a set of controls I can use with MVC5 that allows me to render complex child objects without going through these extra steps?
If no on 1, then is there a better approach than manually create input tag?
Option 1: Replace the foreach loop with for:
#for (int i = 0; i < Model.Steps.Count; i++)
{
<div class="form-group">
#Html.HiddenFor(m => m.Steps[i].StepId)
#Html.TextBoxFor(m => m.Steps[i].Name, new { #class = "form-control text-box single-line" })
...
</div>
}
Option 2: Create an editor template called Step.chtml for the Step class and use EditorFor:
Step.chtml
#model Step
<div class="form-group">
#Html.HiddenFor(m => m.StepId)
#Html.TextBoxFor(m => m.Name, new { #class = "form-control text-box single-line" })
...
</div>
Main View
<h3>Steps</h3>
<div>
#Html.EditorFor(m => m.Steps)
<div>
In both these ways the framework will give the inputs correct names and ids.
Looks like complicated the things, try the below.
1. Create a new editor template (which is a view) named 'Step.cshtml' under the EditorTemplates folder with the model Step.
2. In that do the below code, Step.cshtml
#model Step
<div class="form-group">
#Html.HiddenFor(model => model.StepId)
#Html.LabelFor(modelItem => modelItem.Name, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.TextBoxFor(model => model.Name, htmlAttributes: new { #class = "form-control text-box single-line" })
#Html.ValidationMessageFor(modelItem => modelItem.Name, "", new { #class = "text-danger" })
</div>
3. Remove the foreach statement from your view, and instead call the editor template as,
#Html.EditorFor(model => model.Steps)
I have a viewmodel that contains a list of another viewmodel. As demonstrated here:
public class PrizeViewModel
{
public Guid PrizeId { get; set; }
public string Description { get; set; }
public List<CategoryViewModel> Categories { get; set; }
}
CategoryViewModel is defined as such:
[Serializable]
public class CategoryViewModel
{
public int Id { get; set; }
public string Name { get; set; }
public bool Selected { get; set; }
}
The Prize view looks like this:
#model PrizeViewModel
#{
ViewBag.Title = "Create";
}
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Prize</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.Description, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Description, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Description, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Categories, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
Create New Category
#Html.EditorFor(model => model.Categories, new { htmlAttributes = new { #class = "form-control" } })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
</div>
}
I then have a CategoryEditorTemplate:
#model CategoryViewModel
<div>
<span><input id="#Model.Id" name="#Model.Id" type="checkbox" checked="#(Model.Selected)" value="#Model.Name" /></span>
<label for="#Model.Id">#Model.Name</label>
</div>
The Create method in the controller takes a PrizeViewModel, problem that I am having is that when I get the PrizeViewModel back, Categories is null. Any suggestions?
First, I don't understand how your Category template is supposed to work. You're mixing up your Boolean and id and somehow expecting them to bind... Here's how I think you want it to work.
Change your category editor template to this (it should be called CategoryViewModel.cshtml) The key is that you need to hidden values in order to post them back to the server. And, like Stephen mentions, you were overriding the Editor Template automatic collection naming by not using a helper for your input fields.
#model CategoryViewModel
<div>
#Html.HiddenFor(x => x.Id)
#Html.HiddenFor(x => x.Name)
<Label>#Html.CheckBoxFor(x => x.Selected) #Model.Name</label>
</div>
Your prize view should be fine exactly as you posted it.
Do not. I repeat, do NOT use any form of foreach or for statement with an editor template and collections.
If you loop through categories with for loop when posting to actionresult it will be able to bind model to list here is post about binding model to list
http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx/
here is example of how to make it:
#for(int i = 0; i < Model.Categories.Count(); i++)
{
#Html.EditorFor(model => Model.Categories[i])
}
In your CategoryEditorTemplate, you are overriding the default naming required for correct binding when you do ...name="#Model.Id"...
The html for your categories should look like
<input type="text" name=Categories[0].Name ...
<input type="checkbox" name=Categories[0].Selected...
<input type="text" name=Categories[1].Name ...
....
Either use helpers in your Template, for example
#Html.TextBoxFor(m => m.Name)
#Html.CheckBoxFor(m => m.Selected)
or delete the template and use a for loop to generate the html
#for(int i = 0; i < Model.Categories.Count; i++)
{
#Html.TextBoxFor(m => m.Categories[i].Name)
#Html.CheckBoxFor(m => m.Categories[i].Selected)
}