Working with dynamic editor template - asp.net-mvc

I am trying to build a web site that lets user to create new violation.Each violation has its own input area.For instance, if user wants to create a new Ramp Width Violation, he/she should provide width or for Ramp Slope Violation slope should be provided as input.
Taking into account those requirements , I decided to have a dynamic property inside my view.
My dynamic object property can be boolean , integer or double according to violation type.
Here is my class that includes dynamic property.Other properties are removed for brevity.
public class CreateViolationViewModel
{
public string DynamicName { get; set; }
public object DynamicValue { get; set; }
}
Main View
#Html.LabelFor(model => model.DynamicName, #Model.DynamicName, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(x => x.DynamicValue, "violationDynamics_" + Model.DynamicValue.GetType().Name)
</div>
#Html.HiddenFor(model => model.DynamicName, new {#value=Model.DynamicName })
EditorTemplate For Integer(violationDynamics_Int32.cshtml)
#{
Layout = null;
}
#model int
#Html.TextBoxFor(x => x, new { #class = "form-control", #type = "number" })
#Html.Hidden("ModelType", Model.GetType())
EditorTemplate For Double(violationDynamics_Double.cshtml)
#{
Layout = null;
}
#model double
#Html.TextBoxFor(x => x, new { #class = "form-control", #type = "number" })
#Html.Hidden("ModelType", Model.GetType())
Controller
[HttpPost]
public ActionResult Create(CreateViolationViewModel createViolationViewModel)
{
}
When the page is loaded it renders the related editor templates.The problem is that when the form is posted back dynamic value comes as a array of string as shown below.
I know I need something like model binder but haven't found the solution yet.By the way I take this approach from the previous answer here
So questions are ;
1- How can I make this approach work for post backs?
2- Is there any other recommend or easy approach to accomplish my task ?
Thanks

Related

How do I carry a complex object model through a POST request

