Fields Filtering in ASP.NET Web API

 In this article I will be talking about the following two things:

  1. Returning only useful fields from the API.
  2. Consuming an API that accepts a comma-separated list of fields.

Returning only useful fields from the API

When you are writing a RESTful web API you often want to allow clients to feed a list of fields to the API that the clients need. The reason is to return only the useful data to the client. Say for example, you have an entity called Product that has many properties. The client may need only a few properties of the Product object. If you return the entire object every time the client asks for a product, it unnecessarily wastes bandwidth and increases the response time. So to avoid that you can accept a list of fields the client wants and return only those. How can you do that?

Here is your Product class:

  1. public class Product : BaseEntity       
  2. {         
  3.     public int? Id { getset; }             
  4.     public string Name { getset; }        
  5.     public double? Price { getset; }     
  6.     public bool? isAvailable { getset; }            
  7.     public int? UnitsInStock { getset; }             
  8.     public string Category { getset; }            
  9.     public int? ShelfLife { getset; } //in days           
  10.   //many more such properties      
  11. }   
The Product class derives from the BaseEntity class as in the following:
  1.     public abstract class BaseEntity    
  2.     {    
  3.         public List<string> serializableProperties { getset; }    
  4.      
  5.         public void SetSerializableProperties(string fields)    
  6.         {    
  7.             if (!string.IsNullOrEmpty(fields))    
  8.             {    
  9.                 var returnFields = fields.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);    
  10.                 serializableProperties = returnFields.ToList();    
  11.                 return;    
  12.             }    
  13.             var members = this.GetType().GetMembers();    
  14.      
  15.             serializableProperties = new List<string>();    
  16.             serializableProperties.AddRange(members.Select(x => x.Name).ToList());    
  17.         }    
  18.     }    
The BaseEntity class is an abstract class that has a list of strings as a member property. This list of strings specifies the fields of the class that should be serialized when returning the object of that class.

The SetSerializableProperties method accepts a comma-separated list of properties and fills this list.

Now, when returning the result from the API, we will be using JSON .NET serialize to return the required data. In order to serialize only the required properties, we'll write a custom ContractResolver.
  1. public class ShouldSerializeContractResolver : DefaultContractResolver    
  2. {    
  3.       
  4.     protected override JsonProperty CreateProperty(System.Reflection.MemberInfo member, Newtonsoft.Json.MemberSerialization memberSerialization)    
  5.     {    
  6.         var property = base.CreateProperty(member, memberSerialization);    
  7.         if (property.DeclaringType == typeof(BaseEntity) || property.DeclaringType.BaseType == typeof(BaseEntity))    
  8.         {    
  9.             if (property.PropertyName == "serializableProperties")    
  10.             {    
  11.                 property.ShouldSerialize = instance => { return false; };    
  12.             }    
  13.             else    
  14.             {    
  15.                 property.ShouldSerialize = instance =>    
  16.                 {    
  17.                     var p = (Product)instance;    
  18.                     return p.serializableProperties.Contains(property.PropertyName);    
  19.                 };    
  20.             }    
  21.         }    
  22.         return property;    
  23.     }    
  24. }    
ShouldSerializeContractResolver derives from DefaultContractResolverclass and overrides a method CreateProperty. So when an object is being serialized using this contract resolve, this method will be invoked to create the properties of the serialized object. What this method essentially does is, it just checks if the name of the property being created exists in the serializableProperties list. If it does then that property's ShouldSerialize attribute is set to true. Also, all of this is done if the object being serialized is either BaseEntity or is derived from BaseEntity. Quite simple, isn't it?

Now this is how our GET API will look:
  1. // GET api/products/5    
  2. public JsonResult<Product> Get(int id, string fields="")    
  3. {    
  4.     var product = _productsRepository.Find(x => x.Id == id);    
  5.     product.SetSerializableProperties(fields);    
  6.     return Json(product, new Newtonsoft.Json.JsonSerializerSettings()    
  7.     {    
  8.         ContractResolver = new ShouldSerializeContractResolver()    
  9.     });    
  10. }  
Neat, right? Now when someone consumes the API they then can append a comma-separated list of fields to the URL and get the data they need.

For example: myproductswebapi.azurewebsites.net/api/products?fields=Name,Id

Consuming an API that accepts a comma-separated list of fields

Now, when consuming an API that accepts such comma-separated list, appending a bunch of fields to the URL string looks really shabby for two reasons:
  1. If you want like 30 fields to be returned out of 50, you can easily make a mistake while writing all those property names and end up wasting a lot of time in figuring out why something is not being returned.
  2. If you forget one or two fields and/or decide to alter that list, making changes to that long string is not something you'd enjoy. :)

Here we can use the Expression Tree to specify the fields you want. Instead of creating a long string, we can use a lambda expression. So what we want to do is:

  1. _productService.GetProducts(x => new { x.Id, x.Name, x.Price });   
The input of this expression must be Product and the output is an anonymous type, so we can have an output type as object.

The definition GetProducts method in the service looks as in this:
  1. public async Task GetProducts(Expression<Func<Product, object>> parameters)   
Let's focus on the input parameter first. It's an expression tree of type Func, the input type of which is "Product" and the output type is "object" as discussed above. Then we write a small utility function that will take this expression tree and get the fields that are being passed. How do we do that?
  1. public static string GetProps<T>(Expression<Func<T, object>> parameters)    
  2. {    
  3.     StringBuilder requestedParametersString = new StringBuilder();    
  4.     if (parameters != null && parameters.Body != null)    
  5.     {    
  6.         var body = parameters.Body as System.Linq.Expressions.NewExpression;    
  7.         if (body.Members != null && body.Members.Any())    
  8.         {    
  9.             foreach (var member in body.Members)    
  10.             {    
  11.                 requestedParametersString.Append(member.Name + ",");    
  12.             }    
  13.         }    
  14.     }    
  15.     return requestedParametersString.ToString();    
  16. }  
Looks a bit, complicated right? I admit, it is a bit. I'll try to explain.

There are many types of expressions that are part of an expression tree "body" like BinaryExpression:

x => x + 5

Here x + 5 is a BinaryExpression.

In the lambda expression that we are using, we are instantiating a new object

x => new { x.Id, x.Name, x.Price }

So the type of that Expression is NewExpression.

The "Body" attribute of the expression tree is actually of type Expression that is the base class of all these expression types (recall inheritance concept). Using the "as" keyword, we are telling the compiler that this Expression object holds the reference to a derived class NewExpression object (since we are sure that we will be sending an object of type NewExpression).

NewExpression has a Members property that gets the members that can retrieve the values of the fields that were initialized with constructor arguments. By iterating through this collection we will get all the member names and form a comma-separated string of all these names. This string is then returned from the utility function.

Errr, is it making sense to you? It works, trust me! :D

Here is how my ProductService class looks:

  1. public class ProductService    
  2. {    
  3.     private string BaseUrl { getset; }    
  4.     private HttpClient client { getset; }    
  5.   
  6.   
  7.     public ProductService()    
  8.     {    
  9.         client = new HttpClient();    
  10.         BaseUrl = "http://myproductswebapi.azurewebsites.net/api/products";    
  11.     }    
  12.   
  13.     public async Task<List<Product>> GetProducts(Expression<Func<Product, object>> parameters)    
  14.     {    
  15.         string requestedParametersString = Utilities.GetProps(parameters);    
  16.         HttpResponseMessage response = await client.GetAsync(this.BaseUrl + "?fields=" + requestedParametersString);    
  17.         if (response.IsSuccessStatusCode)    
  18.         {    
  19.             var products = await response.Content.ReadAsAsync<List<Product>>();    
  20.             return products;    
  21.         }    
  22.         return new List<Product>();    
  23.     }    
  24.   
  25.     public async Task<Product> GetProduct(int id,Expression<Func<Product, object>> parameters)    
  26.     {    
  27.         string requestedParametersString = Utilities.GetProps(parameters);    
  28.         HttpResponseMessage response = await client.GetAsync(this.BaseUrl + "/" + id + "?fields=" + requestedParametersString);    
  29.         if (response.IsSuccessStatusCode)    
  30.         {    
  31.             var product = await response.Content.ReadAsAsync<Product>();    
  32.             return product;    
  33.         }    
  34.         return new Product();    
  35.     }    
  36.   
  37. }   
Now your client can use this Service class to make calls to the APIs. Here is an example of the MVC client.
  1. public class ProductController : Controller    
  2. {    
  3.     private ProductService _productService;    
  4.   
  5.         
  6.   
  7.     // GET: Product    
  8.     public async  Task<ActionResult> Index()    
  9.     {    
  10.         this._productService = new ProductService();    
  11.         var products = await this._productService.GetProducts(x => new { x.Id, x.Name, x.Price });    
  12.         return View("Products",products);    
  13.     }    
  14.   
  15.     // GET: Product/Details/5    
  16.     public async Task<ActionResult> Details(int id)    
  17.     {    
  18.         this._productService = new ProductService();    
  19.         var product = await this._productService.GetProduct(id,x => new { x.Id, x.Name, x.Price,x.ShelfLife, x.UnitsInStock });    
  20.         return View(product);    
  21.     }  
  22. }  
products

details
As you can see, we have a main view that shows a list of all the products with limited information and if you click on the details button, it shows detailed information of the product. So when the list view is loaded we are only fetching the fields that we need and not wasting the bandwidth and time to get other fields. This will increase the web application's performance significantly.

You can check out the entire solution here.

I hope this was helpful.
Until next time, cheers.


Similar Articles