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

4

2

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.

Sufflate answered 29/8, 2016 at 17:45 Comment(1)
The OP is creating a link and they want to preserve search options and sorting options between 2 requests. For example, for page links of a pager or column links of a grid which is showing the result of a search and sort.Ketonuria
K
3

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.

Ketonuria answered 29/8, 2016 at 20:55 Comment(3)
The answer doesn't solve all problems of the code, but its focus is on preserving search, sort and paging options. It's the route values part which is a little tricky in this question, which I showed you how to mix an object with some other values to create suitable route values. Let me know if you have any question about the answer :)Ketonuria
Thank you Reza your example helped me find a solution.Sufflate
You're welcome @Sufflate Happy to hear it helped you to solve the problem :)Ketonuria
S
4

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.

Sanjak answered 29/8, 2016 at 17:48 Comment(3)
I recommend you go through a basic tutorial for MVC on how to make GET and POST methods and do simple data manipulation in views. etc. It's unrealistic to think you can code in .net mvc if you don't understand this basic concept.Sanjak
The OP is creating a link and they want to preserve search options and sorting options between 2 requests. For example, for page links of a pager or column links of a grid which is showing the result of a search and sort.Ketonuria
It's completely a common scenario, for example when creating a grid which columns contain sort links or the grid has pager. Then when the grid is showing sorted result of a search, then when the user clicks on another column, only sort column should change and search criteria should be preserved, or if the user click on another page, only page number should change, neither sort options not search criteria.Ketonuria
K
3

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.

Ketonuria answered 29/8, 2016 at 20:55 Comment(3)
The answer doesn't solve all problems of the code, but its focus is on preserving search, sort and paging options. It's the route values part which is a little tricky in this question, which I showed you how to mix an object with some other values to create suitable route values. Let me know if you have any question about the answer :)Ketonuria
Thank you Reza your example helped me find a solution.Sufflate
You're welcome @Sufflate Happy to hear it helped you to solve the problem :)Ketonuria
T
0

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).

Tonedeaf answered 29/8, 2016 at 17:58 Comment(0)
M
0

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.

Marchesa answered 29/8, 2016 at 17:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.