ASP.NET passing List back to controller from IEnumerable @model inside Table header ActionLink with non-indexing
Asked Answered
B

1

0

I have been reviewing possible ways to return a View's @model information which is of type IEnumerable back to the controller, so that if I sort/filter on a query from database, I can refine the return each iteration without restarting with a fresh full list being returned. All the ways show you need to POST back based on model[index] which works if you are inside a for loop. But I am working with sending the collection back from an @HTML.ActionLink within a table's header section, so there is no possible indexing available.

My WebAPI setup is based on this where they show how to sort and filter. I am trying to make it a little more complex in that after I filter a list based on my original DB query, I will then be able to sort (from a clickable-actionLink on a table's header column) from that filtered list; where as currently it would just sort from a fresh complete list.

The only way I can think of to do this is pass back (by a POST) to the controller the updated list of the customClass.

@Html.ActionLink("Name", "Index", new { orderBy = ViewBag.sortByName, companyListIds = Model.???? })

A better option (based on comments from Tacud) which would require a smaller POST URL would be by returning a list of the id properties only which can then be applied to a query. But its still a list and still needs to be sent back without an index from and ActionLink. This will help keep track and allow me to continue drilling down to a smaller and smaller list.

Below is parts of my model class, the index Action from the controller, and the index view. Model namespace:

public class Company
{
    public int Id { get; set; }

    [Required]
    public string Name { get; set; }
    public string Address { get; set; }
    public string City { get; set; }
    public string State { get; set; }
}

Controller Namespace:

public async Task<IActionResult> Index(ICollection<int> prev, string orderBy , string searchCategory ="", string searchString = "")
    {
        List<string> Categories = new List<string>() { "Name", "City", "State", "Zip", "Contact Person" };
        ViewBag.searchCategory = new SelectList(Categories);
        ViewBag.sortByName = orderBy == null ? "name" : orderBy == "name" ? "namedesc" : "name";
        ViewBag.sortByCity = orderBy == "city" ? "citydesc" : "city";
        ViewBag.sortByState = orderBy == "state" ? "statedesc" : "state";
        ViewBag.companyIndex = companyList.Count==0 ? await _context.Company.ToListAsync() : companyList ;

        List<Company> resultSet = new List<Company>(ViewBag.companyIndex);

        if (!String.IsNullOrEmpty(searchCategory) && !String.IsNullOrEmpty(searchString))
        {
            switch (searchCategory)
            {
             .....
            }
        }

        switch (orderBy)
        {
          ....
        }

        return View(resultSet);
    }

View namespace:

@model IEnumerable<Laier_It.Models.Company> <p>
@using (Html.BeginForm() {
<p>
    Search By: @Html.DropDownList("SearchCategory", "")
    Search For: @Html.TextBox("SearchString")
    <input type="submit" value="Filter" />
</p> }
<table class="table ">
    <thead>
        <tr>
            <th>
                @Html.ActionLink("Name", "Index", new { orderBy = ViewBag.sortByName, companyList = Model })
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Address)
            </th>
            <th>
                @Html.ActionLink("City", "Index", new { orderBy = ViewBag.sortByCity, companyList = Model })
            </th>
            <th>
                @Html.ActionLink("State", "Index", new { orderBy = ViewBag.sortByState, companyList = Model })
            </th> </tr>
    </thead>
    <tbody>
        @foreach (var item in Model)
        {
            <tr>
                <td>
                    @Html.DisplayFor(modelItem => item.Name)
                </td>
                <td>
                    @Html.DisplayFor(modelItem => item.Address)
                </td>
                <td>
                    @Html.DisplayFor(modelItem => item.City)
                </td>
                <td>
                    @Html.DisplayFor(modelItem => item.State)
                </td>  </tr>
        }
    </tbody>
</table>
Benton answered 23/3, 2017 at 17:39 Comment(9)
Why do you need to send the list back? Why don't you call the database and build the list again in the POST controller action method?Nickey
@Nickey It already does that. Because if you continue to drill down (multiple filters) from the same index page, how do you keep track of the filters you already did? But as stated in the question, what if I click on a header column to sort/order that column when it was already filtered by a search; it then returns the complete list sorted not the one that was filtered. One action at a time, with needing a way to preform actions on the result.Benton
Just post back those filters and build the list based on them. If you don't edit the table values, you should not be sending whole list back.Nickey
@Nickey When you first come to the page there is no "edit" values so it does load a complete list. So now you have me building a list of filters, per action, which again is needing to POST back to the controller, and be built upon with each view rendition. How would you go about that; since I am again trying to pass a list back to the controller without any type of index or iteration from an ActionLink?Benton
You can for example wrap the whole table in the form element and change the links (Html.ActionLink) to submit buttons (and style them as links). This way you will always post back values from all form fields (filters) + the name of the button that was clicked. Or you can add a little bit of javascript if you want to keep links, or to implement sorting only on the client side. If you want, I can add my proposed solutions in an answer with some examples. But I'm sorry, I won't support sending lists back to server when it is not necessary :)Nickey
@Nickey While that might work in theory, it of course isn't what I am seeking based on the title (whether you support it or not). Also your suggestion on buttons still would require added complexity to the controller because how do you know which header you want to sort by first if more than one is selected; or what if you want to first filter by a state then a city but only have the one search box (this requires a need to know what came before)? I am not familiar enough with JavaScript. BUT a list of filters is less then that of Companies. I am still seeking a way to POST a non-indexed list.Benton
Just to answer your questions. Yes, my solution wouldn't support that because from the example it wasn't clear it should. Your solution doesn't support that either. Neither the front end (view) nor the back end (controller) count with this the possibility to sort by more than one column. That would require some changes.Nickey
@Nickey If you sort a DbSet<Class> by one property then sort a new query of the same DbSet<Class> then yes you have only the resultSet of the last sort; but if you sort an already sorted list then it will not be the same resultSet.Benton
Based on the example you sent: No, it'll be the same result. You pass orderBy property back to controller and that's it. Nothing about how it was previously sorted. It doesn't matter if you sort a database collection or the already sorted collection you send back. Once you apply .OrderBy() it'll by sorted ONLY by one property. Just the whole idea of sending whole list of values (moreover as a GET request) is bad practice. Couple reasons: Length of the URL has its limit, and you should send all the filter values anyway because how do you set the current values of the filters after the refresh?Nickey
B
1

