Sorting fields ascending and descending with single link

orderby c# multiple fields
c# list sort descending
paging searching and sorting in asp.net mvc 5 using jquery
linq order by ascending
asp net core mvc with ef core sort, filter paging
linq orderby
filter and display data with asp.net mvc
mvc grid paging, sorting filtering

So I have a MVC app with a table and when the header is clicked I would like it to switch between sorting in ascending or descending fashion depending how many times it is clicked.

Currently I can only sort descending or ascending I don't know how to implement both. userName is just a variable I'm getting from another controller when a user logs in and I'm using a switch because I plan on having multiple headers that can be sortable.

View
<table>
    <tr>
        <th>
            <a href="@Url.Action("Dispatch", "Calls", new { userName = Session["UserName"], new { sortOrder = "Name_desc" })">Name</a>
        </th>
    </tr>
</table>
Controller
public ActionResult Index(string userName, string sortOrder)
{
    string userName = Session["UserName"].ToString();

    var model = from t in db.Users
                where t.UserName == userName
                select t;

    switch(sortOrder)
    {
        case "Name_desc":
            model = model.OrderByDescending(t => t.UserName);
            break;
    }
}

Put your sort order in a ViewBag value. Check inline (Viewbag.NameSort):

View:

<th>
    <a href="@Url.Action("Dispatch", "Calls", new { userName = Session["UserName"], new { sortOrder = ViewBag.NameSort })">Name</a>
</th>

Controller:

public ActionResult Index(string userName, string sortOrder)
{
    string userName = Session["UserName"].ToString();

    // Convert sort order
    ViewBag.NameSort = sortOrder == "Name" ? "Name_desc" : "Name";

    var model = from t in db.Users
                where t.UserName == userName
                select t;

    switch(sortOrder)
    {
        case "Name_desc":
            model = model.OrderByDescending(t => t.UserName);
            break;
        case "Name":
            model = modelOrderBy(t => t.UserName);
            break;
    }
}

OrderBy & OrderByDescending - Sorting Operators, This tutorial explains Sorting operators: OrderBy & OrderByDescending. Last, LastOrDefault · Single, SingleOrDefault · SequenceEqual · Concat OrderBy, Sorts the elements in the collection based on specified fields in ascending or decending OrderBy sorts the values of a collection in ascending or descending order. The table has a header row, and in most columns there is a dropdown list and a right-justified sort icon. When the user clicks the sort icon, the data is sorted by that column in either ascending or descending order (depending on sort toggle status).

I didn't want to use ViewBag values, so I instead used attributes of hidden elements to keep track of user sort selection.

I have a Home Index page with an inner partial view that in a section that displays a user activity log table. The table has a header row, and in most columns there is a dropdown list and a right-justified sort icon. When the user clicks the sort icon, the data is sorted by that column in either ascending or descending order (depending on sort toggle status).

I'll apologize in advance that I've supplemented so much code for this simple solution, but I think for full context for the person (possibly a newbie) looking at my answer, the code supplements should be of help for putting together a simple yet full data retrieval and sort toggle solution without using ViewBags.

Explanation of code usage at bottom of post.

My Index view (where the hidden elements reside). The sort attribute is of main interest here:

<div class="hidden">
    <span id="act-user-sort" sort=""></span>
    <span id="act-type-sort" sort=""></span>
    <span id="act-level-sort" sort=""></span>
    <span id="act-date-sort" sort=""></span>
</div>
<section id="partial_Activity">
    @Html.Action("_Activity", "Home")
</section>

The ActionResult "_Activity" which initially returns the _Activity partial view into the above Index view:

public ActionResult _Activity()
{
    using (var context = new ApplicationDbContext())
    {
        //create new LogModel object.  this holds all that will be returned to the view
        LogModel logModel = new LogModel();

        //create new LogSelect object. this is the list of drop downs for the activity log table.
        LogSelect logSelect = new LogSelect();

        //query to get initial set
        var result = from log in context.Logs
                     join user in context.Users on log.UserId equals user.Id into userlog
                     from user in userlog.DefaultIfEmpty()
                     orderby log.LogDate descending
                     select new Models.UserActivity { Log = log, User = user }; //this could bring back null user objects

        //populate items for drop down lists
        logSelect.LogUsers = result.Select(u => new LogUser { Name = u.User.FirstName + " " + u.User.LastName, Id = u.User.Id }).Distinct().ToList();
        logSelect.LogTypes = result.Select(t => t.Log.Type).Distinct().ToList();
        logSelect.LogLevels = result.Select(t => t.Log.Level).Distinct().ToList();

        //initial page for pagination is 1, default page size is 10.
        int pageIndex = 1;
        int pageSize = 10;

        //return view with paginated list.
        PaginatedList<UserActivity> pgList = PaginatedList<UserActivity>.Create(result.AsNoTracking(), pageIndex, pageSize);

        //fill view model.
        logModel.LogSelect = logSelect;
        logModel.UserActivity = pgList;

        return PartialView(logModel);
    }
}

