Sortierungserweiterung für MVC (auf Basis von MvcPaging)

Wie im letzten Beitrag schon erwähnt, ist im dem MvcPaging-Projekt keine Möglichkeit für Sortierung und Filterung vorhanden. Dies habe ich mit ein paar zusätzlichen Codezeilen ergänzt.

Notwendig ist zuerst eine Klasse, welche die Metadaten des Sortierens repräsentiert. Hier habe ich eine Enumeration für die Richtung und eine Klasse für die Aufnahme von Richtung und zu sortierender Spalte erstellt. Zusätzlich enthält die Klasse eine Parse-Methode, um aus einem übergebenen String die Werte zu extrahieren. So ein String wird z.B. im QueryString der URL übergeben. Ein Beispiel dafür findet sich weiter unten in der Funktion SortQuery.

[csharp]
public enum SortDirection
{
Ascending,
Descending
}

public class SortOption
{
public const char SEPARATOR_CHAR = ‚:‘;
public const string AscendingString = „asc“;
public const string DescendingString = „desc“;

public string ColumnName { get; set; }
public SortDirection Direction { get; set; }

public SortOption()
{
}

public SortOption(string columnName, SortDirection direction)
{
ColumnName = columnName;
Direction = direction;
}

public static SortOption Parse(string value)
{
if (!value.Contains(SEPARATOR_CHAR))
throw new ArgumentException(„Der übergebene Wert enspricht nicht den Spezifikationen.“, value);
var array = value.Split(new char[]{SEPARATOR_CHAR});

if (array.Length != 2)
throw new ArgumentException(„Der übergebene Wert enspricht nicht den Spezifikationen.“, value);

SortOption sort = new SortOption();
sort.ColumnName = array[0];
switch (array[1])
{
case AscendingString:
sort.Direction = SortDirection.Ascending;
break;
case DescendingString:
sort.Direction = SortDirection.Descending;
break;
default:
throw new ArgumentException(„Der übergebene Wert enspricht nicht den Spezifikationen.“, value);
}
return sort;
}
}
[/csharp]

Um die Spalte in einer IQueryable auch manuell einfach sortieren zu können, gibt es bei Microsoft eine Vorlage, die ich verwendet habe, um eine weitere Extension für das Interface zu erstellen. Damit kann man durch Angabe von Eigenschaft und Richtung die Sortierung durchführen.

[csharp]
///

/// Orders a datasource by a property with the specified name in the specified direction
///

/// The datasource to order
/// The name of the property to order by
/// The direction
public static IQueryable OrderBy(this IQueryable datasource
, string propertyName, SortDirection direction)
{
//http://msdn.microsoft.com/en-us/library/bb882637.aspx

if (string.IsNullOrEmpty(propertyName))
{
return datasource;
}

var type = typeof(T);
var property = type.GetProperty(propertyName);

if (property == null)
{
throw new InvalidOperationException(string.Format(„Could not find a property called ‚{0}‘ on type {1}“, propertyName, type));
}

var parameter = Expression.Parameter(type, „p“);
var propertyAccess = Expression.MakeMemberAccess(parameter, property);
var orderByExp = Expression.Lambda(propertyAccess, parameter);

const string orderBy = „OrderBy“;
const string orderByDesc = „OrderByDescending“;

string methodToInvoke = direction == SortDirection.Ascending ? orderBy : orderByDesc;

var orderByCall = Expression.Call(typeof(Queryable),
methodToInvoke,
new[] { type, property.PropertyType },
datasource.Expression,
Expression.Quote(orderByExp));

return datasource.Provider.CreateQuery(orderByCall);
}
[/csharp]

Damit sind wir fast fertig. Es fehlt nur noch die komfortable Möglichkeit zur Einbindung in unsere MVC-Views. Die nachfolgende HtmlHelper-Extension erstellt einen Link, der je nach Übergabe die URL zusammenbaut. Hierbei werden auch RouteValues, wie z.B. Suchwerte, mit berücksichtigt.

[csharp]
public static MvcHtmlString SortLink(this HtmlHelper helper, string title, string action, string controller
, string columnName, string sortIndexName, RouteValueDictionary routeValues, string cssClass, string ascImageUrl, string descImgUrl)
{
var sort = helper.ViewData[sortIndexName] as SortOption;
object originalSort = null;
var link = new TagBuilder(„a“);

if (!string.IsNullOrWhiteSpace(cssClass))
link.AddCssClass(cssClass);

if (routeValues != null && routeValues.ContainsKey(„sort“))
{
originalSort = routeValues[„sort“];
routeValues.Remove(„sort“);
}

if (sort != null && columnName == sort.ColumnName)
{
var img = new TagBuilder(„img“);
img.MergeAttribute(„alt“, sort.Direction == SortDirection.Descending ? „absteigend“ : „aufsteigend“);
link.MergeAttribute(„title“, sort.Direction == SortDirection.Ascending ? „Spalte absteigend sortieren“ : „Spalte aufsteigend sortieren“);
img.MergeAttribute(„src“, sort.Direction == SortDirection.Descending ? descImgUrl : ascImageUrl);
routeValues.Add(„sort“, string.Format(„{0}{1}{2}“, columnName, SortOption.SEPARATOR_CHAR, sort.Direction == SortDirection.Ascending ? SortOption.DescendingString : SortOption.AscendingString));
link.InnerHtml = title + „ “ + img.ToString(TagRenderMode.SelfClosing);
}
else
{
link.MergeAttribute(„title“, „Spalte aufsteigend sortieren“);
routeValues.Add(„sort“, string.Format(„{0}{1}{2}“, columnName, SortOption.SEPARATOR_CHAR, SortOption.AscendingString));
link.InnerHtml = title;
}

string url = UrlHelper.GenerateUrl(null, action, controller, routeValues, helper.RouteCollection, helper.ViewContext.RequestContext, true);
link.Attributes.Add(„href“, url);

routeValues.Remove(„sort“);
if (originalSort != null)
routeValues.Add(„sort“, originalSort);

return MvcHtmlString.Create(link.ToString());
}
[/csharp]

In der View ist die Verwendung dann wieder sehr einfach, auch wenn dieses Mal doch einige Parameter angegeben werden müssen. Ein Beispiel für eine Einbindung habe ich mal hier dargestellt.

[html]

[/html]

Um das Sortieren überall einfach einbinden und verwenden zu können, habe ich eine generische Methode in meinem Basiscontroller erstellt. Diese gibt eine sortiere IQueryable vom übergebenen Typ zurück. Die Sortierinformationen werden in das übergebene RouteValueDictionary geschrieben und beim Erzeugen der Sortierlinks (s.o.) ausgewertet.

[csharp]
protected IQueryable SortQuery(string sortoption,
IQueryable query, RouteValueDictionary valueDic)
{
if (!string.IsNullOrWhiteSpace(sortoption))
{
SortOption sort = SortOption.Parse(sortoption);
if (valueDic != null)
{
valueDic.Add(„sort“, sortoption);
ViewData.Add(„sort“, sort);
}
return query.OrderBy(sort.ColumnName, sort.Direction);
}
else
return query;
}

//Und die Verwendung in einer Controllermethode
public ActionResult Index (string movieTitle)
{
IQueryable movies;
movies = InitDataSource(movieTitle, valueDic);
//…
movies = SortQuery(sort, movies, valueDic);
return View(movies);
}
[/csharp]

So dann, viel Spaß und happy sorting.

Kommentar verfassen

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Abmelden /  Ändern )

Google Foto

Du kommentierst mit Deinem Google-Konto. Abmelden /  Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden /  Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Abmelden /  Ändern )

Verbinde mit %s