Set Dllimport Attribute Dynamically

MVC 5 Dynamic Rows with BeginCollectionItem

Updated Answer - The original code is NOT true to 'dynamic', however it allows for everything I needed to do within the question parameters.

Initially, I couldn't get Stephen's BCI suggestion in the question comments working, since then I have and it's brilliant. The code below in the updated section will work if you copy + paste, but you will need to either manually download BCI from GIT or use PM> Install-Package BeginCollectionItem with Package Manager Console in Visual Studio.

I had some issue with various points using BCI due to the complexity and not having done MVC before - here is more information on dealing with accessing class.property(type class).property(type class).property.

Original answer - I've gone with a more clear example below than in my question which quickly got too confusing.

Using two partial views, one for the list of employees and another for the creation of a new employee all contained within the viewmodel of companyemployee which contains an object of company and a list of employee objects. This way multiple employees can be added, edited or deleted from the list.

Hopefully this answer will help anyone looking for something similar, this should provide enough code to get it working and at the very least push you in the right direction.

I've left out my context and initialiser classes as they're only true to code first, if needed I can add them.

Thanks to all who helped.

Models - CompanyEmployee being the view model

public class Company
{
[Key]
public int id { get; set; }
[Required]
public string name { get; set; }
}

public class Employee
{
[Key]
public int id { get; set; }
[Required]
public string name { get; set; }
[Required]
public string jobtitle { get; set; }
[Required]
public string number { get; set; }
[Required]
public string address { get; set; }
}

public class CompanyEmployee
{
public Company company { get; set; }
public List<Employee> employees { get; set; }
}

Index

@model MMV.Models.CompanyEmployee
@{
ViewBag.Title = "Index";
Layout = "~/Views/Shared/_Layout.cshtml";
}

<h2>Index</h2>
<fieldset>
<legend>Company</legend>
<table class="table">
<tr>
<th>@Html.LabelFor(m => m.company.name)</th>
</tr>
<tr>
<td>@Html.EditorFor(m => m.company.name)</td>
</tr>
</table>
</fieldset>
<fieldset>
<legend>Employees</legend>

@{Html.RenderPartial("_employeeList", Model.employees);}

</fieldset>
<fieldset>
@{Html.RenderPartial("_employee", new MMV.Models.Employee());}
</fieldset>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Submit" class="btn btn-default" />
</div>
</div>

PartialView of List of Employees

@model IEnumerable<MMV.Models.Employee>
@using (Html.BeginForm("Employees"))
{
<table class="table">
<tr>
<th>
Name
</th>
<th>
Job Title
</th>
<th>
Number
</th>
<th>
Address
</th>
<th></th>
</tr>
@foreach (var emp in Model)
{
<tr>
<td>
@Html.DisplayFor(modelItem => emp.name)
</td>
<td>
@Html.DisplayFor(modelItem => emp.jobtitle)
</td>
<td>
@Html.DisplayFor(modelItem => emp.number)
</td>
<td>
@Html.DisplayFor(modelItem => emp.address)
</td>
<td>
<input type="submit" formaction="/Employees/Edit/@emp.id" value="Edit"/>
<input type="submit"formaction="/Employees/Delete/@emp.id" value="Remove"/>
</td>
</tr>
}
</table>
}

Partial View Create Employee

@model MMV.Models.Employee

@using (Html.BeginForm("Create","Employees"))
{
<table class="table">

@Html.ValidationSummary(true, "", new { @class = "text-danger" })

<tr>
<td>
@Html.EditorFor(model => model.name)
@Html.ValidationMessageFor(model => model.name, "", new { @class = "text-danger" })
</td>
<td>
@Html.EditorFor(model => model.jobtitle)
@Html.ValidationMessageFor(model => model.jobtitle)
</td>
<td>
@Html.EditorFor(model => model.number)
@Html.ValidationMessageFor(model => model.number, "", new { @class = "text-danger" })
</td>
<td>
@Html.EditorFor(model => model.address)
@Html.ValidationMessageFor(model => model.address, "", new { @class = "text-danger" })
</td>
</tr>
</table>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
}

