16

I was wondering how people were going about sorting a table in asp.net mvc? I've heard of javascript solutions that work pretty well with non-paged tables, such as jquery's table sorter, but i need a solution that will work with paged tables.

The project I'm working on currently uses the following solution, but I find it very messy.

Controller

public ActionResult Sort(string parameter)
{  

 IEnumerable<IProduct> list;

 if (Session["Model"] != null)
  list = (IEnumerable<IProduct>)Session["Model"]).ToList<IProduct>();
 else
  list = _service.GetAll();

 if (Session["parameter"] == null && Session["sortDirection"] == null)
 {
  //set the parameter and set the sort to desc
  Session["parameter"] = parameter;
  Session["sortDirection"] = "DESC";
 }
 else if (Session["parameter"] != null) //already set so not the first time
 {
  //same parameter sent
  if (Session["parameter"].ToString().Equals(parameter))
  {
   //check sort direction and reverse
   if (Session["sortDirection"].ToString().Equals("DESC"))
    Session["sortDirection"] = "ASC";
   else
    Session["sortDirection"] = "DESC";
  }
  else //different parameter sent
  {
   Session["sortDirection"] = "DESC";
   Session["parameter"] = parameter;
  }
 }

 if (Session["sortDirection"].CompareTo("ASC") == 0)
  list = Models.ContollerHelpers.SortingHelper.OrderBy(list.AsQueryable(), column);
 else
  list = Models.ContollerHelpers.SortingHelper.OrderByDescending(list.AsQueryable(), column);

 return View("Results", list.ToList);
}

Helper

public class Helper()
{
 private static IOrderedQueryable<T> OrderingHelper<T>(IQueryable<T> source, string propertyName, bool descending, bool anotherLevel)
 {
  ParameterExpression param = Expression.Parameter(typeof(T), string.Empty); // I don't care about some naming
  MemberExpression property = Expression.PropertyOrField(param, propertyName);
  LambdaExpression sort = Expression.Lambda(property, param);

  MethodCallExpression call = Expression.Call(
   typeof(Queryable),
   (!anotherLevel ? "OrderBy" : "ThenBy") + (descending ? "Descending" : string.Empty),
   new[] { typeof(T), property.Type },
   source.Expression,
   Expression.Quote(sort));

  return (IOrderedQueryable<T>)source.Provider.CreateQuery<T>(call);
 }

 public static IOrderedQueryable<T> OrderBy<T>(this IQueryable<T> source, string propertyName)
 {
  return OrderingHelper(source, propertyName, false, false);
 }

 public static IOrderedQueryable<T> OrderByDescending<T>(this IQueryable<T> source, string propertyName)
 {
  return OrderingHelper(source, propertyName, true, false);
 }

 public static IOrderedQueryable<T> ThenBy<T>(this IOrderedQueryable<T> source, string propertyName)
 {
  return OrderingHelper(source, propertyName, false, true);
 }

 public static IOrderedQueryable<T> ThenByDescending<T>(this IOrderedQueryable<T> source, string propertyName)
 {
  return OrderingHelper(source, propertyName, true, true);
 }
}

List View

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<IEnumerable<Models.Interface.IProduct>>" %>
<% Session["model"] = Model; %>
 <table>
    <tr>
   <th>
    Edit Details
   </th>
   <th>
    <%=Html.ActionLink("Id","Sort",new {parameter ="Id"}) %>
   </th>
   <th>
    <%=Html.ActionLink("Name", "Sort", new { parameter = "Name"})%>
   </th>
   <th>
    <%=Html.ActionLink("Status", "Sort", new { parameter = "Status" })%>
   </th>
   <th>
    <%=Html.ActionLink("Notes", "Sort", new { parameter = "Notes"})%>
   </th>
  </tr>
  <% foreach (var item in Model){ %>

   <tr>
    <td>
     <%= Html.ActionLink("Edit", "Edit", new {  id=item.Id }) %> |
    </td>
    <td>
     <%= Html.Encode(item.Id) %>
    </td>
    <td>
     <%= Html.Encode(item.Name) %>
    </td>
    <td>
     <%= Html.Encode(item.Status) %>
    </td>
    <td>
     <%= Html.Encode(item.Notes) %>
    </td> 
   </tr>

  <% } %>   
    </table>

