Concepts

This topic explains some of the key concepts for using the Oracle Cloud Infrastructure SDK for .NET.

Raw Requests

Raw requests can be useful, and in some cases necessary. Typical use cases include using your own HTTP client, making a OCI-authenticated request to an alternate endpoint, or making a request to a OCI API that is not currently supported in the SDK. The SDK for .NET exposes the OciHttpClientHandler class that you can use to create a client handler which signs the request.

This raw request example on GitHub shows how to use the OciHttpClientHandler.

Setting the Endpoints

Service endpoints can be set in several ways:

  • Call SetEndpoint(<YOUR_ENDPOINT>) on the service client instance. This lets you specify a full host name (for example, https://www.example.com).
  • Call SetRegion(<YOUR_REGION_ID>) on the service client instance. This selects the appropriate host name for the service for the given region. However, if the service is not supported in the region you set, the SDK for .NET returns an error.
  • Call SetRegion(<Region>) on the service client instance. For example, SetRegion(Region.US_PHOENIX_1).
  • Pass the region in the configuration file. For more information, see SDK and CLI Configuration File.

An example on setting endpoint can be found on GitHub.

Note that a service client instance cannot be used to communicate with different regions. If you need to make requests to different regions, create multiple service client instances.

New Region Support

If you are using a version of the SDK released prior to the announcement of a new region, you can use a workaround to reach it.

A region is a localized geographic area. For more information on regions and how to identify them, see Regions and Availability Domains.

A realm is a set of regions that share entities. You can identify your realm by looking at the domain name at the end of the network address. For example, the realm for xyz.abc.123.oraclecloud.com is oraclecloud.com.

You must first call Region.Register() to register the new region, and then you can set the region by either using the configuration file or by calling the SetRegion method.

An example on adding a new region can be found on GitHub.

oraclecloud.com Realm

For regions in the oraclecloud.com realm, you can pass new region names just as you would pass ones that are already defined in the Region enum for your SDK version.

To set the region:

using Oci.IdentityService;
 
var identityClient = new IdentityClient(<IAuthenticationDetailsProvider>);
identityClient.SetRegion("US_NEWYORK_1");

Other Realms

For regions in realms other than oraclecloud.com, you can use the following workarounds to reach new regions with earlier versions of the SDK.

To set the region:

using Oci.IdentityService;
 
var identityClient = new IdentityClient(<IAuthenticationDetailsProvider>);
identityClient.SetEndpoint("https://<your_endpoint>.com");

Uploading Large Objects

The Object Storage service supports multipart uploads to make large object uploads easier by splitting the large object into parts. The SDK for .NET supports raw multipart upload operations for advanced use cases, as well as a higher level upload class that uses the multipart upload APIs. Managing Multipart Uploads provides links to the APIs used for multipart upload operations. Higher level multipart uploads are implemented using the UploadManager, which will: split a large object into parts for you, upload the parts in parallel, and then recombine and commit the parts as a single object in storage.

The UploadObject example shows how to use the UploadManager to automatically split an object into parts for upload to simplify interaction with the Object Storage service.

Retries

You can configure the SDK for .NET to retry SDK operations that fail. The SDK allows you to specify the strategy to use for how retries are handled, including the number of times to retry, the condition under which the SDK should retry an operation, and when to stop retrying an operation. You can set these parameters at the client level and at the individual request level.

Delay Strategy

You can define your own delay strategy with the GetNextDelayInSeconds property. The SDK for .NET provides two different Delay Strategies::

  • GetFixedDelayInSeconds (seconds) - Each retry is delayed by a specified number of seconds.
  • GetExponentialDelayInSeconds - The delay time for subsequent retry calls increases by an exponential factor of 2.

Termination Strategy

You can define a termination strategy with two properties:

  • TotalElapsedTimeInSecs (seconds) - Defines total duration in seconds for which the retry attempts.
  • MaxAttempts (attempts) - Defines the total number of retry attempts.

Retryable Status Code Families

You can specify a list of status codes to retry using the RetryableStatusCodeFamilies property. For example:
 // Retryable status code family - this will make the SDK retry for all 5xx status codes:
 RetryableStatusCodeFamilies = new List<int>(new int[] { 5 }),

Retryable Errors

This property is a collection of integer and string tuples containing status and error codes returned by a service error for which the SDK can retry. For example:
// Retry on the following HTTP status and error codes:
RetryableErrors = new Collection<Tuple<int, string>>(new Tuple<int, string>[] {
   new Tuple<int, string>(409, "IncorrectState"),
   new Tuple<int, string>(429, "TooManyRequests")

Retry Examples

.NET

This example shows how to configure and use retries with the SDK for .NET:

/*
 * Copyright (c) 2020, 2021, Oracle and/or its affiliates. All rights reserved.
 * This software is dual-licensed to you under the Universal Permissive License (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl or Apache License 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose either license.
 */
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Threading.Tasks;
 
using Oci.Common;
using Oci.Common.Auth;
using Oci.Common.Retry;
using Oci.Common.Waiters;
using Oci.IdentityService;
using Oci.IdentityService.Models;
using Oci.IdentityService.Requests;
using Oci.IdentityService.Responses;
 
namespace Oci.Examples
{
    public class RetryExample
    {
        private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
        const string OciConfigProfileName = "DEFAULT";
        public static async Task MainRetry()
        {
            string compartmentId = Environment.GetEnvironmentVariable("OCI_COMPARTMENT_ID");
 
            // Configuring the AuthenticationDetailsProvider. It's assumed there is a default OCI config file
            // "~/.oci/config", and a profile in that config with the name OciConfigProfileName . Make changes to the following
            // line if needed and use ConfigFileAuthenticationDetailsProvider(configurationFilePath, profile);
            var provider = new ConfigFileAuthenticationDetailsProvider(OciConfigProfileName);
            // Create a client for the service to enable using its APIs
            var client = new IdentityClient(provider, new ClientConfiguration());
            try
            {
                await ListOciRegions(client);
                await ListOciRegionSubscriptions(client, compartmentId);
            }
            catch (Exception e)
            {
                logger.Info($"Received exception due to {e.Message}");
            }
            finally
            {
                client.Dispose();
            }
        }
 
        private static async Task ListOciRegions(IdentityClient client)
        {
            // Create a retry configuration to override defaults
            RetryConfiguration retryConfiguration = new RetryConfiguration
            {
                // Enable exponential backoff
                GetNextDelayInSeconds = DelayStrategy.GetExponentialDelayInSeconds,
                //  Defines total duration in seconds for which the retry attempts.
                TotalElapsedTimeInSecs = 600,
                // Defines the total number of retry attempts.
                MaxAttempts = 4,
                // Retryable status code family - this will make the SDK retry for all 5xx status codes
                RetryableStatusCodeFamilies = new List<int>(new int[] { 5 }),
                // Retry on the following HTTP status and error codes
                RetryableErrors = new Collection<Tuple<int, string>>(new Tuple<int, string>[] {
                    new Tuple<int, string>(409, "IncorrectState"),
                    new Tuple<int, string>(429, "TooManyRequests")
                })
            };
            // List regions
            var listRegionsRequest = new ListRegionsRequest();
            ListRegionsResponse listRegionsResponse = await client.ListRegions(listRegionsRequest, retryConfiguration);
            logger.Info("List Regions");
            logger.Info("=============");
            foreach (Oci.IdentityService.Models.Region reg in listRegionsResponse.Items)
            {
                logger.Info($"{reg.Key} : {reg.Name}");
            }
        }
        private static async Task ListOciRegionSubscriptions(IdentityClient client, string compartmentId)
        {
            // List RegionSubscriptions
            ListRegionSubscriptionsRequest listRegionSubscriptionsRequest = new ListRegionSubscriptionsRequest
            {
                TenancyId = compartmentId
            };
            RetryConfiguration retryConfiguration = new RetryConfiguration
            {
                // Enable exponential backoff with Full Jitter.
                GetNextDelayInSeconds = GetJitterDelayInSeconds
            };
            ListRegionSubscriptionsResponse listRegionSubscriptionsResponse = await client.ListRegionSubscriptions(listRegionSubscriptionsRequest, retryConfiguration);
            List<RegionSubscription> regionSubscriptions = listRegionSubscriptionsResponse.Items;
            logger.Info("List RegionSubscriptions");
            logger.Info("=========================");
            foreach (RegionSubscription regionSubscription in regionSubscriptions)
            {
                logger.Info($"{regionSubscription.RegionName} : {regionSubscription.RegionKey}");
            }
        }
 
        /// <summary>
        /// Defining a custom retry strategy that mimics an exponential backoff with full jitter to reduce
        /// contention between competing calls
        /// </summary>
        private static double GetJitterDelayInSeconds(int retryAttempt)
        {
            Random random = new Random();
            return random.NextDouble() * Math.Pow(2, retryAttempt);
        }
    }
}

Paginated Responses

For large result sets, most OCI calls supported paginated responses. Paginated responses require you to make multiple calls to the list operation, passing in the value of the most recent response's next token. The pagination module allows you to:

  • Load all possible results from a list call in one call
  • Lazily load results using token-based paginated responses

For an example on how to use these functions, please see GitHub.

Polling with Waiters

The SDK offers waiters that allow your code to wait until a specific resource reaches a desired state. A waiter can be invoked in both a blocking or a non-blocking (with asychronous callback) manner, and will wait until either the desired state is reached or a timeout is exceeded. Waiters abstract the polling logic you would otherwise have to write into an easy-to-use single method call.

Waiters are obtained through the service client (client.Waiters). Both a Get<Resource>Request and the desired lifecycle state are passed in to the Waiters.For<Resource> method.

For example:

using Oci.CoreService;
 
public static Instance WaitForInstanceProvisioningToComplete(ComputeClient computeClient, String instanceId)
{
    GetInstanceRequest getInstanceRequest = new GetInstanceRequest { InstanceId = instanceId };
    GetInstanceResponse getInstanceResponse = computeClient.Waiters.ForInstance(getInstanceRequest, Instance.LifecycleStateEnum.Running).Execute();
    return getInstanceResponse.Instance;
}

Each waiters.for<Resource> method has two versions:

One version uses the default polling values. For example:

Waiters.ForInstance(getInstanceRequest, Instance.LifecycleStateEnum.Running)

The other version gives you full control over how long to wait and how much time between polling attempts. For example:

using Oci.Common.Waiters;
 
WaiterConfiguration waiterConfiguration = new WaiterConfiguration
{
     MaxAttempts = 5,
     GetNextDelayInSeconds = DelayStrategy.GetExponentialDelayInSeconds
};
Waiters.ForInstance(getInstanceRequest, waiterConfiguration, Instance.LifecycleStateEnum.Running)

Some API requests return work request identifiers to track the progress of the request. Waiters can be used to wait until the work request has reached the desired state.

For example:


using Oci.Common.Waiters;
using Oci.ContainerengineService;
using Oci.ContainerengineService.Responses;
using Oci.ContainerengineService.Requests;
 
CreateClusterResponse clusterResponse = await containerEngineClient.CreateCluster(createClusterRequest);
GetWorkRequestResponse workRequestResponse = WaitForWorkRequestFinished(containerEngineClient, workRequestId);
private static GetWorkRequestResponse WaitForWorkRequestFinished(ContainerEngineClient containerEngineClient, string workRequestId)
{
   var waiterConfiguration = new WaiterConfiguration
   {
       MaxAttempts = 5,
       GetNextDelayInSeconds = DelayStrategy.GetExponentialDelayInSeconds
   };
   GetWorkRequestRequest getWorkRequestRequest = new GetWorkRequestRequest
   {
      WorkRequestId = workRequestId
   };
   return containerEngineClient.Waiters.ForWorkRequest(getWorkRequestRequest, waiterConfiguration, WorkRequestStatus.Succeeded).Execute();
}

Further examples on waiters can be found on Github.

Exception Handling

When handling an exception, you can get more information about the HTTP request that caused it, such as the status code or service code. You can also get the request ID when handling an exception by looking at the opcRequestId property of the error object.

For example:


try {
  var response = await identityClient.ListAllUsersResponses(listUserReq);
} catch (OciException e) {
  logger.Info($"requestId: {e.OpcRequestId}");
  logger.Info($"StatusCode: {e.StatusCode}");
  logger.Info($"ServiceCode: {e.ServiceCode}"); 
 }

Authenticating with Instance Principals

Instance principals is an IAM service feature that enables instances to be authorized actors (or principals) that can perform actions on service resources. Each compute instance has its own identity, and it authenticates using the certificates that are added to it. These certificates are automatically created, assigned to instances and rotated, preventing the need for you to distribute credentials to your hosts and rotate them.

Note

For more information on instance principals, see Calling Services from an Instance.
While using the .NET SDK on an OCI Instance, you can use InstancePrincipalsAuthenticationDetailsProvider class as shown in the following example. You will not need to provide your authentication details when you use this class.
using Oci.Common.Auth;
using Oci.IdentityService;
 
// Creates an Instance Principal provider that holds authentication details of the OCI Instance
var instanceProvider = new InstancePrincipalsAuthenticationDetailsProvider();
 
// Create a client for the service to enable using its APIs
var client = new IdentityClient(instanceProvider);

A full working example of using instance principals with the OCI .NET SDK can be found on GitHub.