I have the following entity models:
public class AssetLabel
{
public string QRCode { get; set; }
public string asset { get; set; }
public virtual IEnumerable<Conversation> Conversations { get; set; }
}
public class Conversation
{
public int ID { get; set; }
public virtual AssetLabel AssetLabel{ get; set; }
public string FinderName { get; set; }
public string FinderMobile { get; set; }
public string FinderEmail { get; set; }
public ConversationStatus Status{ get; set; }
public IEnumerable<ConversationMessage> Messages { get; set; }
}
public class ConversationMessage
{
public int ID { get; set; }
public DateTime MessageDateTime { get; set; }
public bool IsFinderMessage { get; set; }
public virtual Conversation Conversation { get; set; }
}
public enum ConversationStatus { open, closed };
public class FinderViewModel : Conversation
{/*used in Controllers->Found*/
}
My MVC application will prompt for a QRCode on a POST request. I then validate this code exists in the database AssetLabel and some other server-side logic is satisfied. I then need to request the user contact details to create a new Conversation record.
Currently I have a GET to a controller action which returns the first form to capture the Code. If this is valid then I create a new FinderViewModel, populate the AssetLabel with the object for the QRCode and return a view to consume the vm and show the fields for the Name, Mobile and Email.
My problem is that although the AssetLabel is being passed to the view as part of the FinderViewModel and I can display fields from the AssetLabel; graphed object the AssetLabel does not get passed back in the POST. I know I could modify the FinderViewModel so that it takes the Conversation as one property and set up the QRCode as a separate property that could be a hidden field in the form and then re-find the the AssetLabel as part of the processing of the second form but this feels like a lot of work seeing as I have already validated it once to get to the point of creating the second form (this is why I am moving away from PHP MVC frameworks).
The first question is HOW?, The second question is am I approaching this design pattern in the wrong way. Is there a more .NETty way to persist the data through multiple forms? At this point in my learning I don't really want to store the information in a cookie or use ajax.
For reference the rest of the code for the 1st form POST, 2nd view and 2nd form POST are shown below (simplified to eliminate irrelevant logic).
public class FoundController : Controller
{
private ApplicationDbContext db = new ApplicationDbContext();
// GET: Found
public ActionResult Index()
{
AssetLabel lbl = new AssetLabel();
return View(lbl);
}
[HttpPost]
public ActionResult Index(string QRCode)
{
if (QRCode=="")
{
return Content("no value entered");
}
else
{
/*check to see if code is in database*/
AssetLabel lbl = db.AssetLables.FirstOrDefault(q =>q.QRCode==QRCode);
if (lbl != null)
{
var vm = new FinderViewModel();
vm.AssetLabel = lbl;
vm.Status = ConversationStatus.open;
return View("FinderDetails", vm);
}
else
{/*Label ID is not in the database*/
return Content("Label Not Found");
}
}
}
[HttpPost]
public ActionResult ProcessFinder(FinderViewModel vm)
{
/*
THIS IS WHERE I AM STUCK! - vm.AssetLabel == NULL even though it
was passed to the view with a fully populated object
*/
return Content(vm.AssetLabel.QRCode.ToString());
//return Content("Finder Details posted!");
}
FinderView.cshtml
#model GMSB.ViewModels.FinderViewModel
#{
ViewBag.Title = "TEST FINDER";
}
<h2>FinderDetails</h2>
#using (Html.BeginForm("ProcessFinder","Found",FormMethod.Post))
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Finder Details</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
#Html.HiddenFor(model => model.ID)
#Html.HiddenFor(model => model.AssetLabel)
<div class="form-group">
#Html.LabelFor(model => model.FinderName, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.FinderName, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.FinderName, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.FinderMobile, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.FinderMobile, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.FinderMobile, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.FinderEmail, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.FinderEmail, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.FinderEmail, "", new { #class = "text-danger" })
</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>
}
Rendered HTML snippet for AssetLabel
<input id="AssetLabel" name="AssetLabel" type="hidden"
value="System.Data.Entity.DynamicProxies.AssetLabel_32653C4084FF0CBCFDBE520EA1FC5FE4F22B6D9CD6D5A87E7F1B7A198A59DBB3"
/>
You cannot use #Html.HiddenFor() to generate a hidden output for a complex object. Internally the method use .ToString() to generate the value (in you case the output is System.Data.Entity.DynamicProxies.AssetLabel_32653C4084FF0CBCFDBE520EA1FC5FE4F22B6D9CD6D5A87E7F1B7A198A59DBB3 which cannot be bound back to a complex object)
You could generate a form control for each property of the AssetLabel - but that would be unrealistic in your case because AssetLabel contains a property with is a collection of Conversation which in turn contains a collection of ConversationMessage so you would need nested for loops to generate an input for each property of Conversation and ConversationMessage.
But sending a whole lot of extra data to the client and then sending it all back again unchanged degrades performance, exposes unnecessary details about your data and data structure to malicious users, and malicious users could change the data).
The FinderViewModel should just contain a property for QRCode (or the ID property of AssetLabel) and in the view
#Html.HiddenFor(m => m.QRCode)
Then in the POST method, if you need the AssetLabel, get it again from the repository just as your doing it in the GET method (although its unclear why you need to AssetLabel in the POST method).
As a side note, a view model should only contain properties that are needed in the view, and not contain properties which are data models (in in your case inherit from a data model) when editing data. Refer What is ViewModel in MVC?. Based on your view, it should contain 4 properties FinderName, FinderMobile, FinderEmail and QRCode (and int? ID if you want to use it for editing existing objects).
Thanks Stephen. The QRCode is the PK on AssetLabel and the FK in Conversation so it needs to be tracked through the workflow. I was trying to keep the viewModel generic so that is can be used for other forms rather than tightly coupling it to this specific form and I was trying to pass the AssetLabel around as I have already done a significant amount of validation on it's state which I didn't want to repeat. I worked out what I need to do - If you use #Html.Hidden(model => model.AssetLabel.QRCode) then the form field name becomes AssetLabel_QRCode and is automatically mapped to the correct place in the POST viewmodel. To promote code reuse and avoid any rework later I have created this logic in a display template with the fields defined as hidden and then #Html.Partial() using the overload method that allows me to define the model extension to the form names
#Html.Partial
(
"./Templates/Assetlabel_hidden",
(GMSB.Models.AssetLabel)(Model.AssetLabel),
new ViewDataDictionary()
{
TemplateInfo = new TemplateInfo()
{
HtmlFieldPrefix = "AssetLabel"
}
}
)
But you are absolutely right, this exposes additional fields and my application structure. I think I will redraft the viewModel to only expose the necessary fields and move the AssetLabel validation to a separate private function that can be called from both the initial POST and the subsequent post. This does mean extra code in the controller as the flat vm fields need to be manually mappped to the complex object graph.