My "_Activity" Action Partial view innards (where the table and sort buttons reside):

@model Utils.Models.LogModel
<style>
...
</style>
<div>
    <h3>Recent Activity</h3>
</div>
<div style="height:20px"></div>
@* the panel class rounds the corners of the table *@
<div class="panel panel-default table-responsive" style="font-size:.9em;">
    <table id="log-table" class="table table-striped table-bordered table-hover log-table" style="width:100%;">
        <thead>
            <tr class="bg-primary">
                <th class="dropdown log-dropdown">
                    <a href="#" data-toggle="dropdown" class="dropdown-toggle">User&nbsp;&nbsp;<b class="caret"></b></a>
                    <div class="log-dropdown-sort log-dropdown-sort-init">
                        <a sort="" href="#" id="log-dropdown-user-sort"><img src="~/Images/ui/alpha-sort-white.png" /></a>
                    </div>
                    <ul id="log-dropdown-user" class="dropdown-menu log-dropdown">
                        @foreach (var user in Model.LogSelect.LogUsers)
                        {
                            <li><a id="log-user" href="#">@user.Name</a><span id="log-user-id" class="hidden">@user.Id</span></li>
                        }
                    </ul>
                </th>
                <th>Action</th>
                <th class="dropdown log-dropdown">
                    <a href="#" data-toggle="dropdown" class="dropdown-toggle">Type&nbsp;&nbsp;<b class="caret"></b></a>
                    <div class="log-dropdown-sort log-dropdown-sort-init">
                        <a sort="" href="#" id="log-dropdown-type-sort"><img src="~/Images/ui/alpha-sort-white.png" /></a>
                    </div>
                    <ul id="log-dropdown-type" class="dropdown-menu">
                        @foreach (var log in Model.LogSelect.LogTypes)
                        {
                            <li><a id="log-type" href="#">@log</a><span class="hidden">@log</span></li>
                        }
                    </ul>
                </th>
                <th class="log-dropdown">
                    Date
                    <div class="log-dropdown-sort log-dropdown-sort-init">
                        <a sort="" href="#" id="log-dropdown-date-sort"><img src="~/Images/ui/alpha-sort-white.png" /></a>
                    </div>
                    <div class="log-dropdown-date log-dropdown-date-init">
                        <a href="#" id="log-dropdown-date-range"><img src="~/Images/ui/calendar-white.png" /></a>
                    </div>
                </th>
            </tr>
        </thead>
        <tbody>

            @foreach (var log in Model.UserActivity)
            {
                <tr>
                    <td>@log.User.FirstName @log.User.LastName</td>
                    <td class="log-message">@log.Log.Message</td>
                    <td>@log.Log.Type</td>
                    <td class="log-date">@log.Log.LogDate</td>
                </tr>
            }
        </tbody>
    </table>

</div>
<div>
    <ul class="pagination">
        <li>
            <a href="#" aria-label="Previous">
                <span aria-hidden="true">&laquo;</span>
            </a>
        </li>
        @for (int pg = 1; pg <= Model.UserActivity.TotalPages; pg++)
        {
            if (pg == Model.UserActivity.PageIndex)
            {
                <li class="active"><a href="#">@pg</a></li>
            }
            else
            {
                <li><a href="#">@pg</a></li>
            }
        }
        <li>
            <a href="#" aria-label="Next">
                <span aria-hidden="true">&raquo;</span>
            </a>
        </li>
    </ul>
</div>

The jquery to handle the events:

/**********************************************************************************************
_Activity.cshtml

/*********************************************************************************************/

//activity log sort buttons.
$('#partial_Activity').on('click', '#log-dropdown-user-sort', function (e) {
    e.preventDefault();
    var data = {};
    data.sortOrder = setSort($("#act-user-sort"), "user");
    GetActivity(data);
});

$('#partial_Activity').on('click', '#log-dropdown-type-sort', function (e) {
    e.preventDefault();
    var data = {};
    data.sortOrder = setSort($("#act-type-sort"), "type");
    GetActivity(data);
});

$('#partial_Activity').on('click', '#log-dropdown-level-sort', function (e) {
    e.preventDefault();
    var data = {};
    data.sortOrder = setSort($("#act-level-sort"), "level");
    GetActivity(data);
});

$('#partial_Activity').on('click', '#log-dropdown-date-sort', function (e) {
    e.preventDefault();
    var data = {};
    data.sortOrder = setSort($("#act-date-sort"), "date");
    GetActivity(data);
});