Controller - I used multiple but you can put them all in one

public class CompanyEmployeeController : Controller
{
private MyContext db = new MyContext();

// GET: CompanyEmployee
public ActionResult Index()
{
var newCompanyEmployee = new CompanyEmployee();
newCompanyEmployee.employees = db.EmployeeContext.ToList();
return View(newCompanyEmployee);
}

[HttpPost, ActionName("Delete")]
public ActionResult DeleteConfirmed(int id)
{
Employee employee = db.EmployeeContext.Find(id);
db.EmployeeContext.Remove(employee);
db.SaveChanges();
return RedirectToAction("Index", "CompanyEmployee");
}

[HttpPost]
public ActionResult Create([Bind(Include = "id,name,jobtitle,number,address")] Employee employee)
{
if (ModelState.IsValid)
{
db.EmployeeContext.Add(employee);
db.SaveChanges();
return RedirectToAction("Index", "CompanyEmployee");
}

return View(employee);
}
}

Updated Code - using BeginCollectionItem - dynamic add/delete

Student Partial

@model UsefulCode.Models.Person
<div class="editorRow">
@using (Html.BeginCollectionItem("students"))
{
<div class="ui-grid-c ui-responsive">
<div class="ui-block-a">
<span>
@Html.TextBoxFor(m => m.firstName)
</span>
</div>
<div class="ui-block-b">
<span>
@Html.TextBoxFor(m => m.lastName)
</span>
</div>
<div class="ui-block-c">
<span>
<span class="dltBtn">
<a href="#" class="deleteRow">X</a>
</span>
</span>
</div>
</div>
}
</div>

Teacher Partial

@model UsefulCode.Models.Person
<div class="editorRow">
@using (Html.BeginCollectionItem("teachers"))
{
<div class="ui-grid-c ui-responsive">
<div class="ui-block-a">
<span>
@Html.TextBoxFor(m => m.firstName)
</span>
</div>
<div class="ui-block-b">
<span>
@Html.TextBoxFor(m => m.lastName)
</span>
</div>
<div class="ui-block-c">
<span>
<span class="dltBtn">
<a href="#" class="deleteRow">X</a>
</span>
</span>
</div>
</div>
}
</div>

Register Controller

public ActionResult Index()
{
var register = new Register
{
students = new List<Person>
{
new Person { firstName = "", lastName = "" }
},
teachers = new List<Person>
{
new Person { lastName = "", firstName = "" }
}
};

return View(register);
}

Register and Person Model

public class Register
{
public int id { get; set; }
public List<Person> teachers { get; set; }
public List<Person> students { get; set; }
}

public class Person
{
public int id { get; set; }
public string firstName { get; set; }
public string lastName { get; set; }
}

Index

@{
Layout = "~/Views/Shared/_Layout.cshtml";
}
@model UsefulCode.Models.Register
<div id="studentList">
@using (Html.BeginForm())
{
<div id="editorRowsStudents">
@foreach (var item in Model.students)
{
@Html.Partial("StudentView", item)
}
</div>
@Html.ActionLink("Add", "StudentManager", null, new { id = "addItemStudents", @class = "button" });
}
</div>

<div id="teacherList">
@using (Html.BeginForm())
{
<div id="editorRowsTeachers">
@foreach (var item in Model.teachers)
{
@Html.Partial("TeacherView", item)
}
</div>
@Html.ActionLink("Add", "TeacherManager", null, new { id = "addItemTeachers", @class = "button" });
}
</div>

@section scripts {
<script type="text/javascript">
$(function () {
$('#addItemStudents').on('click', function () {
$.ajax({
url: '@Url.Action("StudentManager")',
cache: false,
success: function (html) { $("#editorRowsStudents").append(html); }
});
return false;
});
$('#editorRowsStudents').on('click', '.deleteRow', function () {
$(this).closest('.editorRow').remove();
});
$('#addItemTeachers').on('click', function () {
$.ajax({
url: '@Url.Action("TeacherManager")',
cache: false,
success: function (html) { $("#editorRowsTeachers").append(html); }
});
return false;
});
$('#editorRowsTeachers').on('click', '.deleteRow', function () {
$(this).closest('.editorRow').remove();
});
});
</script>
}

