Sorting in WebAPI - a generic way to apply sorting

In this post you will learn how to implement sorting functionality in WebAPI so that client can ask for sorted data by sending columns names through query string. Client can send more than one short parameters and request for ascending or descending data in response. Sorting functionality should be generic enough so that we can reuse it.


Data source

So, let's get started. First of all, let's look at the sample data we will be using to demo this functionality.

    public class StudentController : ApiController
    {
        // Data source
        List<Student> db = new List<Student>();
        public StudentController()
        {
            db = new List<Student>
            {
                new Student { Id = 1, Name = "Abhimanyu K Vatsa", City = "Bokaro" },
                new Student { Id = 2, Name = "Deepak Kumar Gupta", City = "Bokaro" },
                new Student { Id = 3, Name = "Manish Kumar", City = "Muzaffarpur" },
                new Student { Id = 4, Name = "Rohit Ranjan", City = "Patna" },
                new Student { Id = 5, Name = "Shiv", City = "Motihari" },
                new Student { Id = 6, Name = "Rajesh Singh", City = "Dhanbad" },
                new Student { Id = 7, Name = "Staya", City = "Bokaro" },
                new Student { Id = 8, Name = "Samiran", City = "Chas" },
                new Student { Id = 9, Name = "Rajnish", City = "Bokaro" },
                new Student { Id = 10, Name = "Mahtab", City = "Dhanbad" },
            };
        }

        // rest code goes here
       
    }

In above code you can see we have a very simple student object with pre-populated data through constructor. You may have other data sources or you may be using some sort of repository, in either case you are good.


Back to basics

Now, let's go ahead and write a very simple GET method which will return all the data.

    public IHttpActionResult Get()
    {
        return Ok(db);
    }

So, above method will return all the records with status code 200 OK, here's the output:



You might notice, chrome renders XML data by default and in same case IE asks to save JSON file. Basically, WebAPI can send JSON as well as XML repose and we can configure it. In my case, I just removed XML support which made JSON as output on Chrome (above screenshot). Notice, I also reformatted the JSON response. To do this I used following code:


Now, let's move to our original discussions.


Implementing field level data selection

I'm going to add this functionality in above/existing GET method. So our client will pass comma separated column names using query string variable sort. The client request URLs will look following:

2.  http://localhost:36140/api/student?sort=-id (order by id descending)
3.  http://localhost:36140/api/student?sort=id,name (order by id then by name)

Notice - sign is being used for descending sort. If client does not pass query string variable sort, service should return the default order from database. So, here's the new GET method with all said functionality.

    public IHttpActionResult Get(string sort = "id")
    {
        // Convert data source into IQueryable
        // ApplySort method needs IQueryable data source hence we need to convert it
        // Or we can create ApplySort to work on list itself
        var data = db.AsQueryable();

        // Apply sorting
        data = data.ApplySort(sort);

        // Return response
        return Ok(data);
    }

Now let’s understand above code point by point.

1.  sort is a parameter of GET method, which will be assigned from query string value
2.  sort parameter’s default value is id (id column) so in case no query string is supplied, the default will be used
3.  My data source is list so I converted it into IQueryable and there are two major reasons behind this. First, I will be writing ApplySort method in the next line which is a generic method to work with any kind of collection or type. Second, after sorting you will have opportunity to send dependent records to client aka eager and lazy loading concepts. It’s totally up to you how you take it forward but service should be entirely generic.
4. Once we have data source and sort string (from query string), we can pass it to our generic method which we will see next.

In above code I’m using ApplySort generic method, here’s the code:

    public static class IQueryableApplySortExtension
    {
        public static IQueryable<T> ApplySort<T>(this IQueryable<T> source, string strSort)
        {
            if(source == null)
            {
                throw new ArgumentNullException("source", "Data source is empty.");
            }

            if(strSort == null)
            {
                return source;
            }

            var lstSort = strSort.Split(',');

            string sortExpression = string.Empty;

            foreach(var sortOption in lstSort)
            {
                if(sortOption.StartsWith("-"))
                {
                    sortExpression = sortExpression + sortOption.Remove(0, 1) + " descending,";
                }
                else
                {
                    sortExpression = sortExpression + sortOption + ",";
                }
            }

            if(!string.IsNullOrWhiteSpace(sortExpression))
            {
                // Note: system.linq.dynamic NuGet package is required here to operate OrderBy on string
                source = source.OrderBy(sortExpression.Remove(sortExpression.Count() - 1));
            }

            return source;
        }
    }

Now let’s understand code point by point:

1.  It’s a generic method accepting data source and sort string in parameters.
2.  It checks data source or sort string should not be empty.
3.  If data source and sort string are not empty then this splits the sort string into lstSort array and then loops on each item in the array. After that a new variable sortExpression is defined and assigned empty string.
4.  Inside foreach loop, if condition will check if sort string item has - sign, if yes then this will apply sort in descending order and if no then default which is ascending will work. In case of descending I’m adding extra string ‘descending’ in sortExpression. Also, removing the - part from string, because this is not required in data query.
5. Once we have sortExpression assigned inside foreach loop, we will check if this is still null or white space. If not, then source (records) will be re-assigned with new order.
6. Notice the commented message, by default OrderBy expression accepts System.Linq.Expressions.Expression type:

But we need this OrderBy accepts string type because we have sort conditions in sortExpression which is of string type. In order to do this, we need to install a very useful NuGet package which been around long time that is System.Linq.Dynamic. Let’s install that to add extra power in OrderBy expression.


Once this package is installed, add a new namespace using System.Linq.Dynamic; and you are good to go. Now you will see following:


7.  Notice in the body of OrderBy expression I’m removing the leading comma sign from sortExpression.
8. Once OrderBy is applied, I am re-assigning source variable with sorted data and sending it back to WebAPI action which will return data with 200 OK status code.

Here’s the running application screenshots:


In case you would like to try out this project, I published the code on GitHub.

Hope this helps.

Comments

Popular posts from this blog

Migrating database from ASP.NET Identity to ASP.NET Core Identity

Customize User's Profile in ASP.NET Identity System