Course Content
ASP.NET Core Web API Fundamentials
0/2
Method Safety And Method Idempotency
0/1
Working With OPTIONS and HEAD Requests
0/2
Root Document in ASP.NET Core Web API
0/1
Ultimate ASP.NET Core Web API

As with everything else so far, we need to extend our RequestParameters class first to be able to send requests with the orderBy clause:

public abstract class RequestParameters
{
    const int maxPageSize = 50;
    public int PageNumber { get; set; } = 1;

    private int _pageSize = 10;
    public int PageSize
    {
        get
        {
            return _pageSize;
        }
        set
        {
            _pageSize = (value > maxPageSize) ? maxPageSize : value;
        }
    }

    public string? OrderBy { get; set; }
}

The only thing we’ve added is the OrderBy property and we added it to the RequestParameters class because we can reuse it for other entities. We want to sort our results by name, even if it hasn’t been stated explicitly in the request.

That said, let’s modify the EmployeeParameters class to enable the default sorting condition for Employee if none was stated:

public class EmployeeParameters : RequestParameters
{
    public EmployeeParameters() => OrderBy = "name";

    public uint MinAge { get; set; }
    public uint MaxAge { get; set; } = int.MaxValue;

    public bool ValidAgeRange => MaxAge > MinAge;

    public string? SearchTerm { get; set; }
}

Next, we will dive right into the implementation of our sorting mechanism, or rather, our ordering mechanism. One thing to note is that we’ll be using the System.Linq.Dynamic.Core NuGet package to dynamically create our OrderBy query on the fly. So, feel free to install it in the Persistence project and add a using directive in the RepositoryEmployeeExtensions class:

using System.Linq.Dynamic.Core;

Now, we can add a new extension method Sort in our RepositoryEmployeeExtensions class:

public static IQueryable<Employee> Sort(this IQueryable<Employee> employees, 
    string orderByQueryString)
{
    if (string.IsNullOrWhiteSpace(orderByQueryString))
        return employees.OrderBy(e => e.Name);

    var orderParams = orderByQueryString.Trim().Split(',');
    var propertyInfos = typeof(Employee).GetProperties(BindingFlags.Public | BindingFlags.Instance);
    var orderQueryBuilder = new StringBuilder();

    foreach (var param in orderParams)
    {
        if (string.IsNullOrWhiteSpace(param))
            continue;

        var propertyFromQueryName = param.Split(" ")[0];
        var objectProperty = propertyInfos.FirstOrDefault(pi => 
            pi.Name.Equals(propertyFromQueryName, StringComparison.InvariantCultureIgnoreCase));

        if (objectProperty is null)
            continue;

        var direction = param.EndsWith(" desc") ? "descending" : "ascending";

        orderQueryBuilder.Append($"{objectProperty.Name.ToString()} {direction}, ");
    }

    var orderQuery = orderQueryBuilder.ToString().TrimEnd(',', ' ');

    if (string.IsNullOrWhiteSpace(orderQuery))
        return employees.OrderBy(e => e.Name);

    return employees.OrderBy(orderQuery);
}

Okay, there are a lot of things going on here, so let’s take it step by step and see what exactly we’ve done.

Step By Step Explanation

First, let’s start with the method definition. It has two arguments — one for the list of employees as IQueryable<Employee> and the other for the ordering query. If we send a request like this one: https://localhost:5001/api/companies/companyId/employees?orderBy=name,age desc, our orderByQueryString will be name,age desc. We begin by executing some basic checks against the orderByQueryString. If it is null or empty, we just return the same collection ordered by name:

if (string.IsNullOrWhiteSpace(orderByQueryString))
    return employees.OrderBy(e => e.Name);

Next, we are splitting our query string to get the individual fields:

var orderParams = orderByQueryString.Trim().Split(',');

We’re also using a bit of reflection to prepare the list of PropertyInfo objects that represent the properties of our Employee class. We need them to be able to check if the field received through the query string exists in the Employee class:

var propertyInfos = typeof(Employee).GetProperties(BindingFlags.Public | BindingFlags.Instance);

That prepared, we can run through all the parameters and check for their existence:

if (string.IsNullOrWhiteSpace(param))
    continue;

var propertyFromQueryName = param.Split(" ")[0];
var objectProperty = propertyInfos.FirstOrDefault(pi => 
    pi.Name.Equals(propertyFromQueryName, StringComparison.InvariantCultureIgnoreCase));

If we don’t find such a property, we skip the step in the foreach loop and go to the next parameter in the list:

if (objectProperty is null)
    continue;

If we do find the property, we return it and check if our parameter contains “desc” at the end of the string. We use that to decide how we should order our property:

var direction = param.EndsWith(" desc") ? "descending" : "ascending";

To build our query with each loop, we use the StringBuilder variable:

orderQueryBuilder.Append($"{objectProperty.Name.ToString()} {direction}, ");

Now that we’ve looped through all the fields, we are just removing excess commas and doing one last check to see if our query indeed has something in it:

var orderQuery = orderQueryBuilder.ToString().TrimEnd(',', ' ');

if (string.IsNullOrWhiteSpace(orderQuery))
    return employees.OrderBy(e => e.Name);

Finally, we can order our query:

return employees.OrderBy(orderQuery);

At this point, the orderQuery variable should contain the “Name ascending, DateOfBirth descending” string. That means it will order our results first by Name in ascending order, and then by DateOfBirth in descending order.

The standard LINQ query for this would be:

employees.OrderBy(e => e.Name).ThenByDescending(o => o.Age);

This is a neat little trick to form a query when you don’t know in advance how you should sort. Once we have done this, all we have to do is modify this part of the GetEmployeesAsync repository method:

var employeesQuery = FindByCondition(e => e.CompanyId.Equals(companyId), trackChanges)
    .FilterEmployees(employeeParameters.MinAge, employeeParameters.MaxAge)
    .Search(employeeParameters.SearchTerm!)
    .Sort(employeeParameters.OrderBy!)

We call the Sort() method and remove the OrderBy() method. We can test this functionality now.

0% Complete