How do I use a SelectListItem in ASP.NET MVC to display choices via a List Box or Check List box

I've been reading several articles about how to present choices to users. Some use ListBoxFor, some use CheckBoxFor, and then there is this thing called MultiSelectList.
What I am (was) confused about was that each example seemed to have done it a totally different way, and none of them actually used the built in "SelectListItem" class but instead always created their own.
So I originally was going to post a question asking for general clarification, but I thought it would just be representative of all the other various post and repetitive.
So let me re-phrase: How do you use a "List" or a "MultiSelectList" to present a user a list of choices, including the option for them to be displayed as a list of Check boxes?
In other words, if I have the following 2 items in my Model, how would I display each of them as a typical choice list box, or a typical Check List box?
public List<SelectListItem> Widgets1 { get; set; }
public MultiSelectList Widgets2 { get; set; }
Warning... just wanted to point out that the "CheckBox" option basically hangs once you get too many choices. (e.g. changed my loop to 500) and it basically won't submit.
the problem is traced back to the validation of the CheckBoxFor line. This can be fixed by changing the one line to...
#Html.CheckBoxFor(cc => cc.WidgetsAsCheckList[myIndex].Selected, new { data_val = "false", htmlAttributes = new { #class = "form-control" } })
If I do this, I can have 1,500 items in the check list and the submit occurs in under 3 seconds
So let's start with the Model. It is really basic and you can see that I'm just creating 3 Lists that will store the same data.
The primary difference here is that all of the examples I read, people where creating their own Item classes, where I wanted to simply use the built in "SelectListItem" class
public class FooModel
{
[Display(Name = "WidgetCheckList")]
public List<SelectListItem> WidgetsAsCheckList { get; set; }
[Display(Name = "WidgetListBox")]
public List<SelectListItem> WidgetsAsListBox { get; set; }
[Display(Name = "WidgetMultiSelectList")]
public MultiSelectList WidgetMultiSelectList { get; set; }
//We have to create a bucket that not only some how
//auto-magically knowns what has been pre-selected in the
//original list, but provides the view something to store
//the new selections in when returning to the controller.
//I have to admit, I have no idea how this knows what was
//pre-selected, but being new at MVC, there are things I
//just have to leave it as a mystery becuase it just works.
[HiddenInput(DisplayValue = false)]
public List<string> userSelectionsAsListBox { get; set; }
[HiddenInput(DisplayValue = false)]
public List<string> userSelectionsAsMultiSelectList { get; set; }
public FooModel()
{
this.WidgetsAsCheckList = new List<SelectListItem>();
this.WidgetsAsListBox = new List<SelectListItem>();
this.WidgetMultiSelectList = new MultiSelectList(new List<SelectListItem>());
}
}
For the Controller, because this was a learning test, I just made up the data. The key here is that I build a List of SelectListItems and then used that same list to populate all 3 demo fields of the Model to show 3 different ways to work with the same data.
// ------------------------------------------------------------------------
[HttpGet] //Display the Edit view
public ActionResult Edit()
{
FooModel myModel = new FooModel;
//For testing, here I'm going to Inject some Choices
//So first we build a list of them
List<SelectListItem> myChoices = new List<SelectListItem>();
for (Int32 myIndex = 1; myIndex < 15; myIndex++)
{
SelectListItem myChoice = new SelectListItem();
myChoice.Value = myIndex.ToString();
myChoice.Text = "Choice " + myIndex.ToString();
if ((myIndex % 2) == 0)
{
myChoice.Selected = true;
}
else
{
myChoice.Selected = false;
}
myChoices.Add(myChoice);
}
String[] mySelections = myChoices.Where(x => x.Selected == true).ToArray().Select(x => x.Value).ToArray();
//Now we use that same list to populate all 3 variations in our model
myModel.WidgetsAsCheckList.AddRange(myChoices);
myModel.WidgetsAsListBox.AddRange(myChoices);
myModel.WidgetMultiSelectList = new MultiSelectList(myChoices, "Value", "Text", mySelections);
return View(myModel);
}
Now for the view, I display each list. The first of course is a check box and the second and third are list boxes but use different underlying objects...
#* This displays the "list" of SelectListItems as Checkboxes but we have to do alot more work *#
<div class="form-group">
#Html.LabelFor(model => model.WidgetsAsCheckList, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
<div class="form-control" style="overflow-y: scroll; height: 25em; width:280px;">
#for (var myIndex = 0; myIndex < Model.WidgetsAsCheckList.Count; myIndex++)
{
#Html.CheckBoxFor(cc => cc.WidgetsAsCheckList[myIndex].Selected, new { htmlAttributes = new { #class = "form-control" } })
#Html.HiddenFor(cc => cc.WidgetsAsCheckList[myIndex].Value, new { htmlAttributes = new { #class = "form-control" } })
#Html.DisplayFor(cc => cc.WidgetsAsCheckList[myIndex].Text, new { htmlAttributes = new { #class = "form-control" } })
<br />
}
</div>
</div>
</div>
#* This displays the "list" of SelectListItems as list box that does all the work for us *#
<div class="form-group">
#Html.LabelFor(model => model.WidgetsAsListBox, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.ListBoxFor(model => model.userSelectionsAsListBox, Model.WidgetsAsListBox, new { #class = "form-control", size = 25 })
</div>
</div>
#* This displays the "MultiSelectList" as list box that does all the work for us *#
<div class="form-group">
#Html.LabelFor(model => model.WidgetMultiSelectList, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.ListBoxFor(model => model.userSelectionsAsMultiSelectList, Model.WidgetMultiSelectList, new { #class = "form-control", size = 25 })
</div>
</div>
And finally, when the user makes their own selections (or takes the pre-selected ones) and hits submit, we can get the results in the the controller simply by...
// ---------------------------------------------------------------------
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(FooModel myFooModel)
{
List<string> SelectedItemsFromListBox = myFooModel.userSelectionsAsListBox;
List<string> SelectedItemsFromMultiSelectList = myFooModel.userSelectionsAsMultiSelectList;
List<string> SelectedItemsFromCheckList = myFooModel.WidgetsAsCheckList.Where(x => x.Selected == true).ToList().Select(x => x.Value).ToList();
}

