18

Closed

OData does not support $inlinecount=allpages

description

This is obviously very needed. See my comments about ODataResult on how this should be intelligently implemented without using things like ODataResult.
Closed Apr 26, 2013 at 8:11 AM by hongyes

comments

MarkBerryman wrote Nov 15, 2012 at 9:59 PM

Telerik AJAX controls' paging support also depends on support for this OData query parameter. W/o it, they're broken.

youssefm wrote Dec 13, 2012 at 7:08 PM

Geminiman wrote Dec 15, 2012 at 1:16 PM

Can you elaborate more on how to use this?

For instance I use my own media formatter, how do I ensure that this information is provided in my response?

hongyes wrote Dec 17, 2012 at 6:52 PM

Hi Geminiman,

In order to get the inlinecount, you need the request first. Please override the GetPerRequestFormatterInstance method in your formatter to create formatter per request so that your formatter can get current request instance.

In write path, use extension method request.GetInlineCount() to get the long? value to get the inline count.

Please reference the code used in odata formatter:
http://aspnetwebstack.codeplex.com/SourceControl/changeset/ed65e90e83c8#src/System.Web.Http.OData/OData/Formatter/Serialization/ODataFeedSerializer.cs

Geminiman wrote Dec 17, 2012 at 8:38 PM

Ok, thanks! I got the latest nightlies and this extension method isn't available for me. It doesn't appear to be on the class.

Any eta on this getting into the nightlies?

josundt wrote Dec 20, 2012 at 3:01 PM

I've been waiting for the "$inlinecount=allpages" parameter to work, since it, as remarked by others, really is the missing link for making a "paged" listview UI with Web API and OData.

After reading about this case and its status, I tested the December 20th 2012 build with great expectations.

Unfortunately it still does not work correctly. I no longer get a server error saying that the "$inlinecount" parameter is unsupported, but the response still does not contain information about the total records.

Looking forward to the feature is working!!

hongyes wrote Jan 2, 2013 at 5:42 PM

@josundt, are you using odata formatter to send the response? Currently, $inlinecount only supports ODataMediaTypeFormatter.

josundt wrote Jan 8, 2013 at 11:32 PM

@hongyes: It seems I can't get my head around how to use the ODataMediaTypeFormatter. Could you shed some light by a short example?

Luiggi370z wrote Jan 9, 2013 at 2:13 PM

@hongyes: please is there any example about how to use the ODataMediaTypeFormatter for inlinecount?

SEWilson wrote Jan 18, 2013 at 8:14 AM

Youssef, what magic is required to get the OData NuGet package updated? It's been 30 days, can we get an rc2 refresh? 0.3.0-rc has a build race requiring a -Force downgrade of depedencies and it's missing $inlinecount - a very necessary query option.

Thanks. :)

danroth27 wrote Jan 18, 2013 at 4:20 PM

You can always grab the latest nightly build as described here: http://aspnetwebstack.codeplex.com/wikipage?title=Use%20Nightly%20Builds

SEWilson wrote Jan 18, 2013 at 6:12 PM

Thanks. I have updated and I am testing with the nightly build available as of 10am PST (4.0.0-rtm-130118) ...

The service now accepts $inlinecount=allpages without any errors, which is excellent news. However, the response does not contain a Count (and the structure of the payload does not change, e.g, I still get [{},...] instead of { Count:x, Results:[{},...] } have I missed something?

I've resorted to looking through the code in an attempt to understand how this value is getting (or not getting) serialized to the feed response, but if you're already familiar with how it works ;) you might be more effective than myself. As far as I can tell this should work, but I haven't really identified the code which serializes the feed data (and which would also be responsible for serializing a different structure which includes a inline count.)

Thanks!

SEWilson wrote Jan 18, 2013 at 7:52 PM

it seems the test to verify serialization doesn't actually verify serialization, it only verifies object state (which I have verified is being correctly set, e.g. MS_InlineCount is being stored/attached to the current Request context, and it's valid.)

I verified that ODataHttpRequestMessageExtensions::SetInlineCount executes, but GetInlineCount is never invoked.

