Field level data selection in WebAPI

In this post you will learn how to implement field level data selection functionality in WebAPI so that client can ask for less data by sending individual columns names through query strings variable fields.

Introduction

Service should be smart enough to split command separated column names from query string and convert it into object that can be used with EF query. If client sent no column name, service should return all the columns. If you are interested in this project code, I published on GitHub for you.

The method which will convert comma separated column names into object should be generic enough so that we can reuse it with any type.

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 codes 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 with fields query string variable like http://localhost:36140/api/student?fields=id,name and our service will return only ID and Name data in response. If client does not pass fields query string, service should return all. So, here's the new GET method with all said functionality.

    public IHttpActionResult Get(string fields = null)
    {
        try
        {
            List<string> lstFields = new List<string>();
            if (fields != null)
            {
                lstFields = fields.ToLower().Split(',').ToList();
            }
            //return Ok(db.Select(i => new { i.City, i.Name }));
            return Ok(db.Select(i => CreateShappedObject(i, lstFields)));
        }
        catch (Exception)
        {
            return InternalServerError();
        }
    }

In above code, notice the null assigned to fields parameter this will help to assign null in case of no query string.

In the try block, comma separated fields in query string is being converted into list of fields. Once we have list of fields, we are calling a method CreateShappedObject(context, fieldsList). This method will return response something like the one commented which is new { i.City, i.Name }. The difference between commented and non-commented return is commented is hardcoded with fixed number of columns which will not solve our purpose.

So CreateShappedObject(i, lstFields) should have a way to dynamically create objects this is where ExpandoObject comes in action, let's look at its implementation:

    public object CreateShappedObject(object obj, List<string> lstFields)
    {
        if (!lstFields.Any())
        {
            return obj;
        }
        else
        {
            ExpandoObject objectToReturn = new ExpandoObject();
            foreach (var field in lstFields)
            {
                var fieldValue = obj.GetType()
                    .GetProperty(field, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance)
                    .GetValue(obj, null);

                ((IDictionary<string, object>)objectToReturn).Add(field, fieldValue);
            }

            return objectToReturn;
        }
    }

In above code, first of all we checking lstFields if its empty, return the object as it is. If not, loop through each field in the list to add into IDictionary. At the end, return object into EF select statement.


Look at the different outputs it's working great in every situation.

I shared the code on GitHub also, build it on your system.

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