3

I am trying to bind all my model data at once through a form submission in MVC 5 using an edited version of BeginCollectionItem as discussed in Joe Steven's blog here.

The model, Company, has a List<Pa_Ipv4>, the class Pa_Ipv4 in turn has a List<IpAllocation>, I want to access and save to the database all the properties of the IpAllocation in each Pa_Ipv4.

IE: Model.pa_ipv4s[x].requestedIps[x].subnet

The main page is using model Company, which has a partial accepting Pa_Ipv4, which has a partial accepting IpAllocation.

Question 1: In my controller, I'm setting a string property for the first item in the list (requestedIp), but when I submit and postback, the property (allocationType) is null, this property needs to be hard coded as it's for internal use within the DB - why is this being reset?

Reason: The property isn't in the post method, as such what is initially declared is discarded as it's not within the end post.

Possible Solution: Use a hidden property within the form so that it is present when the form is posted and the user cannot access the property.

Question 2: BeginCollectionItem is naming attributes appropriately, IE: pa_ipv4s[8e075d50-a5fb-436f-9cef-85abfb6910e3].requestedIps[b693b83c-b6b1-4c42-b983-4d058e766d4c].subnet, but only the initial model, it's then ignoring any others created, what have I done wrong?

Reason: The GUID needed for a prefix generated by the Pa_Ipv4 sections BeginCollectionItem is not able to be accessed by the IpAllocation BeginCollectionItem, as such only the initial content has the correct prefixes, anything added hereafter misses the necessary prefix.

Another potential solution is essentially the same concept, but instead of using a div, use html data attribute instead so that it's accessible.

I think both of the issues I'm experiencing are to do with how I've set my controller up, but I've included the Views and Model below as well. The model contains all the properties, lots of these have been removed in my views to save space as these are not causing the issue.

Create

@model Company

@{
    Layout = "~/Views/Shared/_Layout.cshtml";   
}
@using (Html.BeginForm())
{
    <div class="jumboservice">
        <div data-role="page">
            <div data-role="header">
                <h2>PA IPv4 Request Form</h2>
            </div>
            <div class="ui-content" data-role="main">
                <h3>Company Details</h3>
                <div class="ui-grid-c ui-responsive">
                    <div class="ui-block-a">
                        <p class="lblStyle">Company Name</p>
                        <span>
                            @Html.EditorFor(m => m.name)
                            @Html.ValidationMessageFor(m => m.name)
                        </span>
                    </div>
                </div>
            </div>
            <br />
                @foreach (var i in Model.pa_ipv4s)
                {
                    @Html.Partial("Pa_IPv4View", i)
                }
            <br />
            <div data-role="main" class="ui-content">
                <div data-role="controlgroup" data-type="horizontal">
                    <input type="submit" class="ui-btn" value="Create" />
                </div>
            </div>
        </div>
    </div>
}
<script type="text/javascript">
    $(function () {
            $('#addItemRIpM').on('click', function () {
                $.ajax({
                    url: '@Url.Action("RequestedManager")',
                    cache: false,
                    success: function (html) { $("#editorRowsRIpM").append(html); }
                });
                return false;
            });
            $('#editorRowsRIpM').on('click', '.deleteRow', function () {
                $(this).closest('.editorRow').remove();
            });
        });
</script>

Pa_Ipv4 Partial

@model Pa_Ipv4

    @using (HtmlHelpers.BeginCollectionItem.HtmlPrefixScopeExtensions.BeginCollectionItem(Html,"pa_ipv4s"))
{
    @Html.AntiForgeryToken()
    <div class="ui-grid-c ui-responsive">
        <div class="ui-block-a">
            <p class="lblStyle">Subnet</p>
        </div>
        <div class="ui-block-b">
            <p class="lblStyle">Size(CIDR)</p>
        </div>
        <div class="ui-block-c">
            <p class="lblStyle">Mask</p>
        </div>
        <div class="ui-block-d">
        </div>
    </div>
    @*Request IP Address Space List*@
    <div id="editorRowsRIpM">
        @foreach (var item in Model.requestedIps)
        {
            @Html.Partial("RequestedIpView", item)
        }
    </div>
    @Html.ActionLink("Add", "RequestedManager", null, new { id = "addItemRIpM", @class = "button" })
}

RequestedIp Partial

@model IpAllocation

<div class="editorRow">
    @using (HtmlHelpers.BeginCollectionItem.HtmlPrefixScopeExtensions.BeginCollectionItem(Html, "requestedIps"))
    {
        <div class="ui-grid-c ui-responsive">
            <div class="ui-block-a">
                <span>
                    @Html.TextBoxFor(m => m.subnet)
                </span>
            </div>
            <div class="ui-block-b">
                <span>
                    @Html.TextBoxFor(m => m.cidr)
                </span>
            </div>
            <div class="ui-block-c">
                <span>
                    @Html.TextBoxFor(m => m.mask)
                    <span class="dltBtn">
                        <a href="#" class="deleteRow">Remove</a>
                    </span>
                </span>
            </div>
        </div>
    }
</div>

Controller

