Brett JB Brett JB - 3 months ago 22
ASP.NET (C#) Question

Get data from an Ilist model to a controller

Ok, please forgive if this is newbie error.. I have been looking here at various solutions but still can't get anywhere.
I need to be able to associate a User (Id) with a Company. I have linked to SelectCompInst and have passed in the user Id.
I have a general bool flag on the companyinstitution model which I set up in the get method. The flag is correctly shown in the view. I can change the fact that its a checkbox if needed.. but I just want to put a button, link that ideally causes a postback in the list in order to allow the operator to select a company to be attached to this user.. something I am sure is done all the time.

I have seen all kinds of references to this sort of problem saying I might have to use an Edit Template? but I never find it explained why? Surely it can't be that complicated can it?
Here's my model:

[Key]
public int Id { get; set; }
public DateTime RecordCreateDate { get; set; }
[ForeignKey("Address")]
public int AddressId { get; set; }
public string Name { get; set; }
[ForeignKey("PrimaryContact")]
public string PrimaryContactId { get; set; }

public virtual Address Address { get; set; }
public virtual ApplicationUser PrimaryContact { get; set; }

[NotMapped]
public bool UserFlag { get; set; } //Bool to define if user is in this activity type when searching


Here's the View:

@model IList<Dune.Models.CompanyInstitution>

@{
ViewBag.Title = "Index";
string staffid = ViewBag.StaffId;
}

<h2>Select Company/Institution for user @ViewBag.UserName</h2>
@using (Html.BeginForm("SelectCompInst","UserAdmin",FormMethod.Post, new {id ="compselect"}))
{
@Html.AntiForgeryToken()
<p>
@Html.ActionLink("Create New", "Create")
</p>
<table class="table">
<tr>
<th>
@Html.DisplayNameFor(model => model[0].Name)
</th>
<th>
@Html.DisplayNameFor(model => model[0].Address)
</th>
<th>
@Html.DisplayNameFor(model => model[0].PrimaryContact)
</th>
<th>
@Html.DisplayNameFor(model => model[0].RecordCreateDate)
</th>
<th></th>
</tr>

@for (int i = 0; i < Model.Count(); i++)
{
<tr>
<td>
@Html.DisplayFor(m => m.ElementAt(i).Name)
</td>
<td>
@{
var AddressString = Model.ElementAt(i).Address.AddressAsString();
}
@Html.DisplayFor(modelItem => AddressString)
</td>
<td>
@{ var NameString = "Not Allocated";
if (Model.ElementAt(i).PrimaryContact != null)
{
NameString = Model.ElementAt(i).PrimaryContact.FullNameNoMidString();
}
}
@Html.DisplayFor(modelItem => NameString)
</td>
<td>
@Html.DisplayFor(modelItem => modelItem.ElementAt(i).RecordCreateDate)
</td>
<td>
@Html.EditorFor(modelItem => modelItem.ElementAt(i).UserFlag)
</td>
<td>
@* Ideally I would like to put a simple 'Select' Link here and also post back the UserId
contained in Viewbag.StaffId.. I figure I can add that to the view model though
if I can get anything at all*@
</td>
</tr>
}
</table>
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Select" />
</div>
}


.. and here's the controller methods:

//
// GET:
public async Task<ActionResult> SelectCompInst(string StaffId)
{
ApplicationUser user = await UserManager.FindByIdAsync(StaffId);
ViewBag.UserName = "Error User not found!";
if (user != null)
{
ViewBag.UserName = user.FullNameString();
ViewBag.StaffId = StaffId;
}
var companysInstitutions = db.CompanysInstitutions.Include(c => c.Address).Include(c => c.PrimaryContact);
// Walk the list and set the flag if this user is already mapped to this institution.
foreach (CompanyInstitution comp in companysInstitutions)
{
if (user.CompanyInstitutionStaffMember.CompanyInstituteId == comp.Id)
{
comp.UserFlag = true;
}
else
{
comp.UserFlag = false;
}
}
IList<CompanyInstitution> companys = await companysInstitutions.ToListAsync();
return View(companys);
}

//
// POST:
[HttpPost, ActionName ("SelectCompInst")]
[ValidateAntiForgeryToken]
public async Task<ActionResult> SelectComp(IList<CompanyInstitution> Company )
{
//
// Code removed because it doesnt get this far with any data :)
//
ModelState.AddModelError("", "Something failed.");
return View();
}


I have looked at the post back using fiddler and am getting nothing at all.. although the Post function is being hit. The IList Company is null...
I am sure this boils down to a fundamental misunderstanding on my part but having struggled with it for 2 days now.. have to throw myself on the mercy of the wiser community :)

