August 4, 2020 13:04 by
Peter
OData represents Open Data Protocol, an OASIS standard initiated by Microsoft in 2007. This defines the best practices for the consumption of data and building with quarriable REST APIs.
The difference between Odata and REST API is, Odata is a specific protocol and REST is an architectural style and design pattern. Using REST we can request and get data using HTTP calls. OData is a technology that uses REST to consume and build data.
I expect readers of this article to have some knowledge about OData queries. But, to make it simple, this protocol gives the power to the client to query the data on the database using a query string of REST API requests. It also helps to make the data more secure by not exposing any database related information as well as limiting the data to the outside world.
In general, we need to build the Odata enabled web service using any popular programming language which takes care of building URLs, verbs, and their requests and expected responses accordingly. Now, at the client end to consume these Odata REST APIs, we need to have metadata that contains the request type as well response types to build the concrete classes or we need to create a service proxy class.
This article is about how clients can consume existing Odata REST API using C#. So, let's start.
Simple.Odata.Client is a library that supports all Odata protocol versions and can be installed from the NuGet package and supports both .NET Framework and .NET Core latest versions.
Initializing Client Instance
To communicate with Odata REST API, Simple.Odata.Client library has some predefined class called ODataClient. This class accepts service URL with some optional settings to do a seamless communication with Odata service.
var client = new ODataClient("https://services.odata.org/sferp/");
If you want to log all the requests and responses from this client object to the Console, we can have additional optional settings as below.
var client = new ODataClient(new ODataClientSettings("https://services.odata.org/sferp/")
{
OnTrace = (x, y) => Console.WriteLine(string.Format(x, y))
});
Building the Typed Classes
This library doesn't help you to build any Typed classes of the responses (tables/views) from the given service as we do this with the entity framework. To build the typed DTO classes, we need to fetch the metadata from the configured Odata web service by appending $metadata at the end of the base URL as follows.
https://services.odata.org/sferp/$metadata
Metadata will be displayed in XML format, we need to identify the elements for the table and their columns with datatypes to create classes accordingly for each table.
Retrieving Data Collection
As we did with initializing the communication to the service, now we need to fetch some data from the service. For example, the following statement will fetch all the data in the table Articles with the help of Odata protocol.
var articles = await client
.For<Article>()
.FindEntriesAsync();
Here, the client is an object of the ODataClient class.
There is a problem with the above statement. If this Article table has millions of records, your HTTP call will block or return a timeout exception and you cannot achieve your requirement. To avoid such situations, we need to use annotations defined in this library.
Annotations will help to minimize the load on the network by limiting the records in a single fetch. Along with records it also sends the link to fetch the next set of records so that this can be fetched until all records get fetched from the Articles table.
var annotations = new ODataFeedAnnotations();
var article = await client
.For<Article>()
.FindEntriesAsync(annotations)
.ToList();
while (annotations.NextPageLink != null)
{
article.AddRange(await client
.For<Article>()
.FindEntriesAsync(annotations.NextPageLink, annotations)
);
}
In the above code, the first call will fetch a set of 8 records (by default or it can decide as per network speed to avoid timeout exception) along with nextpagelink property. Just this property will set the URL of OData web service to fetch the next page of records.
Include Associated Data Collections
So far, we fetched the table directly but we can also have the requirement to fetch or include in terms of entity framework all their constraint key records as a response to the request. To achieve it, our library provides an Expand() method to declare all included tables' information so that the OData API will associate it and map to the response object while sending the response.
var articles = await client
.For<Article>()
.Expand(x => new { x.Ratings, x.Comments })
.FindEntryAsync();
In the above example, the system will fetch all information along with ratings and comment data as part of each article record by joining it accordingly at the web service end.
Authentication
So far, we have understood how to configure and consume the data from the existing Odata service API. Now, in real-time to make their data secure, API should authenticate the request as well as the requested client. To do so, we need to generate a token based on the credentials shared by API services.
The following are the codebases to prepare the ODataClient object by generating the token based on given credentials.
private ODataClient GetODataClient()
{
try
{
string url = _config.GetSection("APIDetails:URL").Value;
String username = _config.GetSection("APIDetails:UserName").Value;
String password = _config.GetSection("APIDetails:Password").Value;
String accessToken = System.Convert.ToBase64String(System.Text.Encoding.GetEncoding("ISO-8859-1").GetBytes(username + ":" + password));
var oDataClientSettings = new ODataClientSettings(new Uri(url));
oDataClientSettings.BeforeRequest += delegate (HttpRequestMessage message)
{
message.Headers.Add("Authorization", "Basic " + accessToken);
};
var client = new ODataClient(oDataClientSettings);
Simple.OData.Client.V4Adapter.Reference();
return client;
}
catch(Exception ex)
{
LogError("", $"Failed to connect API Services : {ex.Message}");
}
return null;
}
In the above code, we are getting URL, Username, and password from appsettings.json file and then creating a Basic token by converting the username and password strings. Once the token is generated, we are sending this token in the Authorization header by using the ODataClientSettings object and creating the ODataClient object.
Once this ODataClient object is created, we can request the data from OData web services as discussed above.
I hope this article helped you to understand how C# based client applications can be created and used to consume existing OData API services.