How do I process the items in my list boxes?

Michael Sterling
Michael Sterling used Ask the Experts™
on
I'm very new to MVC5 using razor. I've been able to stumble through some problems but I'm stuck now. Here's what I need to do:

  • Move items from one listbox control to another listbox control
  • Access/get the items from both list boxes at some point in time for processing

my model contains the following:

        public IEnumerable<SelectListItem> AllAvailableServices { get; set; }

        public IEnumerable<string> AllAvailableSelectedServices { get; set; }

		public IEnumerable<SelectListItem> AddedServices { get; set; }

		public IEnumerable<string> AddedSelectedServices { get; set; }

Open in new window


my view contains the following. I would like to access the items in the AddedServices list when the user clicks the "Submit" button

@using (Html.BeginForm("Category", "Manager", FormMethod.Post))
{
    <table class="contain">
        <tr>
            <td>
                <table class="nav">
                    <tr class="nav">
                        <td class="navLeft">
                            <input type="submit" value="Submit" name="btnSaveCategory" class="button" onclick="location.href = '@Url.Action("UpdateCategoryAndServices")'"/>
                            <input type="button" value="Back" name="btnBack" class="button" onclick="location.href = '@Url.Action("Back")'"/>
                        </td>
                    </tr>
                </table>
            </td>
        </tr>
.
.
.
                            <tr>
                                <td class="listAlign">
                                    @Html.ListBoxFor(x => x.AllAvailableSelectedServices, Model.AllAvailableServices, new { id = "lstbxAllServices", @class = "serviceListBox" })
                                </td>
                                <td colspan="1"></td>
                                <td class="listAlign">
                                    @Html.ListBoxFor(x => x.AddedSelectedServices, Model.AddedServices, new { id = "lstbxServicesAdded", @class = "serviceListBox" })
                                </td>
                            </tr>

Open in new window


and here is my method in my controller. my addedSelectedServices object is always empty/null even when I select items from the listbox. I don't want to have to select the items to be able to process them in my controller. If they are in the list, I want to be able to access/grab/get them:

		public ActionResult UpdateCategoryAndServices(ManagerViewModel manager, IEnumerable<string> addedSelectedServices)
		{
			manager = (ManagerViewModel)TempData["ManagerViewModel"];
			if (addedSelectedServices != null)
			{
				StringBuilder sb = new StringBuilder();
				sb.Append("You selected: " + string.Join(",", addedSelectedServices));
			}
			manager = (ManagerViewModel)TempData["ManagerViewModel"];
			TempData["ManagerViewModel"] = manager;
			return View("GroupDetails", manager);
		}

Open in new window


Here are a couple of resources I've tried to use:
https://www.youtube.com/watch?v=pZSP8tTAK1w
https://www.c-sharpcorner.com/forums/method-to-implement-listbox-in-asp-net-mvc

Help please...
Comment
Watch Question

Do more with

Expert Office
EXPERT OFFICE® is a registered trademark of EXPERTS EXCHANGE®
Please post or privately send me:
  • The entire code of your View.
  • The entire code of your Controller.

Also, please point out which of those Controller methods is the one that renders the View where the ListBoxes are shown.

I think I know what is happening, but I need to see the rest of it to be sure.
Michael SterlingWeb Applications Developer

Author

Commented:
my view:
@using eScreen.ServiceGroupsManager
@using ServiceGroupManagement.Models
@using ServiceGroupManagement.Resources
@model ServiceGroupManagement.Models.ManagerViewModel
@{
    ViewBag.Title = "Add/Edit Group";
    Layout = "~/Views/Shared/_Layout.cshtml";

}
@using (Html.BeginForm("Category", "Manager", FormMethod.Post))
{
    @Html.HiddenFor(model => model.SelectedGroupID)
    @Html.HiddenFor(model => model.SelectedGroupName)
    @Html.HiddenFor(model => model.SelectedGroupDescription)
    @Html.HiddenFor(model => model.SerializedSelectedGroup)

    <table class="contain">
        <tr>
            <td>
                <table class="nav">
                    <tr class="nav">
                        <td class="navLeft">
                            <input type="button" value="Submit" name="btnSaveCategory" class="button" onclick="location.href = '@Url.Action("UpdateCategoryAndServices")'"/>
                            <input type="button" value="Back" name="btnBack" class="button" onclick="location.href = '@Url.Action("Back")'"/>
                        </td>
                    </tr>
                </table>
            </td>
        </tr>
        <tr>
            <td>
                <div class="subBlockWrapper">
                    <div class="subBlock">
                        <table>
                            <tr>
                                <td class="tableHead" colspan="4">
                                    @Html.Label(SGMResources.CategoryPageSubTitle)
                                </td>
                            </tr>
                            <tr>
                                <td>
                                    @Html.Label(SGMResources.LabelCategoryServiceGroupName)
                                   
                                </td>
                                <td>
                                    @*@Html.TextBoxFor(model => model.SelectedGroup.Name, new { @class = "groupName" })*@
                                    @Html.DisplayFor(model => model.SelectedGroup.Name)
                                </td>
                            </tr>
                            <tr>
                                <td>
                                    @Html.Label(SGMResources.LabelCategoryServiceGroupCategoryName)
                                    @Html.HiddenFor(Model => Model.SelectedCategoryID)
                                </td>
                                <td>
                                    @Html.TextBoxFor(model => model.SelectedGroup.Name, new { @class = "groupName" })
                                </td>
                            </tr>
                            <tr>
                                <td>
                                    @Html.Label(SGMResources.LabelCategorySerivceGroupCategoryDescription)
                                </td>
                                <td>
                                    @Html.EditorFor(model => model.SelectedGroup.Description, new { htmlAttributes = new { @class = "groupDescription" } })
                                </td>
                            </tr>
                            <tr>
                                <td>
                                    @Html.Label(SGMResources.LabelIsActive)
                                </td>
                                <td>
                                    @Html.CheckBoxFor(model => model.SelectedGroup.IsActive)
                                </td>
                            </tr>
                        </table>
                    </div>
                </div>
            </td>
        </tr>
    </table>
    <table class="contain">
        <tr>
            <td>
                <div class="subBlockWrapper">
                    <div class="subBlock">
                        <table class="contain tableCenter">
                            <tr>
                                <td class="listLabelAlign">
                                    @Html.Label(SGMResources.LabelCategoriesAvailableServicesList)
                                </td>
                                <td colspan="1"></td>
                                <td class="listLabelAlign">
                                    @Html.Label(SGMResources.LabelCategoriesSelectedServicesList)
                                </td>
                            </tr>
                            <tr>
                                <td class="listAlign">
                                    @Html.ListBoxFor(x => x.AllAvailableSelectedServices, Model.AllAvailableServices, new { id = "lstbxAllServices", @class = "serviceListBox" })
                                </td>
                                <td colspan="1"></td>
                                <td class="listAlign">
                                    @Html.ListBoxFor(x => x.AddedSelectedServices, Model.AddedServices, new { id = "lstbxServicesAdded", @class = "serviceListBox" })
                                </td>s
                            </tr>
                        </table>
                    </div>""
                </div>
            </td>
        </tr>
    </table>
}

Open in new window


my controller. the method that renders the view is named: "Category". I was up playing around with this last night so if some of the code looks off, its because I was playing around with it. my code that renders the view and fills the listboxes initially is still functioning though.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;
using System.Web.Mvc;
using System.Web.UI;
using eScreen.ServiceGroupsManager.Models;
using ServiceGroupManagement.Helpers;
using ServiceGroupManagement.Models;
using ServiceGroupManagement.Resources;

namespace ServiceGroupManagement.Controllers
{
	public class ManagerController : Controller
	{
		public ActionResult Index(FormCollection collection)
		{
			ManagerViewModel manager = new ViewModelHelper().GetManagerViewModel(SGMResources.PageTitle);

			if (collection.AllKeys.Any(k => k.Contains("ShowOnlyActive")))
			{
				var key = collection.AllKeys.First(k => k.Contains("ShowOnlyActive"));
				manager.ShowOnlyActive = collection[key].Contains("true");
			}

			if (ViewModelHelper.FormContainsKey(collection, "linkSelectedGroupID"))
			{
				manager.SelectedGroup = new Group() { GroupID = Convert.ToInt32(collection.AllKeys.First(k => k.Contains("linkSelectedGroupID")).Split('_')[1]) };
				if (manager.SelectedGroup.GroupID != 0)
				{
					manager.SelectedGroup = manager.GetAllGroups().First(g => g.GroupID == Convert.ToInt32(collection.AllKeys.First(k => k.Contains("linkSelectedGroupID")).Split('_')[1]));
					manager.SelectedGroupID = manager.SelectedGroup.GroupID.ToString();
					manager.SelectedGroupName = manager.SelectedGroup.Name;
					manager.SelectedGroupDescription = manager.SelectedGroup.Description;
					manager.SelectedGroupType = (int)manager.SelectedGroup.GroupType;
					manager.SelectedIsActive = manager.SelectedGroup.IsActive;

					manager.SelectedGroup.Categories = manager.GetCategoriesForGroup(manager.SelectedGroup.GroupID);
					foreach (var c in manager.SelectedGroup.Categories)
					{
						c.Services = manager.GetServicesInCategory(c.ServiceGroupCategoryID);
					}
				}

				TempData["ManagerViewModel"] = manager;
				return RedirectToAction("GroupDetails", manager);
			}

			return View("Index", manager);
		}

		public ActionResult GroupDetails(ManagerViewModel manager)
		{
			bool selCat = Request.Form.AllKeys.Any(k => k.Contains("linkSelectedCategoryID"));
			bool addCat = Request.Form.AllKeys.Any(k => k.Contains("btnAddCategory"));

			foreach (string key in Request.Form.Cast<string>().Where(key => key.StartsWith("btnAddCategory")))
			{
				addCat = true;
			}

			if (selCat || addCat)
			{
				var temp = TempData["ManagerViewModel"] as ManagerViewModel;
				temp.SelectedGroup.Name = manager.SelectedGroup.Name;
				temp.SelectedGroup.Description = manager.SelectedGroup.Description;
				temp.SelectedGroup.GroupType = manager.SelectedGroup.GroupType;
				temp.SelectedGroup.GroupTypeID = (int)manager.SelectedGroup.GroupType;
				temp.SelectedGroup.IsActive = manager.SelectedGroup.IsActive;
				manager = temp;
			}
			else
			{
				manager = TempData["ManagerViewModel"] as ManagerViewModel;
			}

			int catID;

			if (addCat)
			{
				catID = Convert.ToInt32(Request.Form.AllKeys.First(k => k.Contains("btnAddCategory")).Split('_')[1]);
				manager.SelectedCategory = new Category() { ServiceGroupCategoryID = 0 };
				manager.AllAvailableServices = new ViewModelHelper().GetAllAvailableServices();
				TempData["ManagerViewModel"] = manager;
				return RedirectToAction("Category", manager);
			}

			if (selCat)
			{
				catID = Convert.ToInt32(Request.Form.AllKeys.First(k => k.Contains("linkSelectedCategoryID")).Split('_')[1]);
				manager.SelectedCategory = manager.SelectedGroup.Categories.Find(c => c.ServiceGroupCategoryID == catID);
				manager.AllAvailableServices = new ViewModelHelper().GetAllAvailableServices();
				TempData["ManagerViewModel"] = manager;
				return RedirectToAction("Category", manager);
			}

			TempData["ManagerViewModel"] = manager;
			return View(manager);
		}


		public ActionResult Category(ManagerViewModel manager)
		{
			var temp = TempData["ManagerViewModel"] as ManagerViewModel;

			manager = temp;

			manager.PageTitle = SGMResources.CategoryPageTitle;
			manager.SubTitle = "";

			ViewModelHelper vmh = new ViewModelHelper();
			List<Service> allServices = manager.GetAvailableServices();

			List<ServiceInCategory> srvcsInCategory = vmh.GetServicesInCategory(Convert.ToInt32(manager.SelectedCategory.ServiceGroupCategoryID));

			List<Service> addedServices = new List<Service>();
			foreach (var srvc in srvcsInCategory)
			{
				Service theService = new Service();

				theService.ServiceID = srvc.ID;
				theService.ServiceName = srvc.Name;
				theService.TransactionType = srvc.TransactionType;

				addedServices.Add(theService);
			}

			allServices = new List<Service>(allServices.Where
				(a => !addedServices.Any((y => y.ServiceID == a.ServiceID && y.TransactionType == a.TransactionType))));

			manager.AllAvailableServices = vmh.GetAllAvailableServices(allServices);
			manager.AddedServices = vmh.GetAllAvailableServices(addedServices);

			TempData["ManagerViewModel"] = manager;
			return View("Category", manager);
		}

		public ActionResult Back(ManagerViewModel manager)
		{
			manager = (ManagerViewModel)TempData["ManagerViewModel"];
			TempData["ManagerViewModel"] = manager;
			return View("GroupDetails", manager);
		}

		public ActionResult UpdateCategoryAndServices(ManagerViewModel manager, FormCollection collection, IEnumerable<string> addedSelectedServices)
		{
			manager = (ManagerViewModel)TempData["ManagerViewModel"];

			if (addedSelectedServices != null)
			{
				StringBuilder sb = new StringBuilder();
				sb.Append("You selected: " + string.Join(",", addedSelectedServices));
			}
			manager = (ManagerViewModel)TempData["ManagerViewModel"];
			TempData["ManagerViewModel"] = manager;
			return View("GroupDetails", manager);
		}

		public ActionResult GroupAudit(string groupID)
		{
			ManagerViewModel manager = new ViewModelHelper().GetManagerViewModel(SGMResources.PageTitle);

			manager.AuditItems = new List<AuditItem>();
			manager.AuditItems.Add(new AuditItem() { DateEntered = "3/29/2019 2:26:02 PM", User = "mlatta", Field = "IsActive", From = "False", To = "True" });
			manager.AuditItems.Add(new AuditItem() { DateEntered = "3/28/2019 3:30:55 PM", User = "mlatta", Field = "ServiceGroupName", To = "Instant Point of Care Test" });

			return View(manager);
		}
	}
}

Open in new window

There's a couple of things wrong with what you're doing here.

1. TempData is used to store unique key/value pairs between requests, and as soon as that key is accessed the data is deleted from the data store. Think of it more as temporarily storing a particular value for a particular user between requests or chained method calls that don't have a proper mechanism for handing off data. Unless your application will have literally only one user -- EVER -- you're going to see some weird behavior. It's because the key ManagerViewModel is not unique... so every simultaneous user is sharing the same data. You'll need to either figure out a scheme for using unique keys or (better yet) use a different mechanism for storing your Model data.

2. Your form will currently post back to the Category method. The values coming in from the user post are being overwritten by what is in TempData. You need to update the Form Action if you want it to be processed by the UpdateCategoryAndServices method.

3. I assume that resetting manager at line 55 is unintentional. Admittedly, nothing is being changed before line 55, but if something was it would be blown away by line 55. I suggest removing that line as line 48 already handles this.

4. If you're using the ModelBinder (and you are -- this is line 4 of your View) then you don't need to also post collection or addedSelectedServices; if I'm not mistaken they are already in the Model you bound to the View. Simply add more HiddenFors for those Model properties you want to post to the Controller.

5. By their definition, Checkboxes are included in form posts only if they are checked. So you'll need to use something else if you want to know about those items that were added but not checked. I suggest adding HiddenFor for the AddedServices property in your Model and checking that when you post to the server. When the user adds that service, use JavaScript to add that service's identifier to your AddedServices hidden field.

That should be enough to get you moving forward.
Become a CompTIA Certified Healthcare IT Tech

This course will help prep you to earn the CompTIA Healthcare IT Technician certification showing that you have the knowledge and skills needed to succeed in installing, managing, and troubleshooting IT systems in medical and clinical settings.

Michael SterlingWeb Applications Developer

Author

Commented:
1. The TempData is being used to hold onto and pass around my model. I was having trouble with holding onto the values of my model since it has nested classes/lists with in it. So I have to give it to TempData and then pull it out of TempData as needed. But I understand if that's not going to be consistent, I'll look for another solution.

2. I'm so new to this that I need to know: What values are going to be overwritten by TempData? (above you wrote: "The values coming in from the user post are being overwritten by what is in TempData." Which values?

3. Can you paste what you see on line 55? Above, line 55 looks like this:
     bool selCat = Request.Form.AllKeys.Any(k => k.Contains("linkSelectedCategoryID"));

4. Did you wrote:
"...then you don't need to also post collection or addedSelectedServices..."
     did you mean to write:
 "...then you don't need to also post collection to addedSelectedServices..."

     How would the FormCollection object be already bound to my model? Something I've been wondering about, that you mentioned, is: How would a HiddenFor control apply for my needs? I think I might be able to use it but I'm not sure.

5. This code has been changed a bit from when the checkbox was working so it does work, in a previous version of the code. I did discover exactly what you said about it being checked etc. And as I'm now re-reading your last sentence in #5, I see that I'm somehow going to need to take the items from my AddedServices ListBox (ListBoxFor) and pull them into the HiddenFor that I create and then access them in my controller? (I guess?). That will be another hurdle that I'll have to jump once I get that far I guess. First I'll need to know and make sure that I'm accessing the items in my list box and attaching them to my hidden for control.

Side note, just so that I could start working on accessing the items in my AddedServices listbox, I pre-populated that with data. Ultimately right now, my first step is trying to access that pre-populated listbox to get its items and put it into a HiddenFor control I guess. I don't want to have to select the items in my AddedServices listbox to get to them, I want to be able to just "grab" that listbox and its contents, what ever they may be and loop through them.
Responding to the numbers in your response...

1. Understood.

2. Most of your Controller methods have a ManagerViewModel parameter manager that is passed in. The way the ModelBinder works, any calls to those methods where data was successfully posted to the server will immediately have their values overwritten by your code (manager = (ManagerViewModel)TempData["ManagerViewModel"];). I take it that either this was not intended or not how you're using that parameter.

3. My apologies; it's actually line 155.
manager = (ManagerViewModel)TempData["ManagerViewModel"];

Open in new window


4. Nope, I was referring to these:
public ActionResult UpdateCategoryAndServices(ManagerViewModel manager, FormCollection collection, IEnumerable<string> addedSelectedServices)

Open in new window


5. Yes; the easiest way to go about this will be to use jQuery .
Michael SterlingWeb Applications Developer

Author

Commented:
Thanks for your help Kelvin!

Do more with

Expert Office
Submit tech questions to Ask the Experts™ at any time to receive solutions, advice, and new ideas from leading industry professionals.

Start 7-Day Free Trial