Html Helper Drop Down List switches value to top option on submit / in database

I am filling out a form, however when selecting an option from the drop down list and click submit, no matter what option I select, it always parses the top one through. The displayed value never changes, so it you leave it as the default option 'please select...' and click submit, this stays as 'please select...' but the entry in the database is always the one that appears at the top of the drop down.
Here is the model:
public enum Medium
{
[Description("Teleconference & Report")]
Teleconference_Report,
[Description("Email & Telephone")]
Email_Telephone
}
[Required]
[Display(Name = "Medium")]
public Medium Medium { get; set; }
Here is the field in the form:
<div class="form-group">
#Html.LabelFor(model => model.Medium, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-5">
#Html.DropDownList("MediumID", null, "Please select...", htmlAttributes: new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.Medium, "", new { #class = "text-danger" })
</div>
</div>
The "MediumID" DropDownList is populated using a viewbag which is set to whatever the following returns:
// Puts all of the mediums of communication into a user friendly dropdownlist.
public List<SelectListItem> GetMediumList()
{
List<SelectListItem> mediumList = new List<SelectListItem>();
foreach (Medium state in EnumToList<Medium>())
{
mediumList.Add(new SelectListItem
{
Text = GetEnumDescription(state),
Value = state.ToString(),
});
}
return mediumList;
}
Below shows the form section for another enum called 'Frequency', but these are not changed to user friendly strings (and is working fine).
<div class="form-group">
#Html.LabelFor(model => model.Frequency, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-5">
#Html.EnumDropDownListFor(model => model.Frequency, "Please select...", htmlAttributes: new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.Frequency, "", new { #class = "text-danger" })
</div>
</div>
Below here, shows the two methods which turn the enums into user friendly strings:
// Returns a 'user friendly', readable version of the enum.
public static string GetEnumDescription(Enum value)
{
FieldInfo fi = value.GetType().GetField(value.ToString());
DescriptionAttribute[] attributes =
(DescriptionAttribute[])fi.GetCustomAttributes(typeof(DescriptionAttribute), false);
if (attributes != null && attributes.Length > 0)
return attributes[0].Description;
else
return value.ToString();
}
// Puts all of the same enums into a list.
public static IEnumerable<T> EnumToList<T>()
{
Type enumType = typeof(T);
// Can't use generic type constraints on value types,
// so have to do check like this.
if (enumType.BaseType != typeof(Enum))
throw new ArgumentException("T must be of type System.Enum");
Array enumValArray = Enum.GetValues(enumType);
List<T> enumValList = new List<T>(enumValArray.Length);
foreach (int val in enumValArray)
{
enumValList.Add((T)Enum.Parse(enumType, val.ToString()));
}
return enumValList;
}
Finally, here is the method signature where the fields are binded/bound:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "Point,ApplicationID,MediumID,Frequency,StartDate,EndDate")] TouchPoint touchPoint)
Within this method, the dropdown is passed to the view using the following:
ViewBag.MediumID = GetMediumList();
Any help is greatly appreciated.
Your model has a property named Medium but your view does not bind to that property. The name of the <select> your generating is MediumID which does not exist in your model, so the default value for Medium when you submit will Teleconference_Report (the first enum value).
Change the view to
#Html.DropDownListFor(m => m.Medium, (IEnumerable<SelectListItem>)ViewBag.MediumID, "Please select...", new { #class = "form-control" })
although I would recommend changing the ViewBag property name to say MediumList to make it more obvious that its a collection. And even better, use a view model with a property public IEnumerable<SelectListItem> MediumList { get; set; } so that the viewcan be #Html.DropDownListFor(m => m.Medium, Model.MediumList, .... ).
You also need to change the [Bind] attribute to include "Medium" (and remove "MediumID") although using a view model means the [Bind] attribute is not required.
Side note: You do not need the [Required] attribute unless you want to add a specific error message using the ErrorMessage = "..." property (an enum is always required by default unless you make the property nullable).

