Client Annotation Support
Applies To: # OData client v7 supported OData Client V7
Before ODataLib 6.10.0, OData core library supported metadata annotations for elements in the model and instance annotations for particular instances in the payload. But on client side, there wasn’t a good way to access these annotations. ODataLib 6.10.0 introduced several APIs to enable users to access annotations on client side. Basically, OData client follows the rules defined in OData V4.0 protocol (see 6.4 Vocabulary Extensibility) to access instance or metadata annotations.
How to get annotations on client side
All client CLR types in this tutorial are generated using the OData Connected Service extension. Before we dive into this tutorial, check out the OData Connected Service tutorial on how to generate the client side proxy classes.
OData Client provides the following APIs for accessing annotations in DataServiceContext
class.
public bool TryGetAnnotation<TResult>(object source, string term, string qualifier, out TResult annotation)
public bool TryGetAnnotation<TResult>(object source, string term, out TResult annotation)
public bool TryGetAnnotation<TFunc, TResult>(Expression<TFunc> expression, string term, string qualifier, out TResult annotation)
public bool TryGetAnnotation<TFunc, TResult>(Expression<TFunc> expression, string term, out TResult annotation)
The first two APIs are for getting annotations associated with a specified object. The last two APIs are for getting annotations for a property, a navigation property, an entity set, a singleton, an operation or an operation import.
In these APIs, term is the full qualified name of term, qualifier should be provided if an annotation contains qualifier, which means, if the annotation defines qualifier, but user use null as qualifier, then these APIs will return false.
In following part, we will give some examples for what we have supported in ODataLib 6.10.0. But for other elements that we didn’t mentioned, they haven’t been supported yet.
Request preference odata.include-annotations
To get instance annotations, we need to set odata.include-annotations preference in request to specify the set of annotations the client requests to be included.
public static void Main(string[] args)
{
DefaultContainer dsc = new DefaultContainer(new Uri("https://services.odata.org/V4/(S(uvf1y321yx031rnxmcbqmlxw))/TripPinServiceRW/"));
dsc.SendingRequest2 += (sender, eventArgs) =>
{
eventArgs.RequestMessage.SetHeader("Prefer", "odata.include-annotations=\"*\"");
};
}
Please refer to 8.2.8.4 Preference odata.include-annotations for the rules of this preference.
Get annotations Code Sample
Get an instance annotation for a feed
var personQueryResponse = dsc.People.Execute();
personQueryResponse.ToList();
dsc.TryGetAnnotation(personQueryResponse, fullQualifiedTermName, null /*qualifier*/, out annotation);
Please note the first parameter should be an QueryOperationResponse<Person\>
.
For feed, we have to enumerate the response to materialize the annotation. So in this code block, we call personQueryResponse.ToList()
to enumerate the response, and then we call public bool TryGetAnnotation<TResult>(object source, string term, string qualifier, out TResult annotation)
to get the instance annotations for the
feed.
Currently, qualifier is not supported for instance annotation. So we pass in null for qualifier parameter. Also, we can use public bool TryGetAnnotation<TResult>(object source, string term, out TResult annotation)
which doesn't contain qualifier parameter.
Please note, if you want to get a metadata annotation for an entity set, you should use the last two APIs, which will be mentioned later.
Get an annotation for an entity
Below is a snippet of the Photo
entity type in the Trippin Service
metadata.
<EntityType Name="Photo" HasStream="true">
<Key>
<PropertyRef Name="Id" />
</Key>
<Property Name="Id" Type="Edm.Int64" Nullable="false">
<Annotation Term="Org.OData.Core.V1.Permissions">
<EnumMember>Org.OData.Core.V1.Permission/Read</EnumMember>
</Annotation>
</Property>
<Property Name="Name" Type="Edm.String" />
<Annotation Term="Org.OData.Core.V1.AcceptableMediaTypes">
<Collection>
<String>image/jpeg</String>
</Collection>
</Annotation>
</EntityType>
string fullQualifiedTermName = "Org.OData.Core.V1.AcceptableMediaTypes";
var photo = dsc.Photos.ByKey(1).GetValue();
dsc.TryGetAnnotation(photo, fullQualifiedTermName, null, out ICollection<string> annotations);
foreach(string annotation in annotations)
{
Console.WriteLine($"Annotation : {annotation}");
}
Please note the first parameter should be the Clr object. This API will firstly try to get the instance annotation of the fullQualifiedTermName
and qualifier
. If the instance annotation doesn't exist, it will try to get the metadata annotation of the fullQualifiedTermName
and qualifier
.
Get an annotation for a property in an entity or a navigation property
var person = dsc.People.ByKey("russellwhyte").GetValue();
string fullQualifiedTermName = "Org.OData.Core.V1.Computed";
// Try to get an annotation for a property
dsc.TryGetAnnotation<Func<long>, bool>(() => person.Concurrency, fullQualifiedTermName, null, out bool annotation);
Console.WriteLine($"Annotation : {annotation}");
// Try to get an annotation for a navigation property
dsc.TryGetAnnotation<Func<Photo>, string>(() => person.Photo, fullQualifiedTermName, qualifier, out annotation);
The first parameter is the closure lambda expression which is to access the property. The API will firstly try to get the instance annotation, if it doesn't exist, it will try to get the metadata annotation for the property.
Below is a snippet of the Person
entity type in the Trippin Service
metadata.
<EntityType Name="Person" OpenType="true">
<Key>
<PropertyRef Name="UserName" />
</Key>
<Property Name="UserName" Type="Edm.String" Nullable="false">
<Annotation Term="Org.OData.Core.V1.Permissions">
<EnumMember>Org.OData.Core.V1.Permission/Read</EnumMember>
</Annotation>
</Property>
<Property Name="FirstName" Type="Edm.String" Nullable="false" />
<Property Name="LastName" Type="Edm.String" Nullable="false" />
<Property Name="Emails" Type="Collection(Edm.String)" />
<Property Name="AddressInfo" Type="Collection(Microsoft.OData.SampleService.Models.TripPin.Location)" />
<Property Name="Gender" Type="Microsoft.OData.SampleService.Models.TripPin.PersonGender" />
<Property Name="Concurrency" Type="Edm.Int64" Nullable="false">
<Annotation Term="Org.OData.Core.V1.Computed" Bool="true" />
</Property>
<NavigationProperty Name="Friends" Type="Collection(Microsoft.OData.SampleService.Models.TripPin.Person)" />
<NavigationProperty Name="Trips" Type="Collection(Microsoft.OData.SampleService.Models.TripPin.Trip)" ContainsTarget="true" />
<NavigationProperty Name="Photo" Type="Microsoft.OData.SampleService.Models.TripPin.Photo" />
</EntityType>
Get annotation for a complex value
var address = dsc.People.ByKey("russellwhyte").Select(p => p.AddressInfo).GetValue();
dsc.TryGetAnnotation(address, fullQualifiedTermName, qualifier, out annotation);
The first parameter is the Clr instance of a complex type. This API will firstly try to get the instance annotation of this complex value, if it doesn't exit, it will try to get the metadata annotation for the complex type of the instance.
Get metadata annotation for an entity set
Below is a snippet of the People
entity set in the Trippin Service
metadata.
<EntitySet Name="People" EntityType="Microsoft.OData.SampleService.Models.TripPin.Person">
<NavigationPropertyBinding Path="Friends" Target="People" />
<NavigationPropertyBinding Path="Microsoft.OData.SampleService.Models.TripPin.Flight/Airline" Target="Airlines" />
<NavigationPropertyBinding Path="Microsoft.OData.SampleService.Models.TripPin.Flight/From" Target="Airports" />
<NavigationPropertyBinding Path="Microsoft.OData.SampleService.Models.TripPin.Flight/To" Target="Airports" />
<NavigationPropertyBinding Path="Photo" Target="Photos" />
<NavigationPropertyBinding Path="Microsoft.OData.SampleService.Models.TripPin.Trip/Photos" Target="Photos" />
<Annotation Term="Org.OData.Core.V1.OptimisticConcurrency">
<Collection>
<PropertyPath>Concurrency</PropertyPath>
</Collection>
</Annotation>
<Annotation Term="Org.OData.Core.V1.ResourcePath" String="People" />
</EntitySet>
If we want to get the value of the Org.OData.Core.V1.ResourcePath
annotation term, we run the code below:
string fullQualifiedTermName = "Org.OData.Core.V1.ResourcePath";
dsc.TryGetAnnotation<Func<DataServiceQuery<Person>>, string>(() => dsc.People, fullQualifiedTermName, /*qualifier*/ null, out string annotation);
Console.WriteLine($"Annotation : {annotation}");
Get annotation for a singleton
Below is a snippet of the Me
singleton in the Trippin Service
metadata.
<Singleton Name="Me" Type="Microsoft.OData.SampleService.Models.TripPin.Person">
<NavigationPropertyBinding Path="Friends" Target="People" />
<NavigationPropertyBinding Path="Microsoft.OData.SampleService.Models.TripPin.Flight/Airline" Target="Airlines" />
<NavigationPropertyBinding Path="Microsoft.OData.SampleService.Models.TripPin.Flight/From" Target="Airports" />
<NavigationPropertyBinding Path="Microsoft.OData.SampleService.Models.TripPin.Flight/To" Target="Airports" />
<NavigationPropertyBinding Path="Photo" Target="Photos" />
<NavigationPropertyBinding Path="Microsoft.OData.SampleService.Models.TripPin.Trip/Photos" Target="Photos" />
<Annotation Term="Org.OData.Core.V1.ResourcePath" String="Me" />
</Singleton>
If we want to get the value of the Org.OData.Core.V1.ResourcePath
annotation term, we run the code below:
string fullQualifiedTermName = "Org.OData.Core.V1.ResourcePath";
dsc.TryGetAnnotation<Func<PersonSingle>, string>(() => dsc.Me, fullQualifiedTermName, /*qualifier*/ null, out string annotation);
Console.WriteLine($"Annotation : {annotation}");
Get annotation for a function import
Below is a snippet of the GetNearestAirport
FunctionImport in the Trippin Service
metadata.
<FunctionImport Name="GetNearestAirport" Function="Microsoft.OData.SampleService.Models.TripPin.GetNearestAirport" EntitySet="Airports" IncludeInServiceDocument="true">
<Annotation Term="Org.OData.Core.V1.ResourcePath" String="Microsoft.OData.SampleService.Models.TripPin.GetNearestAirport" />
</FunctionImport>
If we want to get the value of the Org.OData.Core.V1.ResourcePath
annotation term, we run the code below:
string fullQualifiedTermName = "Org.OData.Core.V1.ResourcePath";
dsc.TryGetAnnotation<Func<double, double, AirportSingle>, string>((lat, lon) => dsc.GetNearestAirport(lat, lon), fullQualifiedTermName, /*qualifier*/ null, out string annotation);
Console.WriteLine($"Annotation : {annotation}");
Get annotation for an entity container
Below is a snippet of the DefaultContainer
in the Trippin Service
metadata.
<EntityContainer Name="DefaultContainer">
....
....
<Annotation Term="Org.OData.Core.V1.Description" String="TripPin service is a sample service for OData V4."/>
</EntityContainer>
If we want to get the value of the Org.OData.Core.V1.Description
annotation term, we run the code below:
string fullQualifiedTermName = "Org.OData.Core.V1.Description";
dsc.TryGetAnnotation(dsc, fullQualifiedTermName, out string annotation);
Console.WriteLine($"Annotation : {annotation}");
Get metadata annotation for a function/action/action import
In section "Get an instance annotation for a feed", we know that to get metadata annotation for an entity set, we should use the last two APIs. This rule is also apply to singleton, function, function import, action and action import.
// Try to get a metadata annotation for a function bound to a person
var person = dsc.People.ByKey("russellwhyte").GetValue();
dsc.TryGetAnnotation<Func<AirlineSingle>, string>(()=>person.GetFavoriteAirline(), fullQualifiedTermName, qualifier, out annotation);
// Try to get a metadata annotaiton for an action bound to a person
dsc.TryGetAnnotation<Func<string, int, DataServiceActionQuery>, string>((userName, tripId) => person.ShareTrip(userName, tripId), fullQualifiedTermName, qualifier, out annotation);
// Try to get a metadata annotation for an action import
dsc.TryGetAnnotation<Func<DataServiceActionQuery>, string>(() => dsc.ResetDataSource(), fullQualifiedTermName, qualifier, out annotation);
Feedback
https://aka.ms/ContentUserFeedback.
Coming soon: Throughout 2024 we will be phasing out GitHub Issues as the feedback mechanism for content and replacing it with a new feedback system. For more information see:Submit and view feedback for