I would like to put a link against each row in the table and simply pass back an id for the row and the user id.. Thanks.

Further edit after Stephens comment..
The scenario is in the editing of a User. A user can have a CompanyInstituion Id as part of its model. I wish to present a list of companies to the operator (not necessarily the User) to allow them to attach a company id to that user. So in concept its dead simple - present a list of companies to pick from. Press a 'select' link on the company line and return to the controller to sort it out. Hence I need to preserve the UserId in the Viewbag - yes I can create a viewmodel if needed but getting the company id alone would be a start)..
So that's it.. present the list, select one and return it.. I don't really want to use checkboxes anyway.. I kind of 'got there' after trying some things. I originally had the submit button in the loop before I added the checkbox. I did try putting the Company Id on the button but that didn't work either.

Answer

If the operator must select a single company that will be associated with the user, then it would make more sense your POST action to take the selected company id and the user id. That should be enough information for you to do the job:

[HttpPost, ActionName ("SelectCompInst")]
[ValidateAntiForgeryToken]
public async Task<ActionResult> SelectComp(int companyId, string staffId)
{
    ...
}

and then in your view you could have multiple forms and a submit button on each row of the table:

@model IList<Dune.Models.CompanyInstitution>

@{
    ViewBag.Title = "Index";
    string staffid = ViewBag.StaffId;
}

<h2>Select Company/Institution for user @ViewBag.UserName</h2>

<table class="table">
    <thead>
        <tr>
            <th>
                @Html.DisplayNameFor(model => model[0].Name)
            </th>
            <th>
                @Html.DisplayNameFor(model => model[0].Address)
            </th>
            <th>
                @Html.DisplayNameFor(model => model[0].PrimaryContact)
            </th>
            <th>
                @Html.DisplayNameFor(model => model[0].RecordCreateDate)
            </th>
            <th></th>
        </tr>
    </thead>
    <tbody>
        @for (var i = 0; i < Model.Count; i++)
        {
            <tr>
                <td>
                    @Html.DisplayFor(m => m[i].Name)
                </td>
                <td>
                    @{
                        var AddressString = Model[i].Address.AddressAsString();
                    }
                    @Html.DisplayFor(modelItem => AddressString)
                </td>
                <td>
                    @{ 
                        var NameString = "Not Allocated";
                        if (Model[i].PrimaryContact != null)
                        {
                            NameString = Model[i].PrimaryContact.FullNameNoMidString();
                        }
                    }
                    @Html.DisplayFor(modelItem => NameString)
                </td>
                <td>
                    @Html.DisplayFor(modelItem => modelItem[i].RecordCreateDate)
                </td>
                <td>
                    @Html.EditorFor(modelItem => modelItem[i].UserFlag)
                </td>
                <td>
                    @using (Html.BeginForm("SelectCompInst", "UserAdmin", new { companyId = Model[i].Id, staffId = staffid }, FormMethod.Post, new { @class = "compselect" }))
                    {
                        @Html.AntiForgeryToken()
                        <div class="col-md-offset-2 col-md-10">
                            <input type="submit" value="Select" />
                        </div>
                    }
                </td>
            </tr>
        }
    </tbody>
</table>

Basically we would have an HTML form on each row of the table which will pass the necessary information to the server:

@using (Html.BeginForm(
    actionName: "SelectCompInst", 
    controllerName: "UserAdmin", 
    routeValues: new { companyId = Model[i].Id, staffId = staffid }, 
    method: FormMethod.Post, 
    htmlAttributes: new { @class = "compselect" }))
{
    @Html.AntiForgeryToken()
    <div class="col-md-offset-2 col-md-10">
        <input type="submit" value="Select" />
    </div>
}