Following the execution of a request, I see that QueryableAttribute::OnActionExecuted executes the following code:
                    IEnumerable query = content.get_Value() as IEnumerable;
                    IQueryable queryable = this.ExecuteQuery(query, request, actionDescriptor);
                    content.set_Value(queryable);
The value of "content.Value" is the queryable, with all options applied. This is also appears to be the serialization target later via ObjectContent::SerializeToStreamAsync. Within the context of this method, "this.Value" is used as the object graph for "this._formatter.WriteToStreamAsync". "this.Value" is the IQueryable set previously in QueryableAttribute's OnActionExecuted.

This is the case regardless of which formatter is used. I've verified that the object graph written by both the Json and Xml media type formatters is the same IQueryable.

Upon completion, the result received at the client looks no different than a request without $inlinecount applied. So, while the service is applying the option, querying for a count (accurately) and tracking the value in memory, I don't see that it is actually being serialized.

This may explain why the payload structure doesn't change. The bottom of the stack during serialization looks like this, notice that the first thing you serialize to the graph is an array, and the first object into Newtonsoft is, again, the queryable set previously by QueryableAttribute.

Newtonsoft.Json.dll!Newtonsoft.Json.JsonTextWriter.WriteStartArray() Line 121 C#
Newtonsoft.Json.dll!Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeList(Newtonsoft.Json.JsonWriter writer, Newtonsoft.Json.Utilities.IWrappedCollection values, Newtonsoft.Json.Serialization.JsonArrayContract contract, Newtonsoft.Json.Serialization.JsonProperty member, Newtonsoft.Json.Serialization.JsonContainerContract collectionContract, Newtonsoft.Json.Serialization.JsonProperty containerProperty) Line 420    C#
Newtonsoft.Json.dll!Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeValue(Newtonsoft.Json.JsonWriter writer, object value, Newtonsoft.Json.Serialization.JsonContract valueContract, Newtonsoft.Json.Serialization.JsonProperty member, Newtonsoft.Json.Serialization.JsonContainerContract containerContract, Newtonsoft.Json.Serialization.JsonProperty containerProperty) Line 602   C#
Newtonsoft.Json.dll!Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.Serialize(Newtonsoft.Json.JsonWriter jsonWriter, object value) Line 258  C#
Newtonsoft.Json.dll!Newtonsoft.Json.JsonSerializer.SerializeInternal(Newtonsoft.Json.JsonWriter jsonWriter, object value) Line 259  C#
Newtonsoft.Json.dll!Newtonsoft.Json.JsonSerializer.Serialize(Newtonsoft.Json.JsonWriter jsonWriter, object value) Line 221  C#
System.Net.Http.Formatting.dll!System.Net.Http.Formatting.JsonMediaTypeFormatter.WriteToStreamAsync.AnonymousMethod__c() Line 204 + 0x180 bytes C#
System.Net.Http.Formatting.dll!System.Threading.Tasks.TaskHelpers.RunSynchronously(System.Action action, System.Threading.CancellationToken token) Line 125 C#
System.Net.Http.Formatting.dll!System.Net.Http.Formatting.JsonMediaTypeFormatter.WriteToStreamAsync(System.Type type, object value, System.IO.Stream writeStream, System.Net.Http.HttpContent content, System.Net.TransportContext transportContext) Line 192 + 0x1bf bytes C#
System.Net.Http.Formatting.dll!System.Net.Http.ObjectContent.SerializeToStreamAsync(System.IO.Stream stream, System.Net.TransportContext context) Line 64 + 0x39 bytes  C#

SEWilson wrote Jan 18, 2013 at 9:07 PM

After more debugging, seems there is no OData formatter registered.

I've added the following to WebApiConfig (App Start):
        System.Web.Http.OData.Formatter.ODataMediaTypeFormatters.Create()
            .ToList()
            .ForEach(f => config.Formatters.Insert(0, f));