StudentManager Action:

public PartialViewResult StudentManager()
{
return PartialView(new Person());
}

MVC 5 BeginCollectionItem with Partial CRUD

You do not need to use BeginCollectionItem in order to achieve this. From having to look into it myself and trying to use it for a similar issue, it appears it was created for problems of this nature with earlier versions of MVC.

Use Partial Views to display and update the list. One partial view to display and iterate through the list of objects, and another to create a new object which upon post back to update the list will show the newly created object in the partial view with the list.

I posted a similar question on here which should solve your issue, click here

Hope this helps.

Update
The reason your delete doesn't work is because you can't call JS from Partial View, put it in the main view (@section Script). Also I think you got a bit muddled with your class and id keywords in your divs, have a look below.

So you should have:

Partial View

@model MvcTest.Models.Employee
@using (Html.BeginCollectionItem("Employees"))
{
<div id="employeeRow" class="employeeRow">
@Html.LabelFor(m => m.Name)
@Html.EditorFor(m => m.Name)

@Html.LabelFor(m => m.Telephone)
@Html.EditorFor(m => m.Telephone)

@Html.LabelFor(m => m.Mobile)
@Html.EditorFor(m => m.Mobile)

@Html.LabelFor(m => m.JobTitle)
@Html.EditorFor(m => m.JobTitle)

<a href="#" id="deleteRow" class="deleteRow" onclick="deleteFunction()">Delete</a>
</div>
}

Main View

    @model MvcTest.Models.Company
@{
ViewBag.Title = "Index";
Layout = "~/Views/Shared/_Layout.cshtml";
}

<h2>Company</h2>
<div>
@Html.LabelFor(m => m.Name)
@Html.EditorFor(m => m.Name)
</div>
<fieldset>
<legend>Employees</legend>
<div id="new-Employee">
@foreach (var Employee in Model.Employees)
{
Html.RenderPartial("_Employee", Employee);
}
</div>
<div>
<input type="button" id="addemployee" name="addemployee" value="Add Employee"/>
<br/>
</div>
<br/>
@section Scripts
{
<script type="text/javascript">
$('#addemployee').on('click', function () {
$.ajax({
async: false,
url: '/Company/AddNewEmployee'
}).success(function (partialView) {
$('#new-Employee').append(partialView);
});
});

$("#deleteRow").live("click", function () {
$(this).parents("#employeeRow:first").remove();
return false;
});
</script>
}
</fieldset>
<div>
<input type="submit" value="Submit" />
</div>

MVC BeginCollectionItem

Your use of Html.BeginCollectionItem("Section") perpends Section[xxxx] to the name attribute (where xxxx is a Guid) so that you generating inputs with

<input name="Section[xxxx].SectionId" .... />

which posts back to a model containing a collection property named Sections.

Since you already have a model with that property, you can change the POST method to

[HttpPost]
public ActionResult SOPTopTemplateBuilder(SOPTopTemplateBuilderViewModel soptop)

other options include

  1. Using your existing POST method and omitting the "Section" prefix
    using Html.BeginCollectionItem("") which will generate
    name="[xxxx].SectionId"
  2. Changing the POST method signature to public ActionResult
    SOPTopTemplateBuilder(IEnumerable<Section> section)
    (where the
    name of the parameter matches the name of the prefix)
  3. Using a BindAttribute to 'strip' the prefix from the form values
    public ActionResult SOPTopTemplateBuilder([Bind(Prefix = "Section")]IEnumerable<Section> soptop)

As a side note, your editing data, so you should always use a view model (say public class SectionViewModel) rather than using data models in your view. - What is ViewModel in MVC?

BeginCollectionItem partial within partial not behaving correctly

Question 1 Solution:

