Envi OData Examples
In this section, you can find some simple examples of the request preparation and request sending for the Inventory module using the C# programming language.
To communicate with Envi API, do the following:
- Obtain a JWT access token.
- Send an HTTP request to a resource URL:
- Specify the
Authorization
header with a validaccess_token
. - Specify other request headers, such as
Content-Type
orAPI-version
. - Prepare request payload (body) for the POST, PUT, or PATCH request.
- Construct correct query options as a part of the URL for the GET request.
- Specify the
- Handle the response.
Note
OData protocol has additional metadata information included in a response.
Obtaining a JWT access token
The following example, written in the C# programming language, shows how to obtain a JWT token and how to use refresh_token
in case access_token
gets expired. For your convenience, introduce a class for representation of the authentication response. In JSON format, the authentication response looks like this.
{
"access_token": "access token information",
"token_type": "bearer",
"expires_in": 59,
"refresh_token": "refresh token information"
}
public class JWT
{
public JWT()
{
IssuedAt = DateTime.Now;
}
[JsonProperty("access_token")]
public string AccessToken { get; set; }
[JsonProperty("token_type")]
public string TokenType { get; set; }
[JsonProperty("expires_in")]
public int ExpiresIn { get; set; }
[JsonProperty("refresh_token")]
public string RefreshToken { get; set; }
[JsonIgnore]
public DateTime IssuedAt { get; }
[JsonIgnore]
public bool IsValid => IssuedAt.AddSeconds(ExpiresIn) > DateTime.Now;
}
In the original JSON, there are two additions except for properties:
IssuedAt
property – holds the date and time information when the token is received. The value of the property is populated in the constructor, so each time you de-serialize an authentication request, this property will be populated with the current date and time.IsValid
property – holds some simple logic to indicate whetheraccess_token
is still valid based on the IssuedAt value.
The example uses the Newtonsoft.Json
nugget package to perform serialization or deserialization. To preserve correct behavior, additional properties are marked with the JsonIgnore
attribute and all others–with the JsonProperty
attribute.
To communicate with API, use an instance of the HttpClient
standard .net class. The Private
field is intended to hold the HttpClient
instance and appropriate property with additional logic for instance creation in case it does not exist.
/// <summary>
/// Base address of OData API Service
/// </summary>
private static readonly string _baseAddress = "https://api-demo.envi.net";
/// <summary>
/// Holds instance of correctly initialized HTTP Client
/// </summary>
private static HttpClient _client;
private static HttpClient Client
{
get
{
if (_client == null)
{
_client = new HttpClient {
BaseAddress = new Uri(_baseAddress)
};
_client.DefaultRequestHeaders.Accept.Add(newMediaTypeWithQualityHeaderValue("application/json"));
}
return _client;
}
}
/// <summary>
/// Holds instance of obtained JWT token
/// </summary>
private static JWT _token;
private static async Task<JWT> GetToken()
{
if (_token == null)
{
List<KeyValuePair<string, string>> requestData = new List<KeyValuePair<string, string>> {
new KeyValuePair<string, string>("grant_type", "password"),
new KeyValuePair<string, string>("client_id", "your_cliet_id_goes_here"),
new KeyValuePair<string, string>("username", "user_name"),
new KeyValuePair<string, string>("password", "password")
};
_token = await RequestToken(requestData);
}
if (!_token.IsValid)
{
List<KeyValuePair<string, string>> requestData = new List<KeyValuePair<string, string>> {
new KeyValuePair<string, string>("grant_type", "refresh_token"),
new KeyValuePair<string, string>("client_id", "your_cliet_id_goes_here"),
new KeyValuePair<string, string>("refresh_token", _token.RefreshToken)
};
_token = await RequestToken(requestData);
}return _token;
}
private static async Task<JWT> RequestToken(List<KeyValuePair<string, string>> requestData)
{
var response = await Client.PostAsync("oauth2/token", new FormUrlEncodedContent(requestData));
var content = await response.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<JWT>(content);
}
Sending HTTP request to a resource URL
The examples are based on the Inventory module, so let’s introduce the .net class, which represents the Inventory entity and two additional classes, which will be helpful for response de-serialization.
The Inventory entity is generated based on the Inventory description from the metadata endpoint.
The following classes are provided for the default metadata level minimal
.
public class Inventory
{
public Guid? InventoryId { get; set; }
public Guid? OrganizationId { get; set; }
public string OrganizationName { get; set; }
public Guid? InventoryGroupId { get; set; }
public string InventoryNo { get; set; }
public string InventoryGroupName { get; set; }
public string InventoryDescription { get; set; }
public string InventoryDescription2 { get; set; }
public string StockUOM { get; set; }
public string ARBillingCode { get; set; }
public string HCPCSCode { get; set; }
public string Notes { get; set; }
public DateTime? DateAdded { get; set; }
public Guid? AddedId { get; set; }
public string AddedByName { get; set; }
public DateTime? LastUpdated { get; set; }
public Guid? LastUpdatedBy { get; set; }
public string LastUpdatedByName { get; set; }
public bool? ActiveStatus { get; set; }
public string UNSPSC { get; set; }
public bool? IsLatex { get; set; }
public Guid? ClassificationId { get; set; }
public string ClassificationName { get; set; }
public Guid? Classification2Id { get; set; }
public string Classification2Name { get; set; }
public string DefaultExpenseLedgerNo { get; set; }
public string DefaultAssetLedgerNo { get; set; }
public Guid? PeriopCategoryId { get; set; }
public string PeriopCategory { get; set; }
public string ItemType { get; set; }
public bool Billable { get; set; }
public byte? SystemTypeId { get; set; }
public string SystemType { get; set; }
}
Retrieving entities list
The following generic class represents the response for the endpoint, which returns a paged list of entities.
Envi API response returns an object that contains a list with entities of the requested type, link to metadata endpoint with entity type description (odata.context
), the number of entities in the database that conform to specified criteria (odata.count
), and URL for the next page of data retrieving (odata.nextLink
).
Before performing the request, set the authorization information in the request header. It is recommended to do verification of access_token
validity each time before request sending since its guarantees that the request will not return a 401 error code.
public class ODataListResponse<T>
{
[JsonProperty("@odata.context")]
public string ODataContext { get; set; }
[JsonProperty("@odata.count")]
public int ODataCount { get; set; }
[JsonProperty("@odata.nextLink")]
public string ODataNextLink { get; set; }
public List<T> Value { get; set; }
}
Note
It is strongly recommended to omit unexpected values in response while building integrations.
The following method will help to do this job.
private static async Task<bool> SetAuthHeader()
{
var auth = await GetToken();
if (auth != null)
{
Client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", auth.AccessToken);
}
return auth != null;
}
SetAuthHeader
method returns a Boolean value that will be not omitted but awaited. And, the following method performs actually the call itself.
private static async Task<ODataListResponse> GetInventoryList()
{
SetAuthHeader();
var response = await Client.GetAsync("/odata/Inventory");
return JsonConvert.DeserializeObject<ODataListResponse>(await response.Content.ReadAsStringAsync());
}
The OData protocol uses the system query options $skip
and $top
to implement server-driven paging, which allows clients to request a specific subset of data from a large collection. In addition to these options, OData pagination also utilizes odata.nextLink
.
The odata.nextLink
is a URL included in each list type response to show more results are available beyond what was returned in the first request.
When you make a request to an OData API, it may return only a subset of the total results (for example, 100 out of 1000). If there are other results, the API will include odata.nextLink
in the response, which you can use to retrieve the next subset of the requested collection.
If the response does not include odata.nextLink
, it means that all records have been retrieved.
The following method allows you to retrieve all entities in the collection by using odata.nextLink
.
private static async Task<ODataListResponse> GetInventoryList()
{
var result = new List<T>();
while (!string.IsNullOrEmpty(requestUri))
{
var list = await Get<ODataListResponse<T>>(requestUri);
if (list.Value.Any())
{
result.AddRange(list.Value);
}
requestUri = list.ODataNextLink;
if (!string.IsNullOrEmpty(requestUri))
{
requestUri = requestUri.Replace(_baseAddress, string.Empty);
}
}
return result;
}
Retrieving entity details
The following method retrieves details of an Inventory item by the specified ID.
private static async Task<Inventory> GetInventoryById(Guid inventoryId)
{
await SetAuthHeader();
var response = await Client.GetAsync($"/odata/Inventory({inventoryId})");
return JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync());
}
Note
It is strongly recommended to omit unexpected values in response while building integrations.
New entity creation
To add a new entity using Envi API, do the following:
- Send the POST request to an appropriate resource, then provide new entity details with filled-in all required fields in JSON format.
- If it's an Inventory item, populate the Inventory entity.
- Serialize entity to JSON format and POST it to the Inventory endpoint.
- Create a new Inventory item with all required fields.
var inventory = new Inventory
{
InventoryGroupId = new Guid("88212fc1-7698-40b3-8642-edd4ff793fcd"),
InventoryNo = new Random().Next(0, int.MaxValue).ToString(),
InventoryDescription = "InventoryDescription",
StockUOM = "EA",
SystemTypeId = 1,
ActiveStatus = true
};
private static string Serialize<T>(T target)
{
var serializerSettings = new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver(),
NullValueHandling = NullValueHandling.Ignore
};
return JsonConvert.SerializeObject(target, serializerSettings);
}
public class ODataSingleValueResponse<T>
{
[JsonProperty("@odata.context")]
public string ODataContext { get; set; }
public T Value { get; set; }
}
private static async Task<ODataSingleValueResponse<Guid>> PostInventory(Inventory newItem)
{
await SetAuthHeader();
var content = new StringContent(Serialize(newItem), Encoding.UTF8, "application/json");
var response = await Client.PostAsync("/odata/Inventory", content);
return JsonConvert.DeserializeObject<ODataSingleValueResponse>(await response.Content.ReadAsStringAsync());
}
Update existing entity
To update an existing entity using Envi API, do the following:
- Send the PUT request to an appropriate resource, and then provide the ID of the entity that will be updated as well as entity details with all required fields filled-in in JSON format.
- In case of an Inventory item, populate the Inventory entity.
- Serialize it to the JSON format and PUT it to the Inventory endpoint.
- Retrieving entity details occurs before entity updating:
- Then, update some fields with new information:
- Send resulting entity back to API:
private static async Task<bool> PutInventory(Inventory inventory)
{
await SetAuthHeader();
var content = new StringContent(Serialize(inventory), Encoding.UTF8, "application/json");
var response = await Client.PutAsync($"odata/Inventory({inventory.InventoryId})", content);
return response.IsSuccessStatusCode;
}
Partially update existing entity
In addition to full entity update, Envi API supports partial update based on the provided data with a help of the PATCH HTTP method. In case of entity patching, only changed fields should be sent to API. For example, if you want to update only Inventory No filed, do the following:
-
Create a new Inventory entity.
-
Set the only fields that have been changed, and then use the PATCH endpoint along with the target entity ID.
Inventory patch = new Inventory
{
InventoryNo = "78-95-1938"
};
var updateSuccessfull = await PatchInventory(new Guid("88212fc1-7698-40b3-8642-edd4ff793fcd"), patch);
private static async Task<bool> PatchInventory(Guid entityId, Inventory patch)
{
await SetAuthHeader();
var content = new StringContent(Serialize(patch), Encoding.UTF8, "application/json");
var response = await Client.PatchAsync($"odata/Inventory({entityId})", content);
return response.IsSuccessStatusCode;
}
Since .net class HttpClient does not contain implementation of PatchAsync
, for this purpose, use the following extension method.
public static Task<HttpResponseMessage> PatchAsync(this HttpClient client, string requestUri, HttpContent content)
{
HttpRequestMessage request = new HttpRequestMessage {
Method = new HttpMethod("PATCH"),
RequestUri = new Uri($"{client.BaseAddress}{requestUri}"),
Content = content
};
return client.SendAsync(request);
}