This post is to guide you through the extension support of OData URI parser, which was added in ODataLib 6.7 release.
You can refer to the following article for basic usage of OData URI parser:
All the demo code in this post has been put into ODataSamples project, you can check it out under the Components/UriParser subfolder.
Background
The OData URI parser implements the logic for processing various URIs according to OData convention. As you may know, the Open Data Protocol (OData) V4 is described by a series of specification documents. Among them, the Uri parser is covered in Part1– Protocol, Part2– URL Conventions, and OData ABNF Constructor Rule.
The protocol has defined relatively strict rules on what a valid OData URI should look like. For instance, identifiers should be case sensitive, and bound function calls should always use namespace qualified function name. Those rules are helpful for eliminating ambiguity and name conflict. The OData URI parser follows them, which in turn leads to a set of strong validation rules.
On the other hand, there are some drawbacks, such as:
- Some existing services/clients may have their APIs using case insensitive convention. It becomes a problem when porting to OData.
- Namespace qualified identifiers sometimes make the whole URI too long, and user may want to use the short name directly, provided that there are no name conflicts.
- Advanced users may even want to have the customized rules for parsing URIs.
In order to deal with those issues, we added the extension framework for URI parser in ODataLib.
Overview
The basic idea for extending the OData URI parser is to expose some inner parsing procedures and allow users to override them.
In general, we introduced a new class named ODataUriResolver, which exposes some parsing phrases, such as ResolveType and ResolveProperty. And ODataUriParser/ODataQueryOptionParser now have a new property called Resolver, which accepts an ODataUriResolver instance. The user could choose to override the default behavior of ODataUriResolver and thus have their customized parser.
User could choose to customize the following behaviors by overriding corresponding method on ODataUriResolver class:
- Resolving property name
- Resolving type name
- Resolving navigation source name
- Resolving operation import name
- Resolving bound operation name
- Resolving function parameters
- Resolving entity set key
- Resolving binary operator node elements
One major scenario we want to enable is case insensitive. An option EnableCaseInsensitive in ODataUriResolver class is introduced to control the case behavior. In addition, two built in extension implementations are provided to support extended features such as unqualified function call, and prefix-free enum value. All those extensions would be discussed in following section.
Using built in extensions
Case-insensitive support
There is an option on ODataUriResolver for case insensitive behavior. It controls both built in identifiers ($filter, eq, etc.) and user metadata (property name, type name). A detailed scope could be found on the ODataLib 6.7 release note. To enable it, Resolver property needs to be set to an ODataUriResolver instance(we use default implementation here) with EnableCaseInsensitive option turned on.
Unqualified function call support
Unqualified function call is supported by UnqualifiedODataUriResolver class, which inherits from ODataUriResolver. It enables bound function call without namespace in both path and query options. When conflicts are found, for example
if there’re two functions with same name and signature but in different namespaces, the parser would throw an exception.
Prefix-free enum value support
Prefix-free enum value is supported by StringAsEnumResolver class, which also inherits from ODataUriResolver. According to OData V4 specification, enum values should be prefixed by enum type name. In some cases, we already know what the expected type is, thus the type name is redundant as it could be inferred. For example, we have a function accepting an enum parameter, then in the function call URI, the parameter value could be written as a string. When evaluating the parameter, we can parse it to the expected enum type.
Write your own extension
There are other scenarios that built-in extension did not cover directly. In this case, you can choose to extend the URI parser by writing your own resolver.
Combination of built-in extensions
One common case is when user want to enable unqualified function call and prefix-free enum at the same time. Thus you can write a resolver class which internally wraps those two resolver. You can find the source code for AllInOneResolver class in the sample project. Here is the demo for its usage:
Write customized extensions from scratch
If there is no existing extension meets your requirement, it is also easy to write your own extension from scratch. Here is an example for operator overloading extension.
In some programming languages, it supports the syntax for multiplying a string and a number, which means duplicating the string by N times. For example ‘3’*5 would produce ‘33333’. With OData URI parser, when you write‘3’ mul 5 in query option, it would be treated as invalid as mul operator does not support Edm.String and Edm.Int32 as operands. In this case, you could write your own extension for it: