13

I'm really new to API design and MVC concepts, but as far as I can tell, something like GET /api/products should return a list of products and GET /api/products/1 should return a single product. In terms of speed my feeling is that /api/products should return less information i.e. just id and name, whereas /api/products/1 should return more i.e. id, name, and description.

As far as I can see, the best way to handle this is to make certain fields of the product class not be returned in the /api/products endpoint. This is especially necessary in the case of /api/products?fields=name . I'm using ASP.Net Web Api 2 and have tried the following:

Is there any simple way to do what I'm trying to do?

Otherwise could you suggest a better API design than what I'm doing?

5
  • Maybe you could return an anonymous type at /api/products, then return your detailed Product type /api/products/10. Commented Nov 18, 2013 at 22:44
  • Thanks @jtlowe - both /api/products and /api/products/10 are retrieving Product objects via entity framework so we have the full Product object in both cases. Surely since we have the full Product object already on /api/products, there must be some way to just return that List<Product> but with some of the properties omitted? Commented Nov 19, 2013 at 9:43
  • 1
    Depends on size/setup of database, and possibly the serializer. It sounds like you only want to load info you need for /api/products, not the full Product. There may be a performance hit if you are retrieving all full records from a Products table, which it sounds like you are trying to avoid. Martin's answer is a good approach. Also check out supporting OData Querying. This makes it easier to do filtering and such. Commented Nov 19, 2013 at 15:53
  • The main performance concern is passing a lot of data in the response to the client, not so much the database pull. In this regard Martin's approach does sound good, though I can't see it being scalable. For example what if we were to introduce /api/stores/1/products which return Product objects but this time only exposing ID, name, and say SKU number? Do we now need to create another type of Product class? Commented Nov 19, 2013 at 17:59
  • In the case you've just outlined, you could use the same initial Product object for both /api/products and /api/store/1/products. For a ProductDetail view -- instead of continuing down /api/store/1/products/10, you would make an api call to /api/products/10. Commented Nov 19, 2013 at 21:19

3 Answers 3

8

You could also use WebApi.PartialResponse (http://www.nuget.org/packages/WebApi.PartialResponse/). It's a package I wrote which uses LINQ to JSON (Json.NET) to manipulate the returned objects. It uses the fields syntax used by Google in their API's, eg.:

  • fields=items/id,playlistItems/snippet/title,playlistItems/snippet/position
  • fields=items(id,snippet/title,snippet/position)
  • fields=items(id,snippet(title,position))

You can find more information on the GitHub project page: https://github.com/dotarj/PartialResponse.

Sign up to request clarification or add additional context in comments.

2 Comments

Is there is a way to use this with collections? Say I return a paged collection from web api, and instead of getting the entire contents of the paged collection, I just want the id, and name off each item in the collection. I can get everything to work, except for the above scenario, which is what I'd really like to use it for :)
I read the Google dev doc on partial response thinking there might be some insight. Looks like this should work with collections. developers.google.com/drive/web/performance My JSON has type info, could that be messing things up? : { $id: "4", $type: "System.Collections.Generic.List`1[[Acme.Models.FluidModel, KNE.Athena.Models]], mscorlib", $values: [ { $id: "5", $type: "Acme.Fluids.FluidModel, Acme.Models", links: {}, id: 1, name: "Fluid", },
4

I'd recommend using separate classes to map to when returning a list of entities.

Particularly as the problem is not just what you return to the user, but also what you select from the database.

So, make getting and entity return a Product object, and getting a list of entities return a ProductLink object or something similar.

Edit

As per jtlowe's comment, if you have many different methods returning slight variations of product properties, use anonymous classes (though I'd question whether this is necessarily a good design).

Consider something like this in your action

return from p in this.context.Products
       select new
       {
           p.Id,
           p.Name,
           p.SKU
       };

This:

  • Only selects the columns you need from the database.
  • Needs no additional classes defined for new variations of the product

This doesn't make it easy to pass the result of this statement around to other methods because you can only return it as IEnumerable, object or dynamic. If you are putting this in the controller then it may be good enough. If you are implementing a repository pattern, you'll be unable to return strongly typed lists if you use anonymous types.

4 Comments

Thanks for the advice Martin - I think we'll use this approach. How would you suggest handling the ?fields=id,name problem?
Actually thinking about it, please see my comment above to jtlowe. Surely this approach is not scalable?
This will scale if your Product object isn't the actual Product object containing all product details. for example: Product {ID, SKU, Name}, ProductDetails {ID, SKU, NAME, Manufacturer, Contact, Price, Origin, Height, Width, ..., ..., ..., ..., ...}.
Thanks for your help jtlowe and Martin Booth - it's really helping to set my thinking straight! With regards to partial responses though, I can't find anything concrete about how to handle something like /api/products?fields=color,size,name . Are there any best practices in the .NET ecosystem for this? I'd rather not use OData if possible.
0

Stumpled over this topic and just want to share my feelings - maybe it helps others :) I recommend to use something like OData. You can implement it so that you can write /api/products?$select=Id,Name,Price

some advantages:

  • with OData you can use further functions, like $filter, $orderby to work with filters and sort it
  • $skip, $top, $count to get a nice paging
  • more $-functions :)
  • you can directly apply it to a IQueryable<T>. Why is this great? You reduce the result not just in the response of your API, but you even reduce the result your database generates, which makes your application much faster. - and you don't even have to change your query

some disadvantages:

  • you can't filter directly on columns that are calculated
  • setting it up will take a little time

hint: sometimes it's better to just use ODataQueryOptions<T> in the parameter instead of complete implementation.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.