Parsing OData Paths, $select and $expand using the ODataUriParser

UPDATE: This post was updated on May 29th 2013 to reflect changes to simplify the SelectExpandClause API between 5.5 beta and 5.5 RTM Release, the biggest change involved merging Select and Expand into SelectedItems.

The ODataUriParser is continuing its march towards completion. In fact in ODataLib version 5.5 (RTM drops on May 29th) it is almost complete.

In the last post I talked about how you could parse $filter and $orderby. In 5.5 you can also parse $select, $expand and the path part of an OData url too. Which means all that remains it putting all these bits together into a method that will parse the whole Url with a single step.

That said with 5.5 you can do this yourself, the algorithm is essentially:

  1. Parse the Uri path into an ODataPath (see below)
  2. Use the Type and IEdmEntitySet of the ODataPath to:
    1. Parse $filter clause – Use ODataPathParser.ParseFilter(string, IEdmType, IEdmEntitySet) – (see this post)
    2. Parse $orderby clause – Use ODataPathParser.ParseOrderBy(string, IEdmType, IEdmEntitySet) – (see this post)
    3. Parse $select and $expand clauses – (see below)
    4. Parse $inlinecount clause – Use ODataPathParser.ParseInlineCount(string)
    5. Parse $top and $skip clauses – Trivial since these are just whole numbers

If you do this you can build a structure that holds the whole URI converted into various ASTs. In 5.6 we will add a new Parse(..) method that will do this for you and return an appropriate data structure to hold the ODataPath and various query clauses.

In the meantime lets take a look what we’ve added in 5.5:

Parsing the Path

This API is pretty simple, you need an ODataUriParser (which requires an IEdmModel and a base Uri) then you call ParsePath(..), like this:

// Get a model, clearly you could do this in many ways, in this example we borrow one of the sample OData models
IEdmModel model = EdmxReader.Parse(XmlTextReader.Create(“https://services.odata.org/V3/OData/OData.svc/$metadata”));

// Construct a parser for that model
ODataUriParser parser = new ODataUriParser(model, new Uri(“https://server/service”));

// Parse a relative path
ODataPath path = parser.ParsePath(new Uri("https://services.odata.org/V3/OData/OData.svc/Products/ODataDemo.FeaturedProduct(1)/Advertisement"));

If you do this you get an ODataPath that hold an enumeration of segments.
In this case the segments will be (from first to last):

EntitySetSegment With: - An EdmType property that returns an IEdmType (an IEdmCollectionType containing a IEdmTypeReference to the ‘ODataDemo.Product’ entity type). - An EntitySet property that returns an IEdmEntitySet (in this case ‘Products’). - A convenient ElementType property that return an IEdmEntityType (in this case ‘ODataDemo.Product’)
TypeSegment With:  - An EdmType property that returns an IEdmType (in this case an IEdmCollectionType containing an IEdmTypeReference to the ‘ODataDemo.FeaturedProduct’ entity type). - A Set property that returns an IEdmEntitySet (in this case ‘Products’)           
KeySegment With: - An EdmType property that returns an IEdmType (in this case the ‘ODataDemo.FeaturedProduct’ entity type). - An EntitySet property that returns an IEdmEntitySet (in this case ‘Products’). - A Keys property that holds a IEnumerable<KeyValuePair<string,object>> , where the key is the name of the Entity’s key property and the value is the Entity’s key value. (in this case 1).
NavigationPropertySegment With: - An EdmType property that returns an IEdmType (in this case the ‘ODataDemo.Advertisement’ entity type). - An EntitySet property that returns an IEdmEntitySet (in this case ‘Advertisements’).            - A NavigationProperty property that holds an IEdmNavigationProperty (in this case ‘Advertisement’).

There are of course other segments OData paths you will encounter other segments like: PropertySegment, BatchSegment, MetadataSegment, ValueSegment, CountSegment, NavigationPropertyLinkSegment, OperationSegment and so forth.

Doesn’t WebAPI have an ODataPath too?

Those of you using WebAPI will be aware that it has it’s own code for parsing the path part of an OData URI. This is basically just bad timing, if we’d finished the ODataLib URI parser sooner Web API would have used it to parse paths as well. Indeed going forward Web API is likely to refactor their ODataPath code so that it uses the new ODataLib path parsing capabilities.

Parsing $select and $expand

The $select and $expand clauses are related, for example $select effects what is expanded. So we have a single method for parsing both simultaneously, this allows us to create an AST that captures the semantics of both query options together.

For example if someone issued a query like this:

GET https://server/service/Products?$select=ID,Name,Category&$expand=Category

You would convert this into an AST by making the following calls:

// Construct a parser for that model
ODataUriParser parser = new ODataUriParser(model, new Uri(“https://server/service”));

// Parse the $select and $expand clauses
SelectExpandClause selectAndExpand = parser.ParseSelectAndExpand("ID,Name,Category", "Category", type, set);

The SelectExpandClause is a data structure that looks like this:

SelectExpandClause Has a SelectedItems property that returns IEnumerable<SelectItem> indicating which Properties, NavigationProperties, Actions and Functions have been requested. Has a AllSelected boolean property that when true means everything is selected at this level. This avoids needing to populate SelectedItems with every possible item.
SelectItem Is an abstract base class. At runtime a SelectItem will be one of  PathSelectItem, ExpandedNavigationSelectItem or WildcardSelectItem
PathSelectItem Has a SelectedPath property holding an ODataPath that terminates with one of PropertySegment, NavigationPropertySegment, OpenPropertySegment or OperationSegment.
ExpandedNavigationSelectItem Has a PathToNavigationProperty property that returns a ODataPath that terminates with a NavigationPropertySegment to indicate which NavigationProperty needs to be expanded.                      It also has a SelectExpandOption property that returns a nested SelectExpandClause. This is where nested selection and expansion information will be stored. For example if someone selects “Category/ID” and expands “Category” the nested SelectExpandClause for the “Category” ExpandItem will have AllSelected set to false and the SelectedItems will include a PathSelectItem for the “ID” property of the “Category”. The ExpandedNavigationSelectItem is also designed to support V4 of the OData protocol, which allows for filtering, sorting and paging of expanded items. However in V3 these properties will be null.
WildcardSelectItem Indicates that the client wishes to select every property.

     

Summary:

The new version of the ODataUriParser that ships in ODataLib version 5.5 is finished. 5.5 adds support for parsing Paths, $select and $expand, which means the ODataUriParser now handles all of OData. All that remains is to add a more convenient way of parsing a full Url in one step, and we will ship something that does that in version 5.6. In the meantime you can still parse an OData Uri completely if you use the algorithm outlined above.

If you have any questions, concerns or suggestion, as always we are keen to hear from you.

- Alex