The issue with Q1 was that the property value I was assigning in the controller wasn't being parsed back from the form post, because the property wasn't there.

Added a hidden field for the property to rectify the pesky null.

<div class="ui-block-a">
<span>
@Html.HiddenFor(m => m.allocationType)
@Html.TextBoxFor(m => m.subnet, new { @class = "checkFiller" })
</span>
</div>

Question 2 Solution:

The issues that I was facing with the GUID of the first model being attached as the prefix to the second model was largely due to how I was sending data using AJAX to the controller action method.

The code snippets shown below fix the issues and display the correctly bound GUIDs.

name="pa_ipv4s[f7d8d024-5bb6-451d-87e3-fd3e3b8c1bba].requestedIps[d5c08a43-f65e-46d1-b224-148225599edc].subnet" is now being shown on the dynamically created model properties, not just the initially created.

When running in debug in visual studio and hovering over the model, digging down into the data shows the correct counts of the model lists.

Controller ActionMethod:

public ActionResult ExistingManager(string containerPrefix)
{
ViewData["ContainerPrefix"] = containerPrefix;
return PartialView("ExistingIpView", new IpAllocation { allocationType = "Existing" });
}

AJAX GET Method calling Controller ActionMethod:

$('#addItemEIpM').on('click', function () {
$.ajax({
url: '@Url.Action("ExistingManager")',
cache: false,
data: 'containerPrefix=' + $('#addItemEIpM').data('containerprefix'),
success: function (html) {
$("#editorRowsEIpM").append(html);
}
});
return false;
});

A Partial View passing a collection using the Html.BeginCollectionItem helper

First start by creating a view model to represent what you want to edit. I'm assuming cashAmount is a monetary value, so therefore should be a decimal (add other validation and display attributes as required)

public class CashRecipientVM
{
public int? ID { get; set; }
public decimal Amount { get; set; }
[Required(ErrorMessage = "Please enter the name of the recipient")]
public string Recipient { get; set; }
}

Then create a partial view (say) _Recipient.cshtml

@model CashRecipientVM
<div class="recipient">
@using (Html.BeginCollectionItem("recipients"))
{
@Html.HiddenFor(m => m.ID, new { @class="id" })
@Html.LabelFor(m => m.Recipient)
@Html.TextBoxFor(m => m.Recipient)
@Html.ValidationMesssageFor(m => m.Recipient)
@Html.LabelFor(m => m.Amount)
@Html.TextBoxFor(m => m.Amount)
@Html.ValidationMesssageFor(m => m.Amount)
<button type="button" class="delete">Delete</button>
}
</div>

and a method to return that partial

public PartialViewResult Recipient()
{
return PartialView("_Recipient", new CashRecipientVM());
}

Then your main GET method will be

public ActionResult Create()
{
List<CashRecipientVM> model = new List<CashRecipientVM>();
.... // add any existing objects that your editing
return View(model);
}

and its view will be

@model IEnumerable<CashRecipientVM>
@using (Html.BeginForm())
{
<div id="recipients">
foreach(var recipient in Model)
{
@Html.Partial("_Recipient", recipient)
}
</div>
<button id="add" type="button">Add</button>
<input type="submit" value="Save" />
}

and will include a script to add the html for a new CashRecipientVM

var url = '@Url.Action("Recipient")';
var form = $('form');
var recipients = $('#recipients');
$('#add').click(function() {
$.get(url, function(response) {
recipients.append(response);
// Reparse the validator for client side validation
form.data('validator', null);
$.validator.unobtrusive.parse(form);
});
});

and the script to delete an item

$('.delete').click(function() {
var container = $(this).closest('.recipient');
var id = container.find('.id').val();
if (id) {
// make ajax post to delete item
$.post(yourDeleteUrl, { id: id }, function(result) {
container.remove();
}.fail(function (result) {
// Oops, something went wrong (display error message?)
}
} else {
// It never existed, so just remove the container
container.remove();
}
});

And the form will post back to

public ActionResult Create(IEnumerable<CashRecipientVM> recipients)


Related Topics



Leave a reply



Submit