LabelFor incorrect when using AllowHtml

I'm using MVC 5.2.2.0, .net 4.5.1 and I'm seeing some odd behavior. I have a model like so:
public class Program
{
// .... Other Properties
[Display(Name = "Courses Description")]
[RichText]
[AllowHtml]
public string CoursesDescription { get; set; }
// .... Other Properties
}
RichText is a custom attribute:
[AttributeUsage(AttributeTargets.Property)]
public class RichText : Attribute
{
public RichText() { }
}
All it is used for is to tell the T4 template to add the wysi data-editor attribute to the TextAreaFor html helper.
My view has the following:
<div class="form-group">
#Html.LabelFor(model => model.CoursesDescription, htmlAttributes: new { #class = "control-label col-sm-3" })
<div class="col-sm-6">
#Html.TextAreaFor(model => model.CoursesDescription, 10, 20, new { #class = "form-control", data_editor = "wysi" })
#Html.ValidationMessageFor(model => model.CoursesDescription, "", new { #class = "text-danger" })
</div>
</div>
With the AllowHtml attribute, the View renders like this:
However, if I modify the model by removing the AllowHtml attribute, the view renders correctly.
public class Program
{
// .... Other Properties
[Display(Name = "Courses Description")]
[RichText]
public string CoursesDescription { get; set; }
// .... Other Properties
}
It is also worth pointing out that removing the RichText attribute doesn't change anything, the View still ignores the Display attribute.
Any ideas why AllowHtml is interfering with LabelFor?
Digging deeper into this, I noticed that my MVC project was using MVC 5.2.2.0, but I had inadvertently installed MVC 5.2.3.0 for my models project.
Downgrading the models project to 5.2.2.0 fixed the problem.

Master-Detail created in one View

