As well as Stephen's method, you can achieve all this relatively simply, without partial views.
In the first instance, you should revise your model a little. Add to Machine:
// Foreign key
public int JobID { get; set; }
// Navigation properties
public virtual Job Job{ get; set; }
The Job model, you have not shown, but it needs to be:
public class Job
{
public int Id { get; set; }
public float Price { get; set; }
public int JobSubCategoryId { get; set; }
public string JobDescription { get; set; }
public int SpecialRequirementId { get; set; }
public virtual List<Machine> Machines { get; set; }
}
Here is my complete JobViewModel:
public class JobViewModel
{
public JobViewModel()
{
Machines = new List<Machine>();
}
public int Id { get; set; }
public float Price { get; set; }
public int JobSubCategoryId { get; set; }
public string JobDescription { get; set; }
public int SpecialRequirementId { get; set; }
public List<Machine> Machines { get; set; }
public string NewMachineBrand { get; set; }
public string NewMachineType { get; set; }
public string NewMachineName { get; set; }
public void AddMachine()
{
Machine tmp = new Machine { Brand = NewMachineBrand, Type = NewMachineType, Name = NewMachineName };
Machines.Add(tmp);
NewMachineBrand = NewMachineType = NewMachineName = null;
}
public Job GetJob()
{
Job job = new Job();
job.JobDescription = JobDescription;
job.Price = Price;
job.JobSubCategoryId = JobSubCategoryId;
job.SpecialRequirementId = SpecialRequirementId;
job.Machines = new List<Machine>();
foreach (Machine m in Machines)
{
job.Machines.Add(m);
}
return job;
}
}
When creating your create view based on JobViewModel, you will need to add two things that are not defaulted for you, firstly a table to hold the new Machines, and secondly a button to add each machine in turn.
My complete create.cshtml view looks like this:
@model JobMachinesMVC.Models.JobViewModel
@{
ViewBag.Title = "Create";
}
<h2>Create</h2>
@using (Html.BeginForm())
{
@Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Job</h4>
<hr />
@Html.ValidationSummary(true, "", new { @class = "text-danger" })
<div class="form-group">
@Html.LabelFor(model => model.Price, htmlAttributes: new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(model => model.Price, new { htmlAttributes = new { @class = "form-control" } })
@Html.ValidationMessageFor(model => model.Price, "", new { @class = "text-danger" })
</div>
</div>
<div class="form-group">
@Html.LabelFor(model => model.JobSubCategoryId, htmlAttributes: new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(model => model.JobSubCategoryId, new { htmlAttributes = new { @class = "form-control" } })
@Html.ValidationMessageFor(model => model.JobSubCategoryId, "", new { @class = "text-danger" })
</div>
</div>
<div class="form-group">
@Html.LabelFor(model => model.JobDescription, htmlAttributes: new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(model => model.JobDescription, new { htmlAttributes = new { @class = "form-control" } })
@Html.ValidationMessageFor(model => model.JobDescription, "", new { @class = "text-danger" })
</div>
</div>
<div class="form-group">
@Html.LabelFor(model => model.SpecialRequirementId, htmlAttributes: new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(model => model.SpecialRequirementId, new { htmlAttributes = new { @class = "form-control" } })
@Html.ValidationMessageFor(model => model.SpecialRequirementId, "", new { @class = "text-danger" })
</div>
</div>
<div class="form-group">
@Html.LabelFor(model => model.NewMachineBrand, htmlAttributes: new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(model => model.NewMachineBrand, new { htmlAttributes = new { @class = "form-control" } })
@Html.ValidationMessageFor(model => model.NewMachineBrand, "", new { @class = "text-danger" })
</div>
</div>
<div class="form-group">
@Html.LabelFor(model => model.NewMachineType, htmlAttributes: new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(model => model.NewMachineType, new { htmlAttributes = new { @class = "form-control" } })
@Html.ValidationMessageFor(model => model.NewMachineType, "", new { @class = "text-danger" })
</div>
</div>
<div class="form-group">
@Html.LabelFor(model => model.NewMachineName, htmlAttributes: new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(model => model.NewMachineName, new { htmlAttributes = new { @class = "form-control" } })
@Html.ValidationMessageFor(model => model.NewMachineName, "", new { @class = "text-danger" })
</div>
</div>
<div class="form-group">
<table>
<thead>
<tr>
<th style="text-align:right">
@Html.DisplayNameFor(model => model.Machines.FirstOrDefault().Brand)
</th>
<th style="text-align:right">
@Html.DisplayNameFor(model => model.Machines.FirstOrDefault().Name)
</th>
<th style="text-align:right">
@Html.DisplayNameFor(model => model.Machines.FirstOrDefault().Type)
</th>
</tr>
</thead>
<tbody>
@for (int i = 0; i < Model.Machines.Count; i++)
{
<tr>
<td style="text-align:right">@Html.HiddenFor(m => m.Machines[i].Id)@Html.DisplayFor(m => m.Machines[i].Brand)@Html.HiddenFor(m => m.Machines[i].Brand)</td>
<td style="text-align:right">@Html.DisplayFor(m => m.Machines[i].Name)@Html.HiddenFor(m => m.Machines[i].Name)</td>
<td style="text-align:right">@Html.DisplayFor(m => m.Machines[i].Type)@Html.HiddenFor(m => m.Machines[i].Type)</td>
</tr>
}
</tbody>
</table>
</div>
<div class="form-group">
<input type="submit" value="Add Machine" name="addmachine" class="btn btn-default" />
</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")
}
A couple of things to note here. I always include a @Html.HiddenFor in such a sub-table, because @Html.DisplayFor items can be lost when posting back to the controller. Secondly, there are two input type="submit" on the same View. One is given a name attribute. This is so that the Controller can distinguish between the two clicks.
The relevant lines from my controller are these:
// GET: Jobs/Create
public ActionResult Create()
{
JobViewModel job = new JobViewModel();
return View(job);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(JobViewModel jobvm)
{
if (Request.Form["addmachine"] != null)
{
jobvm.AddMachine();
ModelState.Remove("NewMachineName");
ModelState.Remove("NewMachineType");
ModelState.Remove("NewMachineBrand");
return View(jobvm);
}
if (ModelState.IsValid)
{
Job job = jobvm.GetJob();
db.Jobs.Add(job);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(jobvm);
}
If "addmachine" is clicked, the new machine values get added to the Machines List, get reset and the form is redisplayed. Note you need to set the ModelState even though the ViewModel sets the values to null, otherwise you old values persist in the view. If Create is clicked, the model is checked for ValidState, and the job is saved. What about the Machine table? Because the models are set up as outlined above, internally MVC knows that it has to save the values to Machine as well.
Please note that the above illustration is very crude. I have applied no styling apart from that which you get "out of the box". You will want to tidy this up (a lot!), but I hope I have given you a good start in one way to approach this problem.