0

First of all sorry for the super generic title but i have no idea how to ask this question properly.

I am a bit new to Asp net MVC 5 and i am having some issues with passing information from the Controller to the View. The problem is that in the View I need to have 2 search functions, one using a text (with a textbox) and another with listboxes, and I can t seem to get the two working at the same time (they work separately), I suspect it has something to do with my Viewmodel but I have no idea why this is happening.

this is my ViewModel:

public class ViewModel{

    public IEnumerable<ABC> xyz { get; set; }
    public List<SelectListItem> device { get; set; }
    public List<SelectListItem> type { get; set; }
    public SelectListItem selectedItem { set; get; }

    //these are all the properties that ABC has

    public int a { get; set; }
    public string Name{ get; set; }
}

These are the controller actions

public ActionResult FillListBoxes()
{
    ViewModel myViewModel = new ViewModel();

    var tempObjects = db.ABC

        //.Where(...)
       .Select(v =>
           new
           {
               v.Type,
               v.device,

           })
       .ToList();



          myViewModel.device = new List<SelectListItem>();
         foreach (var device in tempObjects.Select(obj => obj.device).Distinct().ToList())
         {
             myViewModel.device.Add(new SelectListItem { Text = device, Value = device });

         }


         myViewModel.type = new List<SelectListItem>();
         foreach (var type in tempObjects.Select(obj => obj.device).Distinct().ToList())
         {
             myViewModel.type.Add(new SelectListItem { Text = type, Value = type });

         }

        return View(myViewModel);



}

public ActionResult Search(string searchString)
{
   ViewModel myViewModel = new ViewModel();
   // FillListBoxes();
   var tempObjects = db.ABC.ToList();

  if (!String.IsNullOrEmpty(searchString))
   {
       myViewModel.abc = tempObjects.Where(m => m.Name.Contains(searchString));
       return View(myViewModel);
   }

   else
   {
       myViewModel.abc = tempObjects;
       return View(myViewModel);
   }
}

And this is the View

@model project.Models.ViewModel

@{
    ViewBag.Title = "Search";
}

@section Styles {
    <link href="@Url.Content("~/Content/Search.css")" rel="stylesheet" type="text/css" />
    <link href="@Url.Content("~/Content/chosen.css")" rel="stylesheet" type="text/css" />
}




@using (Html.BeginForm("Search", "ABC", FormMethod.Get))
{
    <p>
        Name: @Html.TextBox("SearchString") <br />
        <input type="submit" value="Filter" />
    </p>

}


@using (Html.BeginForm("FillListBoxes", "ABC", FormMethod.Get))
{
    <p>   
       @Html.DropDownListFor(model => model.selectedItem, Model.device, new { @class = "chosen", multiple = "multiple", style = "width: 350px;" })
    </p>

    <p>
        @Html.DropDownListFor(model => model.selectedItem, Model.type, new { @class = "chosen", multiple = "multiple", style = "width: 350px;" })
    </p>

    <p>
        <input type="submit" value="Filter" />
    </p>

}

<table class="table">
                <tr>
                    <th>
                        @Html.DisplayNameFor(model => model.a)
                    </th>
                    <th>
                        @Html.DisplayNameFor(model => model.Name)
                    </th>
                    <th></th>
                </tr>

                @foreach (var item in Model.abc) {
        <tr>

            <td>
                @Html.DisplayFor(modelItem => item.a)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Name)
            </td>

        </tr>
    }

</table>

The error i am getting now is that selectedItem should be a IEnumerable but even if i change it get a There is no ViewData item of type 'IEnumerable' that has the key 'selectedItem'. and before this I was facing a: the model item passed into the dictionary is of type 'system.data.entity.infrastructure.dbquery 1[abc] but this dictionary requires a model item of type ViewModel this is when I changed the Search method in the controller to use the Viewmodel.

If I am missing anything please do say.

I apologize again for the super generic title.

@Edit Added the suggestions made by Stephen

@Edit 2 So here is a brief explanation on how this all works, the texbox is there so the user can filter the ABCs that are presented in that page (based solely on the name attribute). The listboxes are there so if the user can filter the presented ABCs through their type and/or device only.

I hope this clears some things up

3
  • Can you explain what is the purpose of this. Is the search textbox filtering you collection of ABC's based on the Name property? What are the dropdownlists doing - arethey also filtering ABC's based on other properties and if so, which? Commented Dec 15, 2014 at 21:19
  • @StephenMuecke I added a more detailed explanation on what i am trying to achieve in all this. What would be the best approach to this then? Commented Dec 15, 2014 at 21:31
  • I'll post an answer shortly explaining what your doing wrong (give me 30 minutes) Commented Dec 15, 2014 at 21:49

2 Answers 2

0

There are numerous problems

Firstly, you view includes helpers to generate dropdownlists, for example

@Html.DropDownListFor(model => model.selectedItem, Model.device, ...)

but when you call the Search() method, you do not populate the SelectList's so Model.device is null and throws the exception

There is no ViewData item of type 'IEnumerable' that has the key 'selectedItem

Secondly, <select> elements (and all controls) bind to value types but you are trying to bind to typeof SelectListItem which is a complex object.