Additionally, I've modified my ajax request to be explicit about the content type:
        $.ajax('/api/foo?$inlinecount=allpages', {
            headers: { "Accept":"application/json;odata=verbose", "MaxDataServiceVersion":"3.0" },
I still seem to pass through the default Json formatter, though. I've tried various combinations for accept headers. If I remove the JsonFormatter explicitly:
        config.Formatters.Remove(config.Formatters.JsonFormatter);
All response content types are application/xml (instead of application/json).

I feel as though $inlinecount is implemented (and this issue is appropriately in a 'Fixed' state) but I cannot find a way to receive a "real" OData feed. Who is responsible/knowledgeable when it comes to formatters and their resolution?

SEWilson wrote Jan 18, 2013 at 10:41 PM

... and after even more debugging ...

the ODataMediaTypeFormatter::CanWriteType() method always returns false because the private _request member is always null. So these formatters are never selected by "DefaultContentNegotiator" for a properly-formed odata request.

I recall there was an EnableOData() call, and it looks like it has been superceded by MapODataRoute() instead, not really understanding the guts of what I'm attempting at this point, I have added the following:
        System.Web.Http.OData.Formatter.ODataMediaTypeFormatters.Create()
            .ToList()
            .ForEach(f => config.Formatters.Insert(0, f));

        var modelBuilder = new System.Web.Http.OData.Builder.ODataConventionModelBuilder();
        modelBuilder.EntitySet<Foo>("foo");

        config.Routes.MapODataRoute(
            routeName: "OData", 
            routePrefix: "odata", 
            model: modelBuilder.GetEdmModel());
Now I am able to hit /odata/foo?$inlinecount=allpages due to the Map Route call, but _request is still null on all these formatters. I removed the line which adds the formatters (seen above) but nothing else is adding them on my behalf.

Dan, I am officially stuck. My confidence in Web API + OData is all but destroyed at this point and I'm insanely close to either ditching OData so I can stay on a Microsoft stack or switching to node.js for our data services.

Any suggestions?

SEWilson wrote Jan 18, 2013 at 10:59 PM

Success!

By chance while reading through some of the test code I saw the following test failure message:
            "The OData formatter requires an attached request in order to deserialize. Controller classes must derive from ODataController or be marked with ODataFormattingAttribute. Custom parameter bindings must call GetPerRequestFormatterInstance on each formatter and use these per-request instances.");
I applied the [ODataFormatting] attribute mentioned. I re-added all of the OData formatters to my WebAPI Config, and kept my MapODataRoute class.

Hopefully this helps others experiencing the same problem. I haven't tested the remainder of the services to ensure nothing else is broken yet, but it seems to be working as intended.

Thanks!

raghuramn wrote Jan 18, 2013 at 11:29 PM

Hi Wilson,

Sorry about the whole debugging experience. Glad that you finally found the [ODataFormatting] attribute. We added ODataController that has this attribute by default.

We would like our customers to derive from ODataController if they are implementing OData services. The OData controller sets the formatters and a couple of other useful action selection conventions. We are close to an RTM release for the OData packages and are working on improving the documentation and samples. So, all this should be more easier and clearer very soon.

Let me know if this helps.

hongyes wrote Jan 18, 2013 at 11:44 PM

I just checked in a sample code to http://aspnet.codeplex.com/SourceControl/changeset/13217b5b4cf1

The sample is using 0117's nightly build. It demonstrates all the basic operations on odata service.

Although it doesn't provide sample specifically for inlinecount. You should be able to do that by requesting GET http://localhost:50231/Products?$inlinecount=allpages

SEWilson wrote Jan 20, 2013 at 7:13 AM

I don't regret the time and effort when the end results are worth it,

The sample migration from hongyes is an excellent crash-course on some recent changes.

I think I speak for everyone that comes across this page when I say $inlinecount was the missing link to adoption for a large number of projects :) and also that after spending a few days looking at the source repo and seeing the number, size and quality of commits I realize I just need to be patient :) you guys are already running full-speed. Thanks Hongye, Youssef, Dan, and everyone else for all the hard work to make the new webapi/odata bits worth diving into like this, it's all good stuff.