To first obtain the list of Id's that I want to post back to the controller of the current rendition showing within the WebAPI view page I used @Model.Select(x => x.Id)

My controller index method was only changed by this

var resultSet = prev.Count == 0 ? await _context.Company.ToListAsync() : 
               await _context.Company.Where(x => prev.Contains(x.Id)).ToListAsync();

And my View looks like this:

@model IEnumerable<Laier_It.Models.Company>

@using (Html.BeginForm() )
{
    <p>
        Search By: @Html.DropDownList("SearchCategory", "")
        Search For: @Html.TextBox("SearchString")
        <input type="submit" value="Filter" />
    </p>
}

<table class="table ">
    <thead>
        <tr>
            <th>
                @Html.ActionLink("Name", "Index", new { orderBy = ViewBag.sortByName, prev = @Model.Select(x => x.Id) } )
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Address)
            </th>
            <th>
                @Html.ActionLink("City", "Index", new { orderBy = ViewBag.sortByCity, prev = @Model.Select(x => x.Id) } )
            </th>
            <th>
                @Html.ActionLink("State", "Index", new { orderBy = ViewBag.sortByState, prev = @Model.Select(x => x.Id) } )
            </th>
        </tr>
    </thead>
    <tbody>
        @foreach (var item in Model)
        {
            <tr>
                <td>
                    @Html.DisplayFor(modelItem => item.Name)
                </td>
                <td>
                    @Html.DisplayFor(modelItem => item.Address)
                </td>
                <td>
                    @Html.DisplayFor(modelItem => item.City)
                </td>
                <td>
                    @Html.DisplayFor(modelItem => item.State)
                </td>
            </tr>
        }
    </tbody>
</table>
Benton answered 25/3, 2017 at 1:56 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.