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

2.5k Views Asked by At

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.

4

There are 4 best solutions below

3
Reza Aghaei On BEST ANSWER

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.

3
ObedMarsh On

You can't pass a value from your viewbag back to the controller.

Instead you should use model binding in your view to pass the information to your post method.

0
Shyju On

You can pass the filter values in querystring after reading from viewbag. Assuming your SearchTransacModel class has the DATEDISPFROM and OFFICERNAME properties,

public class SearchTransacModel
{
     public string DATEDISPFROM { set; get; }
     public string OFFICERNAME { set; get; }
}  

You can send the values as route values where the key will be the property name (of SearchTransacModel class) and value will be the value you have.

@{
    var filter = ViewBag.CurrentFilter as SearchTransacModel;
}
@Html.ActionLink("Reference No", "Index", new { sortOrder = ViewBag.RefNoSortParm, 
        DATEDISPFROM = filter!=null? filter.DATEDISPFROM:"", 
        OFFICERNAME = filter != null ? filter.OFFICERNAME : "" })

Model binder will be able to map the querystring values to your currentFilter object.

I suggest you not use 2 parameters with same type (currentFilter & searchModel).

0
Orel Eraki On

While ViewBag can be used to achieve what you are looking for, it is still not designed for that purpose.

Instead, you should create a Model and use it to communicate with your controller and view.

Change and align your return model to be a little bigger.

public TransactionsViewModel
{
    public SearchTransacModel SearchModel { get; set; }
    public List<TransacViewModel> TransactionList { get; set; }
}

Now you can drop the usage of ViewBag.currentFilter and use only this model.