Search code examples
c#asp.net.netasp.net-mvcroutevalues

How can I pass some objects in ViewBag to the Action? - Preserve search, sort and paging options


I have a view which I show result of a search and sort in a paged list. I add column headers as links to be enable the user to sort based on a column this way:

<tr>
    <th>           
        @Html.ActionLink("Reference No", "Index", 
          new { sortOrder = ViewBag.RefNoSortParm, currentFilter = ViewBag.CurrentFilter })
    </th>
    <th>
        @Html.ActionLink("Category", "Index", 
          new { sortOrder = ViewBag.CatSortParm, currentFilter = ViewBag.CurrentFilter })
    </th>
    ... Some other columns
</tr>

Also I added a pager this way:

Page @(Model.PageCount < Model.PageNumber ? 0 : Model.PageNumber) of @Model.PageCount

@Html.PagedListPager(Model, page => Url.Action("Index",
    new { page, sortOrder = ViewBag.CurrentSort, currentFilter = ViewBag.CurrentFilter }))

And the action which I use looks like this:

private Registry_fssEntities db = new Registry_fssEntities();
public ActionResult Index(string sortOrder, SearchTransacModel searchModel, SearchTransacModel currentFilter, int? page)
{            
    var model = from c in db.TRANSACs
                select c;
    ViewBag.CurrentSort = sortOrder;
    ViewBag.DispDateSortParm = String.IsNullOrEmpty(sortOrder) ? "DispDate" : "";
    ViewBag.RefNoSortParm = sortOrder == "RefNo" ? "RefNo_desc" : "RefNo";
    ViewBag.CatSortParm = sortOrder == "Cat" ? "Cat_desc" : "Cat";   
    if (searchModel.DATEDISPFROM != null || searchModel.DATEDISPTO != null || searchModel.DATEDISPFROM != null || searchModel.OFFICERNAME != null || searchModel.APPNAME != null || searchModel.REFNO != null || searchModel.PROCNAME != null)
    {
        page = 1;
    }
    else
    {
        searchModel = currentFilter;
    }
    if (searchModel != null)
    {
        if (!String.IsNullOrEmpty(searchModel.DATEDISPFROM))
        {                    
            ViewBag.DispFrom = searchModel.DATEDISPFROM.ToString();
        }
        else
        {
            searchModel.DATEDISPFROM = "01/01/" + DateTime.Today.Year.ToString();
            ViewBag.DispFrom = "01/01/" + DateTime.Today.Year.ToString();
        }
        if (!String.IsNullOrEmpty(searchModel.DATEDISPTO))
        {                   
            ViewBag.DispTo = searchModel.DATEDISPTO.ToString();
        }
        else
        {
            searchModel.DATEDISPTO = "31/12/" + DateTime.Today.Year.ToString();
            ViewBag.DispTo = "31/12/" + DateTime.Today.Year.ToString();
        }
    }
    if (searchModel != null)
    {
        var tRANSACs = new TransacBusinessLogic();
        model = tRANSACs.GetTransacs(searchModel);

        ViewBag.currentFilter = searchModel;
    }
    List<TransacViewModel> TransactionList = new List<TransacViewModel>();
    foreach (var item in model)
    {
        TransactionList.Add(new TransacViewModel {
                TRANSID = item.TRANSID,
                REFNO = item?.REFNO,
                PROCESS = item?.PROCESS,
                //CATEGORY = item.PROCESS.CATEGORY,
                DOCTYPE = item?.DOCTYPE,
                DATEDEL = returnasdate(item.DATEDEL),
                DATEDISP = returnasdate(item.DATEDISP),
                APPNAME = item?.APPNAME,
                OFFICER = item?.OFFICER,
                DATEREG = item.DATEREG
            });
    }
    switch (sortOrder)
    {
        case "DispDate":
            TransactionList = TransactionList.OrderBy(x => x.DATEDISP).ToList();
            break;
        case "RefNo":
            TransactionList = TransactionList.OrderBy(t => t.REFNO).ToList();
            break;
        case "RefNo_desc":
            TransactionList = TransactionList.OrderByDescending(t => t.PROCESS.CATEGORY.DETAIL).ToList();
            break;
        case "Cat":
            TransactionList = TransactionList.OrderBy(t => t.PROCESS.CATEGORY.DETAIL).ToList();
            break;
        case "Cat_desc":
            TransactionList = TransactionList.OrderByDescending(t => t.REFNO).ToList();
            break;
        default:
            TransactionList = TransactionList.OrderByDescending(t => t.DATEDISP).ToList();
            break;
    }
    int pageSize = 6;
    int pageNumber = (page ?? 1);            
    return View(TransactionList.ToPagedList(pageNumber, pageSize));
}

SearchTransacModel is a model used to contain my search parameters as I pass them to the controller. It works fine when I submit the search form via the submit button, and I am sending this search criteria back to the view using a ViewBag like this: ViewBag.currentFilter = searchModel;

But when I click any of the sort action links I lose the search parameters. I.e currentFilter is null when I get to the controller.

I am passing currentFilter as query string and assigning the search model to it like in this case:

 @Html.ActionLink("Reference No", "Index", new { sortOrder = ViewBag.RefNoSortParm, currentFilter = ViewBag.CurrentFilter })

Can anyone help please? I am new at MVC btw.


Solution

  • You can use such action:

    public ActionResult Index(SearchModel searchModel, string sortColumn, string sortOrder)
    {
        ViewBag.SearchModel = searchModel;
        ViewBag.SortColumn= sortColumn;
        ViewBag.SortOrder = sortOrder;
        var business = new BusinessLogic();
        var model = business.Search(searchModel, sortColumn, sortOrder);
        return View(model);
    }
    

    You can simply mix some route values this way. This way, both search options and sort options will be preserved between requests:

    @{
        var routeValues = new RouteValueDictionary(ViewBag.SearchModel ?? new { });
        routeValues["sortColumn"] = ViewBag.SortColumn;
        routeValues["sortOrder"] = ViewBag.SortOrder;
    }
    @Html.ActionLink("Link Text", "Action", routeValues);
    

    Note:

    • Based on the idea in this answer you can simply create a method which mixes even 2 objects to create route values for you.

    • You also can encapsulate sort options in a SortOption class and put it in ViewBag and use it in model binding.

    • System.Linq.Dynamic will help you to have a more dynamic and clean code for sort.

    • To learn more about benefits of such SearchModel and BusinessLogic technique take a look at Filter/Search using Multiple Fields - ASP.NET MVC

    • A common scenario for this requirement is for example, for page links of a pager or column links of a grid which is showing the result of a search and sort. If you are using this technique on a grid, paging links should preserve search options and sort options between requests. But for generating column header links, for each column you should use different suitable sort order and sort column; also for the column which its name equals to current sort column, the sort order should be inverse of current sort order. Also column headers should also preserve page index.