//this function checks and sets the "sort" attribute of the passed element.
//then based on setting of the "sort" attribute, returns a string value that will be used by code-behind to sort values in the table.
function setSort(element, sort) {

    if (element.attr("sort") == "") {
        element.attr("sort", "asc");
        sort = sort + "_asc";
    }
    else {
        if (element.attr("sort") == "asc") {
            element.attr("sort", "desc")
            sort = sort + "_desc";
        }
        else {
            element.attr("sort", "asc")
            sort = sort + "_asc";
        }
    }
    return sort;
}

function GetActivity(options) {
    $.ajax({
        cache: false,
        type: "GET",
        url: "../Home/GetActivity",
        data: options,
        contentType: "application/json; charset=utf-8",
        success: function (obj) {
            $("#partial_Activity").html(obj);
        }
    }).done(function () {
    });
}

The GET method that returns the partial view when the user clicks a sort button:

[HttpGet]
public PartialViewResult GetActivity(
    string sortOrder,
    string userFilter,
    string typeFilter,
    string levelFilter,
    DateTime? startDate,
    DateTime? endDate,
    int pageIndex = 1,
    int pageSize = 10)
{

    using (var context = new ApplicationDbContext())
    {
        //create new LogModel object.  this holds all that will be returned to the view
        LogModel logModel = new LogModel();

        //create new LogSelect object. this is the list of drop downs for the activity log table.
        LogSelect logSelect = new LogSelect();

        //query
        var result = from log in context.Logs
                     join user in context.Users on log.UserId equals user.Id into userlog
                     from user in userlog.DefaultIfEmpty()
                     orderby log.LogDate descending
                     select new Models.UserActivity { Log = log, User = user }; //this could bring back null user objects.

        //populate items for drop down lists
        logSelect.LogUsers = result.Select(u => new LogUser { Name = u.User.FirstName + " " + u.User.LastName, Id = u.User.Id }).Distinct().ToList();
        logSelect.LogTypes = result.Select(t => t.Log.Type).Distinct().ToList();
        logSelect.LogLevels = result.Select(t => t.Log.Level).Distinct().ToList();

        //filters
        if (!String.IsNullOrEmpty(userFilter)) { result = result.Where(r => r.User.UserName.Equals(userFilter)); }
        if (!String.IsNullOrEmpty(typeFilter)) { result = result.Where(r => r.Log.Type.Equals(typeFilter)); }
        if (!String.IsNullOrEmpty(levelFilter)) { result = result.Where(r => r.Log.Level.Equals(levelFilter)); }
        if (startDate.HasValue) { result = result.Where(r => r.Log.LogDate >= startDate); }
        if (endDate.HasValue) { result = result.Where(r => r.Log.LogDate <= endDate); }

            //always default sorting by date desc
            result = result.OrderByDescending(r => r.Log.LogDate);

            //sorting (any selection other than date sorting will be sorted secondarily by date descending)
            switch (sortOrder)
            {
                case "date_asc":
                    result = result.OrderBy(r => r.Log.LogDate);
                    break;
                case "date_desc":
                    result = result.OrderByDescending(r => r.Log.LogDate);
                    break;
                case "user_asc":
                    result = result.OrderBy(r => r.User.FirstName)
                                   .ThenByDescending(r => r.Log.LogDate);
                    break;
                case "user_desc":
                    result = result.OrderByDescending(r => r.User.FirstName)
                                   .ThenByDescending(r => r.Log.LogDate);
                    break;
                case "type_asc":
                    result = result.OrderBy(r => r.Log.Type)
                                   .ThenByDescending(r => r.Log.LogDate);
                    break;
                case "type_desc":
                    result = result.OrderByDescending(r => r.Log.Type)
                                   .ThenByDescending(r => r.Log.LogDate);
                    break;
                case "level_asc":
                    result = result.OrderBy(r => r.Log.Level)
                                   .ThenByDescending(r => r.Log.LogDate);
                    break;
                case "level_desc":
                    result = result.OrderByDescending(r => r.Log.Level)
                                   .ThenByDescending(r => r.Log.LogDate);
                    break;
                default:
                    break;
            }

        //turn result into paginated list
        PaginatedList<UserActivity> pgList = PaginatedList<UserActivity>.Create(result.AsNoTracking(), pageIndex, pageSize);

        //fill view model.
        logModel.LogSelect = logSelect;
        logModel.UserActivity = pgList;

        //return model to the view
        return PartialView("_Activity", logModel);
    }
}

