I have a class which has approx 50 properties, instances of this class is added to a list. This list is then added to a Velocity context. Now, I would like to sort this data. Which field, or if it is ascending or descending is not known until the template is being parsed.

Resources I've looked into:

Better way to use Velocity's GenericTools in a Standalone app?

Velocity foreach sort list

http://velocity.apache.org/tools/devel/generic/

Based on the resources listed here I can't figure out how to solve this. Is the GenericTools available for the Castle's Nvelocity? If not, how may I implement such a generic sort I'm asking for here?

有帮助吗?

解决方案

My solution was to write my own sort-class and add this as a context to nvelocity. I'm passing the field to sort on as string and accessing it as reflection. I'm also setting sort ascending or descending by string value. I'm also passing in the name of the comparer and accessing this with reflection as well. I'm then using the List method OrderBy or OrderByDescending with the choosen field and comparer.

I did find parts of the code here: http://zootfroot.blogspot.co.uk/2009/10/dynamic-linq-orderby.html

public class NvelocitySort
{
    public List<MyObject> Sort(List<MyObject> list, string fieldAndMode, string comparerName)
    {
        fieldAndMode = fieldAndMode.Trim();

        // Split the incoming string to get the field name and sort ascending or descending
        string[] split = fieldAndMode.Split(' ');
        // Set default sort mode
        string mode = "asc";
        // If sort mode not specified, this will be the field name
        string field = fieldAndMode;
        // If sort mode added split length shall be 2
        if (split.Length == 2)
        {
            field = split[0];
            if (split[1].ToLower() == "asc" || split[1].ToLower() == "ascending") mode = "asc";
            if (split[1].ToLower() == "desc" || split[1].ToLower() == "descending") mode = "desc";
        }
        // If length is more than 2 or 0, return same list as passed in
        else if (split.Length > 2 || split.Length == 0)
        {
            return list;
        }

        // Get comparer based on comparer name
        IComparer<string> comparer = (IComparer<string>)Activator.CreateInstance(Type.GetType(string.Format("Namespace.{0}", comparerName)));

        // Choose the sort order
        if (mode == "asc")
            return list.OrderBy(item => item.GetReflectedPropertyValue(field), comparer).ToList();
        if (mode == "desc")
            return list.OrderByDescending(item => item.GetReflectedPropertyValue(field), comparer).ToList();

        // If sort order not asc/desc return same list as passed in
        return list;
    }
}

This is the reflection method for retrieving the field.

public static string GetReflectedPropertyValue(this object subject, string field)
{
    object reflectedValue = subject.GetType().GetProperty(field).GetValue(subject, null);
    return reflectedValue != null ? reflectedValue.ToString() : "";
}

Simple comparer example:

public class TextComparer : IComparer<string>
{
    public int Compare(string x, string y)
    {
        return string.Compare(x, y);
    }
}

Added to Nvelocity context like this:

this.velocityContext.Put("sorter", new NvelocitySort());

Accessed from Nvelocity template like this:

#foreach($item in $sorter.Sort($listObject, "Name desc", "TextComparer"))  
$item.Name
#end

Hope it helps someone else...

EDIT: Found an even better way to do it (implements multiple fields sorting): http://www.codeproject.com/Articles/280952/Multiple-Column-Sorting-by-Field-Names-Using-Linq

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top