-
Arnaud Champion authored4dd76c15
- OData.Client
- Installation
- NuGet Package
- Direct Project Reference
- Key Features
- Quick Start
- Advanced Configuration
- Usage Examples
- Simple Query
- Query with Filters
- Retrieving an Entity by Key
- Error Handling
- Using a Custom HttpClient
- Authentication
- Basic Authentication
- Bearer Token
- API Key
- Retry Policies
- Simple Retry
- Exponential Backoff Retry
- Custom Retry
- JSON Serializers
- Newtonsoft.Json (default)
- System.Text.Json
- HTTP Headers
- Adding Global Headers
- Adding Request-Specific Headers
- Accessing Response Headers
- Compatibility with Different OData Services
- Standard Service
- Minimal Service (TripPin)
- Microsoft Service (Graph API)
- Best Practices
- Client Instance Reuse
- Using with Dependency Injection
- Performance Optimization
- License
README.md 8.54 KiB
OData.Client
A lightweight and minimalist OData client library for .NET 9.0, designed to be simple, performant, and without heavy dependencies.
Installation
NuGet Package
dotnet add package OData.Client
Direct Project Reference
git clone https://github.com/your-username/OData.Client.git
dotnet add reference path/to/OData.Client/OData.Client.csproj
Key Features
- Minimalist: Supports only essential operations (Select, Skip, Take, Key, Where)
- Modern: Targeted for .NET 9.0
- Lightweight: No dependencies on Microsoft or third-party OData libraries
- Flexible: Fluent API for intuitive use
- Robust: Built-in error handling, retries, and timeouts
- Compatible: Supports different types of OData services (Standard, Minimal, Microsoft)
Quick Start
// Create a simple client
var client = OData.CreateClient("https://services.odata.org/TripPinRESTierService");
// Execute a query
var people = await client.For<Person>("People")
.Select("UserName", "FirstName", "LastName")
.Take(5)
.ExecuteAsync();
Advanced Configuration
// Using the builder for advanced configuration
var client = new ODataClientBuilder()
.WithBaseUrl("https://services.odata.org/TripPinRESTierService")
.WithServiceCompatibility(ODataServiceType.Minimal)
.WithTimeout(TimeSpan.FromSeconds(30))
.WithHeader("X-Custom-Header", "CustomValue")
.WithRetryPolicy(new ExponentialBackoffRetryPolicy(
maxRetries: 3,
initialDelay: TimeSpan.FromMilliseconds(200),
maxDelay: TimeSpan.FromSeconds(2)))
.Build();
Usage Examples
Simple Query
var client = OData.CreateClient("https://services.odata.org/TripPinRESTierService");
var people = await client.For<Person>("People")
.Take(5)
.ExecuteAsync();
Query with Filters
var client = OData.CreateClient("https://services.odata.org/TripPinRESTierService");
var malePeople = await client.For<Person>("People")
.Where("Gender eq 'Male'")
.Select("UserName", "FirstName", "LastName")
.Skip(2)
.Take(3)
.ExecuteAsync();
Retrieving an Entity by Key
var client = OData.CreateClient("https://services.odata.org/TripPinRESTierService");
var person = await client.For<Person>("People")
.Key("russellwhyte")
.ExecuteAsync();
Error Handling
try
{
var client = OData.CreateClient("https://services.odata.org/InvalidService");
var people = await client.For<Person>("People").ExecuteAsync();
}
catch (HttpRequestException ex)
{
Console.WriteLine($"HTTP Error: {ex.Message}");
}
catch (ODataException ex)
{
Console.WriteLine($"OData Error: {ex.Message}");
}
Using a Custom HttpClient
var httpClient = new HttpClient
{
Timeout = TimeSpan.FromSeconds(60)
};
var customHttpClient = new DefaultHttpClient(httpClient);
var client = new ODataClient(customHttpClient,
new ODataClientOptions { BaseUrl = "https://services.odata.org/TripPinRESTierService" });
Authentication
Basic Authentication
var client = OData.CreateClientWithBasicAuth(
"https://api.example.com/odata",
"username",
"password");
Bearer Token
var client = new ODataClientBuilder()
.WithBaseUrl("https://api.example.com/odata")
.WithBearerToken("your-token-here")
.Build();
API Key
var client = new ODataClientBuilder()
.WithBaseUrl("https://api.example.com/odata")
.WithAuthentication(new ApiKeyProvider("api_key", "your-api-key-here", ApiKeyLocation.QueryString))
.Build();
Retry Policies
Simple Retry
var client = new ODataClientBuilder()
.WithBaseUrl("https://services.odata.org/TripPinRESTierService")
.WithRetryPolicy(new DefaultRetryPolicy(3))
.Build();
Exponential Backoff Retry
var client = new ODataClientBuilder()
.WithBaseUrl("https://services.odata.org/TripPinRESTierService")
.WithRetryPolicy(new ExponentialBackoffRetryPolicy(
maxRetries: 5,
initialDelay: TimeSpan.FromMilliseconds(100),
maxDelay: TimeSpan.FromSeconds(5)))
.Build();
Custom Retry
public class MyCustomRetryPolicy : RetryPolicyBase
{
public MyCustomRetryPolicy() : base(3) { }
protected override TimeSpan GetDelayBeforeNextRetry(int attemptNumber, Exception exception)
{
return TimeSpan.FromMilliseconds(200 * attemptNumber);
}
protected override bool ShouldRetry(Exception exception)
{
if (exception is HttpRequestException httpEx)
{
return httpEx.Message.Contains("500") ||
httpEx.Message.Contains("503");
}
return false;
}
}
// Usage
var client = new ODataClientBuilder()
.WithBaseUrl("https://services.odata.org/TripPinRESTierService")
.WithRetryPolicy(new MyCustomRetryPolicy())
.Build();
JSON Serializers
Newtonsoft.Json (default)
var client = new ODataClientBuilder()
.WithBaseUrl("https://services.odata.org/TripPinRESTierService")
.WithJsonSerializer(new NewtonsoftJsonSerializer())
.Build();
System.Text.Json
var client = new ODataClientBuilder()
.WithBaseUrl("https://services.odata.org/TripPinRESTierService")
.WithJsonSerializer(new SystemTextJsonSerializer())
.Build();
HTTP Headers
Adding Global Headers
var client = new ODataClientBuilder()
.WithBaseUrl("https://services.odata.org/TripPinRESTierService")
.WithHeader("X-Custom-Header", "CustomValue")
.WithHeader("X-Api-Version", "1.0")
.Build();
Adding Request-Specific Headers
var client = OData.CreateClient("https://services.odata.org/TripPinRESTierService");
var person = await client.For<Person>("People")
.Key("russellwhyte")
.WithHeader("X-Request-ID", Guid.NewGuid().ToString())
.ExecuteAsync();
Accessing Response Headers
var client = new ODataClientBuilder()
.WithBaseUrl("https://services.odata.org/TripPinRESTierService")
.WithOptions(options => options.IncludeResponseHeaders = true)
.Build();
var response = await client.For<Person>("People")
.Key("russellwhyte")
.ExecuteAsync();
var person = response.First();
var serverHeader = response.Headers.GetHeader("Server");
Compatibility with Different OData Services
Standard Service
var client = OData.CreateClientForService(
"https://services.odata.org/V4/Northwind/Northwind.svc",
ODataServiceType.Standard);
Minimal Service (TripPin)
var client = OData.CreateClientForService(
"https://services.odata.org/TripPinRESTierService",
ODataServiceType.Minimal);
Microsoft Service (Graph API)
var client = OData.CreateClientForService(
"https://graph.microsoft.com/v1.0",
ODataServiceType.Microsoft);
Best Practices
Client Instance Reuse
Reuse ODataClient
instances to benefit from the underlying HTTP connection pooling:
// Create a single instance and reuse it
var client = OData.CreateClient("https://services.odata.org/TripPinRESTierService");
// Use the same instance for all queries
var people = await client.For<Person>("People").ExecuteAsync();
var trips = await client.For<Trip>("Trips").ExecuteAsync();
Using with Dependency Injection
// In Program.cs or Startup.cs
services.AddSingleton<IODataClient>(provider =>
{
return OData.CreateClientForService(
"https://services.odata.org/TripPinRESTierService",
ODataServiceType.Minimal);
});
// In your service
public class MyService
{
private readonly IODataClient _client;
public MyService(IODataClient client)
{
_client = client;
}
public async Task<IEnumerable<Person>> GetPeopleAsync()
{
return await _client.For<Person>("People").ExecuteAsync();
}
}
Performance Optimization
-
Use Select to limit returned properties:
await client.For<Person>("People") .Select("UserName", "FirstName", "LastName") .ExecuteAsync();
-
Use Take/Skip for pagination:
await client.For<Person>("People") .Skip(10) .Take(10) .ExecuteAsync();
-
Use Where to filter results:
await client.For<Person>("People") .Where("Gender eq 'Male'") .ExecuteAsync();
-
Use Key to retrieve a specific entity:
await client.For<Person>("People") .Key("russellwhyte") .ExecuteAsync();
License
MIT