Explanation: When you click <a id="log-dropdown-user-sort"><img.../></a> in the _Activity partial view, the javascript click event $('#partial_Activity').on('click', '#log-dropdown-user-sort', function (e) {} fires and calls the setSort(DOM element, string) function. The setSort() function sets the "sort" attribute of the passed element, toggling the attribute value between 'asc' and 'desc', thus influencing future logic flow within the function, and returns a concatenated string value that will be used as the sortOrder parameter within the GetActivity GET method, which is called next. The GetActivity GET method then of course queries and returns the data sorted based on sortOrder argument evaluated by the switch statement.

Tutorial: Add sorting, filtering, and paging with the Entity Framework , tutorial, you: Add column sort links; Add a Search box; Add paging; Create an About page Last Name ascending, descending, ascending. GridView sorting: SortDirection always Ascending alternates between Ascending and Descending. the DataView.Sort and DataGrid.Sort methods unreliable when

just put code - "order": [[6, "asc"]] in your .js file. where 6 represent placeNumber of column. for eg 1.id, 2.name. etc.

Tutorial: Add sorting, filtering, and paging, Prerequisites; Add column sort links; Add a Search box; Add paging to repeatedly toggles between ascending and descending sort order. Therefore, this code results in a single query that's not executed until the The text box will let you enter a string to search for in the first name and last name fields. ascending order D descending order E control fields to be modified. Specify E if you include an E61 user exit to modify control fields before the program sorts them. After an E61 user exit modifies the control fields, DFSORT collates the records in ascending order using the formats specified. 3

For Razor pages, I just added an if-else statement and a "CurrentSortDir" var and "ToggleSortDir" var to the page's model.

So my "index.cshtml":

         <th>
            <a asp-page="./Index" 
            asp-route-sortOrder="ManufacturerPN"
            asp-route-sortDir = "@Model.ToggleSortDir">
                @Html.DisplayNameFor(model => model.Inventory[0].ManufacturerPN)
            </a>
          </th>

And my index.cshtml.cs:

public string CurrentSortDir { get; set; }
public string ToggleSortDir {get; set; }
public async Task OnGetAsync(string sortOrder, string sortDir, string currentFilter, string searchString, int? pageIndex)
    {
        CurrentSort = sortOrder;
        CurrentSortDir = sortDir;
  if (CurrentSortDir == "DESC")
        {
            ToggleSortDir = "ASC";
            switch (sortOrder)
            {
                case "ManufacturerPN":
                    Items = Items.OrderByDescending(s => s.ManufacturerPN);
                    break;
             ....
             }
         }
         else
         {
           ToggleSortDir = "DESC";
            switch (sortOrder)
            {
                case "ManufacturerPN":
                    Items = Items.OrderBy(s => s.ManufacturerPN);
                    break;
               ....
             }
           }

You'll end up with a global sort direction... but I decided that was OK. Otherwise you'd need a separate var for each column and then add if else in each case.

sorting based on multiple columns, I want to do sorting data based on 3 columns, but my data is sorting only on 1 field i.e. is User Name. Apr 22, 2015 02:40 PM|scala_1988|LINK How can I do sorting of data on all the above fields (ascending/descending)? Since you are only using a single sortOrder parameter within your method, you  You can also use query syntax and say: var hold = (from x in MyList orderby x.StartDate, x.EndDate descending select x).ToList(); ThenByDescending is an extension method on IOrderedEnumerable which is what is returned by OrderBy. See also the related method ThenBy.

Implementing Sorting and Paging in ASP.NET MVC, Such a table also needs facilities such as sorting the data based on a One needs to either use a third-party helper or make some custom should toggle the sort order between ascending and descending. by the sorting and paging features, such as the field on which the data is Extra Reading Links. The SQL ORDER BY Keyword The ORDER BY keyword is used to sort the result-set in ascending or descending order. The ORDER BY keyword sorts the records in ascending order by default. To sort the records in descending order, use the DESC keyword.

Sorting - Microsoft Access Basics, If you simply wish to sort your data on a single field (such as “Title” in the columns and again choosing “Ascending” or “Descending” from the  Reorders, in ascending or descending order, records in a feature class or table based on one or multiple fields. The reordered result is written to a new dataset. Learn more about how Sort works. Illustration Usage. Feature classes can be spatially reordered, or sorted. The Shape field must be used as the sort field for spatial sorting. There

Sorting Data - SPSS Tutorials, Sorting data allows us to re-organize the data in ascending or descending order with respect to a specific variable. Some procedures in SPSS 

Comments
  • So for every additional header I would create a new ViewBag value and add it to the switch statement?
  • Correct, so far allways used this in my projects and worked like a charm. Hope it helps you get on your way.
  • For a useful answer this reaction needs to be extended. Explain why this is an answer to the question.