How to return number of items in OData v4 HTTP response?
I need this number to pagination, so it should be number of items after filtering, but before 'skip' and 'top'.
I already tried passing '$inlinecount=allpages' and '$count=true' parameters in query options in url (https://damienbod.wordpress.com/2014/06/13/web-api-and-odata-v4-queries-functions-and-attribute-routing-part-2/ - "Example of $count"), but my responses from WebAPI always have only query results (collection) - whole response looks like:
[
{
"Name":"name1",
"age":5
},
{
"Name":"name2",
"age":15
}
]
There is nothing like "odata.count" in the response.
I also tried returning PageResult instead of IQueryable in my WebAPI controller action (like described here: http://www.asp.net/web-api/overview/odata-support-in-aspnet-web-api/supporting-odata-query-options#server-paging), but Request.GetInlineCount() is deprecated and its value is always null.
Any ideas?
[Update] I just found the same problem here: WebApi with Odata NextPage and Count not appearing in the JSON response and I removed [EnableQuery] attribute and now my response looks like:
{
"Items":
[
{
"Name":"name1",
"age":5
},
{
"Name":"name2",
"age":15
}
],
"NextPageLink":null,
"Count":null
}
But still "Count" is always null. :(
public PageResult<People> Get(ODataQueryOptions<People> queryOptions)
{
var query = _context.People.OrderBy(x => x.SomeProperty);
var queryResults = (IQueryable<People>)queryOptions.ApplyTo(query);
long cnt = 0;
if (queryOptions.Count != null)
cnt = long.Parse(Request.Properties["System.Web.OData.TotalCount"].ToString());
return new PageResult<People>(queryResults, null, cnt);
}
And it works fine, but I still don't know why I have to use workarounds like that.
The $select option specifies a subset of properties to include in the response body. For example, to get only the name and price of each product, use the following query: Console Copy. GET http://localhost/odata/Products?$select=Price,Name.
You can use filter expressions in OData requests to filter and return only those results that match the expressions specified. You do this by adding the $filter system query option to the end of the OData request.
OData defines parameters that can be used to modify an OData query. The client sends these parameters in the query string of the request URI. For example, to sort the results, a client uses the $orderby parameter: http://localhost/Products?$orderby=Name. The OData specification calls these parameters query options.
This can also be achieved by an action filter:
/// <summary>
/// Use this attribute whenever total number of records needs to be returned in the response in order to perform paging related operations at client side.
/// </summary>
public class PagedResultAttribute: ActionFilterAttribute
{
/// <summary>
///
/// </summary>
/// <param name="actionExecutedContext"></param>
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
base.OnActionExecuted(actionExecutedContext);
if (actionExecutedContext.Response != null)
{
dynamic responseContent=null;
if (actionExecutedContext.Response.Content != null)
responseContent = actionExecutedContext.Response.Content.ReadAsAsync<dynamic>().Result;
var count = actionExecutedContext.Response.RequestMessage.ODataProperties().TotalCount;
var res = new PageResult<dynamic>() {TotalCount=count,Items= responseContent };
HttpResponseMessage message = new HttpResponseMessage();
message.StatusCode = actionExecutedContext.Response.StatusCode;
var strMessage = new StringContent(JsonConvert.SerializeObject(res), Encoding.UTF8, "application/json");
message.Content = strMessage;
actionExecutedContext.Response = message;
}
}
}
And the custom PageResult class is:
public class PageResult<T>
{
public long? TotalCount { get; set; }
public T Items { get; set; }
}
Usage:
[PagedResult]
[EnableQuery()]
For future reference (OData v4):
First of all $inlinecount
it's not supported in OData v4
so you should use $count=true
instead.
Second, if you have a normal ApiController
and you return a type like IQueryable<T>
this is the way you can attach a count
property to the returned result:
using System.Web.OData;
using System.Web.OData.Query;
using System.Web.OData.Extensions;
//[EnableQuery] // -> If you enable globally queries does not require this decorator!
public IHttpActionResult Get(ODataQueryOptions<People> queryOptions)
{
var query = _peopleService.GetAllAsQueryable(); //Abstracted from the implementation of db access. Just returns IQueryable<People>
var queryResults = (IQueryable<People>)queryOptions.ApplyTo(query);
return Ok(new PageResult<People>(queryResults, Request.ODataProperties().NextLink, Request.ODataProperties().TotalCount));
}
Note: OData functionality does not supported by
ApiController
s so you cannot have things likecount
or$metadata
. If you choose to use simpleApiController
the way above is the one you should use to return acount
property.
For a full support of OData functionality you should implement a ODataController
the following way:
PeopleController.cs
using System.Web.OData;
using System.Web.OData.Query;
public class PeopleController : ODataController
{
[EnableQuery(PageSize = 10, AllowedQueryOptions = AllowedQueryOptions.All)]
public IHttpActionResult Get()
{
var res = _peopleService.GetAllAsQueryable();
return Ok(res);
}
}
App_Start \ WebApiConfig.cs
public static void ConfigureOData(HttpConfiguration config)
{
//OData Models
config.MapODataServiceRoute(routeName: "odata", routePrefix: null, model: GetEdmModel(), batchHandler: new DefaultODataBatchHandler(GlobalConfiguration.DefaultServer));
config.EnsureInitialized();
}
private static IEdmModel GetEdmModel()
{
var builder = new ODataConventionModelBuilder
{
Namespace = "Api",
ContainerName = "DefaultContainer"
};
builder.EntitySet<People>("People").EntityType.HasKey(item => item.Id); //I suppose the returning list have a primary key property(feel free to replace the Id key with your key like email or whatever)
var edmModel = builder.GetEdmModel();
return edmModel;
}
Then you access your OData Api this way (example):
encoded uri:
http://localhost:<portnumber>/People/?%24count=true&%24skip=1&%24top=3
decoded:
http://localhost:<portnumber>/People/?$count=true&$skip=1&$top=3
References:
Will you please take a look at the sample service TripPin web api implementation at https://github.com/OData/ODataSamples/blob/master/Scenarios/TripPin. You can follow the code in Airports controller and the service with the code http://services.odata.org/TripPinWebApiService/Airports?$count=true can return the count correctly.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With