1

I have been searching all over the internet about MVC many-to-many relationships (and have read every thing about it here too), but I can't seem to solve my problem.

I have a many-to-many relationship between a client and a standard service as seen below.

public class Client
{
    public Client()
    {
        CustomServices = new HashSet<CustomService>();
        StandardServices = new HashSet<StandardService>();
    }

    public int ClientID { get; set; }

    public string Name { get; set; }

    public Nullable<int> Users { get; set; }
    public Nullable<bool> FH { get; set; }
    public Nullable<bool> PH { get; set; }

    public bool Hosted { get; set; }
    public string ShortName { get; set; }

    public Nullable<short> AttachmentLimit { get; set; }

    public virtual ICollection<CustomService> CustomServices { get; set; }
    public virtual ICollection<StandardService> StandardServices { get; set; }

And the standard service.

public class StandardService
{
    public StandardService()
    {
        Clients = new HashSet<Client>();
    }

    public int StandardServiceID { get; set; }
    public string StandardServiceName { get; set; }
    public string LogOnAs { get; set; }
    public int ServerID { get; set; }

    public virtual Server Server { get; set; }
    public virtual ICollection<Client> Clients { get; set; }

When creating a new client, I would like to be able to select from a list of check boxes for standard services that the client uses. I am able to get them to show up in the create view using the viewbag, but would like to be able to use a viewmodel.

I would also like to know how to get this to post with default model binding.

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "ClientID,Name,Users,FH,Hosted,PH,ShortName,AttachmentLimit")] Client client)
{
    if (ModelState.IsValid)
    {
        db.Clients.Add(client);
        db.SaveChanges();
        return RedirectToAction("Index");
    }
    return View(client);
}

Here is the create method for the Client Controller.

public ActionResult Create()
{
    ViewBag.StandardServices = db.StandardServices.ToList();
    return View();
}

Part of my create view is below (that does the check boxes).

<div class="form-group">
    @Html.LabelFor(model => model.StandardServices, htmlAttributes: new { @class = "control-label col-md-2" })
    <div class="col-md-10">
        @foreach (var StandardService in ViewBag.StandardServices)
        {   
            <div class="checkbox">
                <input class="checkbox" type="checkbox" name="StandardServiceID" id="StandardServiceID" value="@StandardService.StandardServiceID"/>
                @StandardService.StandardServiceName<br>
            </div>
        }
    </div>
</div>

So how could I go about doing this? I have tried many things, but don't want to go though listing them all. None worked suffice it to say. Any help at all would be appreciated. I don't need the full solution, just some advice on where to go. Thanks!

Based on a comment below; and viewing the link provided, I think I have part of the solution. I just need to work on the post now. Here is what I have for the view model.

public class CreateClientVM
{
    public CreateClientVM()
    {
        ClientStandardServices = new List<StandardService>();
    }
    public int ClientID { get; set; }
    public string Name { get; set; }
    public Nullable<int> Users { get; set; }
    public Nullable<bool> FH { get; set; }
    public Nullable<bool> PH { get; set; }
    public bool Hosted { get; set; }
    public string ShortName { get; set; }
    public Nullable<short> AttachmentLimit { get; set; }
    public List<StandardService> ClientStandardServices { get; set; }
    public List<StandardServiceVM> StandardServices { get; set; }
}

public class StandardServiceVM
{
    public int StandardServiceID { get; set; }
    public string StandardServiceName { get; set; }
    public bool IsSelected { get; set; }
}

And below is my Client Controller create method.

public ActionResult Create()
{
    //ViewBag.StandardServices = db.StandardServices.ToList();
    CreateClientVM clientVM = new CreateClientVM();
    var ListVM = new List<StandardServiceVM>();
    var standardServices = db.StandardServices.ToList();
    foreach(var s in standardServices)
    {
        var viewModel = new StandardServiceVM
        {
            StandardServiceID = s.StandardServiceID,
            StandardServiceName = s.StandardServiceName,
            IsSelected = false
        };
        ListVM.Add(viewModel);
    }
    clientVM.StandardServices = ListVM;
    return View(clientVM);
}

And finally part of my create view.

<div class="form-group">
    @Html.HiddenFor(m => m.ClientID)
    @Html.LabelFor(m => m.StandardServices, htmlAttributes: new { @class = "control-label col-md-2" })
    <div class="col-md-10">
        @for (int i = 0; i < Model.StandardServices.Count; i++)
        {
            <div class="checkbox">
                @Html.HiddenFor(m => m.StandardServices[i].StandardServiceID)
                @Html.CheckBoxFor(m => m.StandardServices[i].IsSelected)
                @Html.LabelFor(m => m.StandardServices[i].IsSelected, Model.StandardServices[i].StandardServiceName)
            </div>
        }
    </div>
 </div>

Here is the html that gets rendered when viewing the page source. I think this looks right, but I'm not entirely sure.

<div class="form-group">
    <input data-val="true" data-val-number="The field ClientID must be a number." data-val-required="The ClientID field is required." id="ClientID" name="ClientID" type="hidden" value="0" />
    <label class="control-label col-md-2" for="StandardServices">StandardServices</label>
    <div class="col-md-10">
        <div class="checkbox">
            <input data-val="true" data-val-number="The field StandardServiceID must be a number." data-val-required="The StandardServiceID field is required." id="StandardServices_0__StandardServiceID" name="StandardServices[0].StandardServiceID" type="hidden" value="1" />
            <input data-val="true" data-val-required="The IsSelected field is required." id="StandardServices_0__IsSelected" name="StandardServices[0].IsSelected" type="checkbox" value="true" />
             <input name="StandardServices[0].IsSelected" type="hidden" value="false" />
             <label for="StandardServices_0__IsSelected">FHEmailService</label>
         </div>
         <div class="checkbox">
             <input data-val="true" data-val-number="The field StandardServiceID must be a number." data-val-required="The StandardServiceID field is required." id="StandardServices_1__StandardServiceID" name="StandardServices[1].StandardServiceID" type="hidden" value="2" />
             <input data-val="true" data-val-required="The IsSelected field is required." id="StandardServices_1__IsSelected" name="StandardServices[1].IsSelected" type="checkbox" value="true" />
             <input name="StandardServices[1].IsSelected" type="hidden" value="false" />
             <label for="StandardServices_1__IsSelected">FHScheduledReports</label>
         </div>
     </div>
 </div>

Can anyone comment if this looks okay? I believe it does. Now I just need to figure out how to post it.

Nathan Hughes
  • 94,330
  • 19
  • 181
  • 276
Chris Kohl
  • 19
  • 3
  • Refer [this answer](http://stackoverflow.com/questions/29542107/pass-list-of-checkboxes-into-view-and-pull-out-ienumerable/29554416#29554416) for an example of the view models and how to generate the view. –  Jan 28 '16 at 04:07
  • Thanks Stephen. I have been looking at the link you provided and it has been helpful. I am going to edit my post to reflect what I've done. – Chris Kohl Jan 29 '16 at 14:20
  • At first glance, it looks fine (although `@Html.LabelFor(m => m.StandardServices ...)` is a bit pointless - you don't have a control for `StandardServices` so it will not set focus to anything). Your POST method just needs to be `[HttpPost] public ActionResult Create(CreateClientVM)` and everything will be bound correctly when you submit. –  Jan 30 '16 at 00:10

0 Answers0