Is this the only way of doing something like this? If anyone knows of a nicer way that doesn't involve having all of the records being loaded to a page at once then please link to examples.

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
AlteredConcept
  • 2,602
  • 7
  • 32
  • 36
  • @AlteredConcept the q is too broad, in that u are not saying what u are most concerned with. Is it the view (hardly, since its pretty simple), the way to sort in a generic way with linq, the use of session to hold the parameters, the use of "parameter" like that or its parse. – eglasius Jan 13 '10 at 18:01
  • Sorry about that. The controller is what really bothered me the most. I don't want to repeat all of that code in every controller for each entity list view. That's a lot of duplication of code. I was trying to figure out a way i could place this in a base controller, but was running a blank. So I was looking to see if others had a nicer more elegant approach for sorting – AlteredConcept Jan 13 '10 at 19:32
  • huh? why does your head hurt? – AlteredConcept Jan 18 '10 at 05:55

5 Answers5

11

Check out the DataTables @ DataTables This will let you page the result and query it with easy setup. it works well with ajax and json data. Look at the samples. Hope this will help you out.

Bharat
  • 3,491
  • 1
  • 22
  • 22
  • +1. I have succesfully used DataTables jQuery plugin in ASP.NET MVC application with server-side processing (http://datatables.net/usage/server-side). – Anthony Serdyukov Jan 09 '10 at 06:42
  • 2
    This loads all of the data on to the page on load (even though it pages the data after loading). Try loading 1000+ records at once. It takes a while before the page is displayed. – AlteredConcept Jan 09 '10 at 21:35
  • 1
    Use server-side processing to only send the rows that are shown to the client http://datatables.net/usage/server-side, as Anton pointed out. – Jan Aagaard Jan 13 '10 at 13:08
  • You could also combine this with a PartialView and a controller that returns a JsonResult – Chris S Jan 13 '10 at 13:28
  • can you post a sample of how to do this@chris? – AlteredConcept Jan 17 '10 at 03:59
8

Try the following extension methods (from top of head):

static class OrderByExtender
{
    public static IOrderedEnumerable<T> OrderBy<T>(this IEnumerable<T> collection, string key, string direction)
    {
        LambdaExpression sortLambda = BuildLambda<T>(key);

        if(direction.ToUpper() == "ASC")
            return collection.OrderBy((Func<T, object>)sortLambda.Compile());
        else
            return collection.OrderByDescending((Func<T, object>)sortLambda.Compile());
    }

    public static IOrderedEnumerable<T> ThenBy<T>(this IOrderedEnumerable<T> collection, string key, string direction)
    {
        LambdaExpression sortLambda = BuildLambda<T>(key);

        if (direction.ToUpper() == "ASC")
            return collection.ThenBy((Func<T, object>)sortLambda.Compile());
        else
            return collection.ThenByDescending((Func<T, object>)sortLambda.Compile());
    }

    private static LambdaExpression BuildLambda<T>(string key)
    {
        ParameterExpression TParameterExpression = Expression.Parameter(typeof(T), "p");
        LambdaExpression sortLambda = Expression.Lambda(Expression.Convert(Expression.Property(TParameterExpression, key), typeof(object)), TParameterExpression);
        return sortLambda;
    }
}

Usage:

var products = Session["Model"] as IEnumerable<Product>() ?? _service.GetAll();

return products.OrderBy("Name", "ASC").ThenBy("Price", "DESC");

Assuming you are only using 1 orderby condition at a time you can use:

var products = Session["Model"] as IEnumerable<Product>();

var sortDirection = Session["Direction"] as string ?? "DESC";
Session["Direction"] = sortDirection == "DESC" ? "ASC" : "DESC";
sortDirection = Session["Direction"] as string;

return products.OrderBy(parameter, sortDirection);
Jan Jongboom
  • 26,598
  • 9
  • 83
  • 120
  • Shouldn't direction.ToLower() be direction.ToUpper()? – AlteredConcept Jan 14 '10 at 02:06
  • Correct. Thanks for pointing that out, I wrote this stuff in notepad :-) – Jan Jongboom Jan 14 '10 at 09:12
  • No problem. I like this approach, but i am running into some issues with it. Sometimes it sorts correctly and other times it doesn't (instead of ascending it does descending and vice-versa). – AlteredConcept Jan 14 '10 at 18:48
  • Add a parameter in your url with `order=DESC`/`order=ASC`. Then handle in your frontend whether a click on the header should result in ASC/DESC sorting. Then you can sort like `public ActionResult Sort(string parameter, string order)`. – Jan Jongboom Jan 15 '10 at 09:22