I have a Model with Child model.
[Table("Personnel")]
public class Personnel
{
[Key]
public int Id { get; set; }
[MaxLength(10)]
public string Code { get; set; }
[MaxLength(20)]
public string Name { get; set; }
public virtual List<PersonnelDegree> Degrees
{
get;
set;
}
}
public class PersonnelDegree
{
[Key]
public int Id { get; set; }
[ForeignKey("Personnel")]
public int PersonnelId { get; set; }
public virtual Personnel Personnel { get; set; }
[UIHint("Enum")]
public Degree Degree { get; set; }
public string Major { get; set; }
public string SubField { get; set; }
public string Location { get; set; }
}
I want to created a view for this.(Add)
I added pesonnel field to view, but how to add items for PersonnelDegree?
#using (Html.BeginForm("Add", "Personnel", FormMethod.Post, new {enctype = "multipart/form-data", #class = "form-horizontal tasi-form", id = "default"}))
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true, null, new {#class = "alert alert-danger "})
<div class="form-group">
#Html.LabelFor(m => m.Code, new {#class = "control-label col-lg-1"})
<div class="col-lg-3">
#Html.TextBoxFor(m => m.Code, null, new {#class = "form-control", maxlength = 10})
#Html.ValidationMessageFor(m => m.Code)
</div>
</div>
<div class="form-group">
#Html.LabelFor(m => m.Name, new {#class = "control-label col-lg-1"})
<div class="col-lg-3">
#Html.TextBoxFor(m => m.Name, new {#class = "form-control", maxlength = 20})
#Html.ValidationMessageFor(m => m.Name)
</div>
#Html.LabelFor(m => m.Family, new {#class = "control-label col-lg-1"})
<div class="col-lg-3">
#Html.TextBoxFor(m => m.Family, null, new {#class = "form-control", maxlength = 30})
#Html.ValidationMessageFor(m => m.Family)
</div>
</div>
Can i add multy PersonnelDegrees in this View?
Edit
I add a div in view for Degrees
<div id="Degrees">
<div id="NewDegree" style="display:none">
<div class="form-group">
<input class="form-control" id="Degrees[#].Major" name="Degrees[#].Major" value="" type="text">
// another items
</div>
</div>
</div>
and in javascript :
$(document).ready(function() {
$(function() {
$("#addItem").click(function () {
var index = $('#Degrees tbody tr').length; // assumes rows wont be deleted
var clone = $('#NewDegree').html();
// Update the index of the clone
clone.replace(/\[#\]/g, '[' + index + ']');
clone.replace(/"%"/g, '"' + index + '"');
$('#Degrees').append(clone);
});
);
});
it add a div ,But after a few seconds hide div and refresh page.
Yes, you can. There are several options how to do it:
Use Js grid-like component
Use some js grid component, i prefer jqgrid you can add data localy on your View with it and then serialize it on form POST to controller.
Advantage: You don't need to write js CRUD operations with your grid your self the only thing that you should get is how to serialize local data right way to controller.
Disadvantage: You should learn how component works and could be that some component not easy emplimentable in MVC project (I mean you could lost your model validation, data annotation etc. on client side)
Add markup with js
Write your own js to resolve this issue. Here is good basic example how to do it. The idea that you generate html with js(get js from controller) and add it to your View.
Advantage: You could do what ever you want is you know js.
Disadvantage: You lost your model validation, data annotation etc. on client side.
Use PartialView with js
Get markup from Controller with Ajax (PartialView for your PersonnelDegree) and attach it to your View with js.
Advantage: You can use all ViewModel advandages (DataAnnotations) plus you can add some tricki logic in your CRUD controller methods if you need. Also it's the most easy maintainable solution if your Project is big and have losg life cicle.
Disadvantage: You should learn how to init client side validation when markup comes from ajax call. Also usually this approach force you to write a lot of code.
I prefer last option if i have a time for this.
you can add items for PersonnelDegree using partial views.
for adding multy PersonnelDegrees in this View you need to create objects in the controller
Personnel pers = new Personnel();
PersonnelDegrees pr_obj = new PersonnelDegrees ();
ind_obj.PersonnelDegrees .Add(pr_obj );
PersonnelDegrees pr_obj1 = new PersonnelDegrees ();
ind_obj.PersonnelDegrees .Add(pr_obj1 );

Resources