Thirdly you have 2 dropdownlist binding to the same property so even if you do change selectedItem to string, it would only ever bind the value of the selected device and the selected type would be ignored.

Finally, you have 2 separate forms so nothing stays in sync. If you post the first form, items would be filtered based on the name (the selected device is ignored) and conversely if you post the second form, the value of name is ignored.

Based on your current approach your view model would need to be

public class ViewModel
{
  public IEnumerable<ABC> Items { get; set; } 
  public string NameFilter { get; set; }
  public string DeviceFilter { get; set; }
  public string TypeFilter { get; set; }

  public SelectList DeviceList { get; set; }
  public SelectList TypeList { get; set; }
}

Controller

public ActionResult Search()
{
  ViewModel model = new ViewModel();
  // populate properties including select lists
  return View(model);
}

[HttPost]
public ActionResult Search(ViewModel model)
{
  model.Items = // Get the filtered items based on the values of NameFilter, DeviceFilter and TypeFilter
  model.DeviceList = // reassign both select lists
  model.Typelist = ..
  return view(model);   
}

View

@model ViewModel
@using(Html.BeginForm())
{
  @Html.TextBoxFor(m => m.NameFilter)
  @Html.DropDownListFor(m => m.DeviceFilter, Model.DeviceList)
  @Html.DropDownListFor(m => m.TypeFilter, Model.TypeList)
  <input type="submit" value="Filter" />
}
<table class="table">
  .....
<table>

However all this can be done better by using ajax to pass the filter values to a method that returns a partial view containing just the updated table, and replacing the DOM, or even better by rendering each table row with data-* attributes for the device and type values and then just using javascript/jquery to hide/show rows based on the filer values (no need to go back to the server)

Sign up to request clarification or add additional context in comments.

8 Comments

Thank you for your response but there is something i don t understand. I used the method ActionResult Search() to fill DeviceList and TypeList with the information I have in the database, so far so good, what I am having trouble understanding is what am I actually supposed to do in the 2nd Search(ViewModel model). I mean with the SelectList and string parameters if you could elaborate a bit more I would greatly appreciate it. I also plan on using ajax after I manage to get a stable version of this.
When you submit, its posting your view model to the public ActionResult Search(ViewModel model) method. That model contains the value of the search text (NameFilter property) and the values of the selected options (DeviceFilter and TypeFilter properties), so just query your database based on those values, for example - model.Items = db.ABC.Where(m => m.Name.Contains(model.NameFilter)); - obviously adjust to take into account the DeviceFilter and TypeFilter properties as well
Thank you for the response but I still have an issue with the actual "search". I dont know if this is needed but my form declaration is as follows: using (Html.BeginForm("Search", "ABC", FormMethod.Get)) so I can display everything i want/need but when I click submit I do not enter the 2nd actionresult (I added some breakpoints and it never goes in there). I have this in the controller method var dbList = db.ABC.ToList(); model.item= dbList.Where(m => m.Name.Contains(model.NameFilter)); return(view)
Its @using(Html.BeginForm()). You need to post to a method so you can pass your view model
Another comment - you will probably want @Html.DropDownListFor(m => m.DeviceFilter, Model.DeviceList, "None") (ditto for TypeList) so you can have an option which does not filter based on DeviceFilter or TypeFilter and check these and NameFilter for null so you can construct the correct query.
|
0

Instead of using MultiSelectList you should use List:

public class ViewModel{

 public IEnumerable<ABC> xyz { get; set; }
    public List<SelectListItem> device { get; set; }
    public List<SelectListItem> type { get; set; }        
    public SelectListItem selectedItem { set; get; }
//these are all the properties that ABC has

    public int a { get; set; }
    public string Name{ get; set; }
}

and

public ActionResult FillListBoxes()
{
    ViewModel myViewModel = new ViewModel();

    ...

    myViewModel.device = new List<SelectListItem>();
    foreach (var device in tempObjects.Select(obj => obj.device).Distinct().ToList()) {
        myViewModel.device.Add(new SelectListItem { Text = device, Value = device });
    // das gleiche für myViewModel.type entsprechend

    return View(myViewModel);

}

EDIT: The type for selectedItem should be "SelectListItem" as well and of course I forgot to add .ToList() at the end.

8 Comments

it complains about the selectedItem, The ViewData item that has the key 'selectedItem' is of type 'System.Int32' but must be of type 'IEnumerable<SelectListItem>' and if i change that it complains about There is no ViewData item of type 'IEnumerable<SelectListItem>' that has the key 'selectedItem'.. Do I need to add something? or change something in the viewmodel?
I fixed this error and added the missing .ToList() call. A good guide to use select list helpers can be found here (old but still valid at this point): asp.net/mvc/overview/older-versions/…
It make no difference whatsoever if you use MultiSelectList SelectList or List<SelectListItem>. The reason for the error is because the value of Model.device is null
@StephenReindl Thank you very much for your time and response and I am sorry for pestering you but I added the changes you suggested and i am still getting the IEnumerable... etc error and if I change it i get a compiler error stating I don t have a definition for selectedItem, any idea where I may be messing up?
@StephenMuecke but if I remove the search function the listboxes are filled correctly, why does adding the search function screw it up then? I am passing something I shouldn t?
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.