4

If JavaScript is disabled, you have a problem.

I'd go for a noscript solution.

I'd have two radio button groups:

direction:  ( ) ascending    (.) descending

orderBy:  (.) Id   ( ) Name   ( ) Status

I'd treat the View as a form with multiple submit buttons:

(without JavaScript) ~~ same name for both buttons.

on your .aspx page, add three buttons:

 <input type="submit"    value="Requery"   name="submitButton"/>
 <input type="submit"    value="Previous"  name="submitButton"/>
 <input type="submit"    value="Next"      name="submitButton"/>

in your Controller:

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Sort(string direction, string orderBy, string submitButton)
{
    if (submitButton == "Requery")       //et cetera

TMTOWTDI: There's More Than One Way To Do It

George Stocker
  • 57,289
  • 29
  • 176
  • 237
gerryLowry
  • 2,626
  • 5
  • 35
  • 44
0

Definately liking Jan's solution - thanks a lot Jan... You just saved me some 60 lines of code with a case statement parsing each of the column headers. The solution "makes a great toggle click, only sort by 1 column solution on tables", when used with two session variables, one to retain the ASC/DESC as a boolean and one to hold the Type/Column Name.

I used this C# Example Extension and implemented it with VB this afternoon. I managed to cut a 30 line case statement into one line of code.

AT THE VIEW:

<th>Date <a class="clickable" href="<%=Url.Action("SortStationVisits", New With {.SortField = "PlanningDate"})%>"><span class="colSort" style="display: inline-table;"></span></a></th>

THE EXTENSION (Public Module OrderByExtender):

Imports System.Linq.Expressions

Public Function OrderBy(Of T)(collection As IEnumerable(Of T), key As String, isDescending As Boolean) As IOrderedEnumerable(Of T)
    Dim sortLambda As LambdaExpression = BuildLambda(Of T)(key)
    If isDescending Then
        Return collection.OrderByDescending(DirectCast(sortLambda.Compile(), Func(Of T, Object)))
    Else
        Return collection.OrderBy(DirectCast(sortLambda.Compile(), Func(Of T, Object)))
    End If
End Function

Public Function ThenBy(Of T)(collection As IOrderedEnumerable(Of T), key As String, isDescending As Boolean) As IOrderedEnumerable(Of T)
    Dim sortLambda As LambdaExpression = BuildLambda(Of T)(key)

    If (isDescending) Then
        Return collection.ThenByDescending(DirectCast(sortLambda.Compile(), Func(Of T, Object)))
    Else
        Return collection.ThenBy(DirectCast(sortLambda.Compile(), Func(Of T, Object)))
    End If
End Function

Private Function BuildLambda(Of T)(key As String) As LambdaExpression
    Dim TParameterExpression As ParameterExpression = Expression.Parameter(GetType(T), "p")
    Dim sortLambda As LambdaExpression = Expression.Lambda(Expression.Convert(Expression.[Property](TParameterExpression, key), GetType(Object)), TParameterExpression)
    Return sortLambda
End Function

AT THE CONTROLLER ACTION:

Public Function SortStationVisits(Optional page As Integer = 1, Optional SortField As String = "") As ActionResult
    Dim sps = LoadSession()

    If SortField = sps.StationVisitSorter Then
        sps.StationVisitDescOrder = Not (sps.StationVisitDescOrder)
    Else
        sps.StationVisitDescOrder = False
    End If

    sps.StationVisitSorter = SortField

    SaveSession(sps)
    Return RedirectToAction("Show")
End Function

AT THE CONTROLLER SHOW METHOD (1 line of code W00T!) :

spv.SelectableStationVisits = spv.SelectableStationVisits.OrderBy(sps.StationVisitSorter, sps.StationVisitDescOrder).ToList
JoeBrockhaus
  • 2,745
  • 2
  • 40
  • 64
Timi
  • 31
  • 2
0

I prefer the methods described here: http://www.c-sharpcorner.com/UploadFile/camurphy/csharpLists03302006170209PM/csharpLists.aspx

So for ex:

var products = new List<Products>();
products = ProductRepository.GetAll();

// Sort Results
products.Sort(
    delegate(Products p1, Products p2) {
    return p1.Name.CompareTo(p2.Name);
});
ringerce
  • 533
  • 2
  • 12
  • 25