public ActionResult Create()
        {
            var cmp = new Company();
            cmp.contacts = new List<Contact>
            {
                new Contact { email = "", name = "", telephone = "" }
            };
            cmp.pa_ipv4s = new List<Pa_Ipv4>
            {
                new Pa_Ipv4
                { 
                    ipType = "Pa_IPv4", registedAddress = false, existingNotes = "",
                    numberOfAddresses = 0, returnedAddressSpace = false, additionalInformation = "",
                    requestedIps = new List<IpAllocation>
                    {
                        new IpAllocation { allocationType = "Requested", cidr = "", mask = "", subnet  = "" } // allocationType is null in cmp in the Create[HttpPost]
                    }
                }
            };

            return View(cmp);
        }

        public ActionResult Pa_IPv4Manager()
        {
            return PartialView("Pa_IPv4View", new Pa_Ipv4());
        }
        public ActionResult RequestedManager()
        {
            return PartialView("RequestedIpView", new IpAllocation { allocationType = "Requested" }); // allocationType is null in cmp in the Create[HttpPost]
        }

        // POST: Pa_Ipv4/Create
        [HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult Create(Company cmp) //only one requestedIps count regardless of how many add
        {
            if (ModelState.IsValid)
            {
                db.companys.Add(cmp);
                db.SaveChanges();
                return RedirectToAction("Index");
            }

Model

[Table("Ipv_Base")]
public class Ipv_Base
{
    [Key]
    public int ipv_baseId { get; set; }
    public int companyId { get; set; }
    [ForeignKey("companyId")]
    public Company company { get; set; }
    public string ipType { get; set; }
    [Required]
    public bool registedAddress { get; set; }
    [Required]
    [DataType(DataType.MultilineText)]
    public string existingNotes { get; set; }
    [Required]
    public int numberOfAddresses { get; set; }
    [Required]
    public bool returnedAddressSpace { get; set; }
    [DataType(DataType.MultilineText)]
    public string additionalInformation { get; set; }
    // navigation properties
    public virtual IList<IpAllocation> requestedIps { get; set; }
}
[Table("Company")]
public class Company
{
    [Key]
    public int companyId { get; set; }
    [Required]
    public string name { get; set; }
    [Required]
    public string telephone { get; set; }
    [Required]
    public string regNumber { get; set; }
    // navigation properties to keep track of the models that belong to the company
    public virtual IList<Pa_Ipv4> pa_ipv4s { get; set; }

}
[Table("IpAllocation")]
public class IpAllocation
{
    [Key]
    public int ipAllocationId { get; set; }
    public int ipv_BaseId { get; set; }
    [ForeignKey("ipv_BaseId")]
    public Ipv_Base ipv_Base { get; set; }
    [Required]
    public string allocationType { get; set; }
    [Required]
    public string subnet { get; set; }
    [Required]
    public string cidr { get; set; }
    [Required]
    public string mask { get; set; }
}
public class Pa_Ipv4 : Ipv_Base
{
    public Pa_Ipv4()
    {
        ipType = "pa_ipv4";
    }
}
PurpleSmurph
  • 2,055
  • 3
  • 32
  • 52
  • How are you actually creating the new items? And I assume `pa_ipv4[sxx].equipmentDetails[xx].type` is a typo? (your model does not contain a property named `equipmentDetails` which contains a property named `type`) –  Sep 05 '15 at 12:52
  • Apologies, that was a typo, corrected now. Sorry I don't understand what you mean by how am I creating the items, through the controller action methods, as well as the Create view initially. – PurpleSmurph Sep 05 '15 at 13:14
  • You said the initial items are OK, but that its _ ignoring any others created_ - I assume you are using ajax to dynamically add new items? In which case, show your scripts. –  Sep 05 '15 at 13:18
  • Oh right, sorry, yes - displayed in the Create View - calling the controller action method. The Pa_Ipv4 Partial doesn't have a button to add more, but I suspect this will become necessary later so would like to get it working and understand it. – PurpleSmurph Sep 05 '15 at 13:22
  • And as far as Question 1 goes, which property are you referring to? –  Sep 05 '15 at 13:23
  • Oops, need to update the Q, I'm referring to the property in the controller `allocationType = "Requested"` - these are null when stepping through code. – PurpleSmurph Sep 05 '15 at 13:24
  • Cant see any form controls in the `RequestedIp.cshtml` partial for property `RequestedIp` - only for `subnet`, `cidr` and `mask` so it will be null in the POST method –  Sep 05 '15 at 13:28
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/88880/discussion-between-purplesmurph-and-stephen-muecke). – PurpleSmurph Sep 05 '15 at 13:30
  • @SelrekJohn - I'm working on sorting this on a project and will post relevant code samples and explanation, no need for a bounty. – PurpleSmurph Sep 07 '15 at 16:49

1 Answers1

2

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;
        });
PurpleSmurph
  • 2,055
  • 3
  • 32
  • 52
  • Will try this out on my project (a rebuild/redesign of the initial blog). – SelrekJohn Sep 08 '15 at 15:58
  • You haven't question 1 :) –  Sep 09 '15 at 02:22
  • Quite right! It's because I'd forgotten to do it in my project (needed sleep) updated answer now :) – PurpleSmurph Sep 09 '15 at 08:36
  • Can you explain how you're using the `BeginCollectionItem` as mine doesn't look like that and the later added rows aren't being prefixed. – SelrekJohn Sep 11 '15 at 08:07
  • 1
    @SelrekJohn Guessing you mean the `(HtmlHelpers.BeginCollectionItem.HtmlPrefixScopeExtensions.BeginCollectionItem(Html, "requestedIps")`, the reason it isn't `Html.BeginItemCollection` is because I downloaded the project from GIT (I think) and added the prefix code as shown in the blog, then compiled it to a dll and referenced it in my project. – PurpleSmurph Sep 11 '15 at 08:33