ASP.NET MVC oraz sortowanie wyników po kolumnach

W jednym z poprzednich postów pokazałem jak zrealizować stronicowanie wyników. Kontrolka ListView z czystego ASP.NET miała jeszcze jedną zaletę – możliwość sortowania wyników po kolumnach. W dzisiejszym poście zaprezentuję sposób uzyskania takiego efektu w ASP.NET MVC w którym jak wiemy nie ma żadnych wbudowanych kontrolek.

image 

Dla zwizualizowania, efekt końcowy pokazuję na powyższym screenie. Mamy kilka kolumn, które są linkami. Po kliknięciu na daną kolumnę chcemy aby wyniki były posortowane za pomocą tej kolumny. Po drugim kliknięciu w tą samą kolumnę kierunek sortowania zostaje odwrócony (z DESC na ASC i z ASC na DESC).

Zdecydowałem się na napisanie metody rozszerzającej HtmlHelper:

public static class HtmlHelper
{
   public static string SortableColumn(this System.Web.Mvc.HtmlHelper htmlHelper, string linkText, string columnName, object routeValues)
   {
       return SortableColumn(htmlHelper, linkText, columnName, routeValues, "sord", "sidx",null);
   }
   public static string SortableColumn(this System.Web.Mvc.HtmlHelper htmlHelper, string linkText, string columnName, object routeValues, string sortTypeParName, string sortColumnParName)
   {
       return SortableColumn(htmlHelper, linkText, columnName, routeValues, sortTypeParName, sortColumnParName);
   }
   public static string SortableColumn(this System.Web.Mvc.HtmlHelper htmlHelper, string linkText, string columnName, object routeValues,string sortTypeParName,string sortColumnParName,string anchorName)
   {
       System.Web.Routing.RouteData data = htmlHelper.ViewContext.Controller.ControllerContext.RouteData;
       string actionName = data.GetRequiredString("action");
       
       StringBuilder sb = new StringBuilder();
       var vals = new RouteValueDictionary(routeValues);
       string sidx = String.Empty;
       if (System.Web.HttpContext.Current.Request[sortColumnParName] != null)
       {
           sidx = System.Web.HttpContext.Current.Request[sortColumnParName].ToString();
       }
       //modify the sidx       
       if (vals.ContainsKey(sortColumnParName) == false)
       {
           vals.Add(sortColumnParName, columnName);
       }
       else
       {
           vals[sortColumnParName] = columnName;
       }
       
       string sord = String.Empty;
       if (System.Web.HttpContext.Current.Request[sortTypeParName] != null)
       {
           sord = System.Web.HttpContext.Current.Request[sortTypeParName].ToString();
       }
       //add the sord key if needed      
       if (vals.ContainsKey(sortTypeParName) == false)
       {
           vals.Add(sortTypeParName, String.Empty);
       }
       //if column matches    
       if (sidx.Equals(columnName, StringComparison.CurrentCultureIgnoreCase) == true)
       {
           if (sord.Equals("asc", StringComparison.CurrentCultureIgnoreCase) == true)
           {
               //draw the ascending sort indicator using the wingdings font.  
               sb.Append(" <img src='/images/asc.png' alt='asc'/>");
               vals[sortTypeParName] = "desc";
           }
           else
           {
               sb.Append(" <img src='/images/desc.png' alt='desc'/>");
               vals[sortTypeParName] = "asc";
           }
       }
       else
       {
           vals[sortTypeParName] = "asc";                
       }
       if(anchorName==null)
           sb.Insert(0, System.Web.Mvc.Html.LinkExtensions.ActionLink(htmlHelper, linkText, actionName,vals));
       else
           sb.Insert(0, System.Web.Mvc.Html.LinkExtensions.ActionLink(htmlHelper, linkText, actionName, null, null, null, anchorName, vals, null));

       return sb.ToString();
   }
}

Powyższy kod jest przeróbką przykładu znalezionego na jednym z serwisów (nie pamiętam już niestety nazwy). Metoda przyjmuje kilka parametrów:

  1. object routeValues – tak samo jak w przypadku ActionLink (dodatkowe parametry do przekazania w URL).
  2. string sortTypeParName – nazwa parametru QueryString, który będzie przechowywać typ sortowania.
  3. string sortColumnParName – nazwa parametru QueryString, który będzie przechowywać nazwę kolumny po której należy sortować.
  4. string anchorName – dodatkowy parametr przechowujący nazwę kotwicy (np. #comments). Jeśli przekażemy null metoda zignoruje po prostu kotwicę.

Sposób wykorzystania metody jest zdecydowanie łatwiejszy. W widoku wystarczy:

<table class="tableList">
    <tr>
       
        <th><%=Html.SortableColumn("Obietnica", "ModificationDate", null, "promisesSortType", "promisSortColumnName", "promises")%></th>                 
        <th><%=Html.SortableColumn("Źródło", "Source", null, "promisesSortType", "promisSortColumnName", "promises")%></th>
    </tr>
    ...
</table>

W powyższym przykładzie ostatni parametr (promises) odpowiada za dołączenie kotwicy #promises. SortableColumn wygeneruje odpowiedni link z tekstem i obrazkiem symbolizującym kierunek sortowania (zielona strzałka na pierwszym screenie).

Pozostało jeszcze pokazać zawartość kontrolera, który odpowiada za faktyczne posortowanie danych:

public ActionResult Details(Guid id)
{  
  string promisesOrderString = (Request.QueryString["promisesSortColumnName"] ?? "ModificationDate") + " " + (Request.QueryString["promisesSortType"] ?? "asc");
    
  PromisesRepository promisesRepository = new PromisesRepository();
  IQueryable<Promises> allOrderedPromises= promisesRepository.GetAllOrderedBy()(promisesOrderString);
  
  ...
  return View(viewModel);
}

Kluczem jest wygenerowania string’a w formacie ‘COLUMN_NAME ASC’ lub ‘COLUMN_NAME DESC’. W powyższym kodzie promisesOrderString zawiera właśnie taki  string. Następnie używamy jednej z przeładowanych metod OrderBy i przekazujemy promisesOrderString. Należy zaznaczyć, że domyślnie LINQ nie zawiera metody OrderBy akceptującej jako parametr wejściowy zmienną typu string. Należy więc ją dodać jako rozszerzenie (kod klasy z CodePlex).

One thought on “ASP.NET MVC oraz sortowanie wyników po kolumnach”

  1. To jest w miare zrozumiale i oczywiste. Zastanawiam sie jak bys polaczyl sortowanie z filtrowaniem. Powiedzmy masz filtr nad gridem z lista pracownikow. W filtrze mozesz wpisac imie i nazwisko i po tym przefiltrowac liste, jest do tego guzik szukaj. Lista ma rozne kolumny (imie, nazwisko, pozycja, data zatrudnienia…) po ktorych mozesz sortowac. Powinno to dzialac tak, ze filtr masz aktualizowany tylko wtedy kiedy wcisniesz szukaj. I wtedy jak sortujesz kolumny, to sortujesz tylko te wyniki ktore otrzymales po odfiltrowaniu. Dodatkowo jak przeedytujesz cos w polach filtru ale nie wcisniesz wyszukaj to filtr sie nie zmienia. Szukam takich rozwiazan i jak na razie ciezko cos znalezc. Pozdrawiam

Leave a Reply

Your email address will not be published.