Skip to content

Whenever possible, use Dependency Injection (DI). If your application/framework does not allow that, you can instantiate clients also without Dependency Injection as described at the end of this page.

Note that in order to authenticate with user credentials, you will need to understand integration options and application registration.

Instantiating clients with Dependency Injection

The platform SDK supports inversion of control (IOC) pattern by providing extensions for registering services (or interfaces of client classes) within Microsoft's dependency injection container - represented by interface IServiceCollection. The user still needs to provide some input to complete the configuration, and there are some extension points that can be used to customize the behavior of the SDK. Here we describe key methods for initialization of the DHI.Platform.SDK clients.

The IAccessTokenProvider interface

The recommended way of authentication is to use user credentials and token based authentication. To make this happen, the client needs a Bearer token acquired during the user authentication flow. The sole responsibility of the DHI.Platform.SDK.IAccessTokenProvider implementation is to return the token from GetTokenAsync method.

DHI.Platform.SDK.Web provides several implementations that you can reuse as explained in Building BFF. If you cannot reuse any, subclass from PlatformWebAccessTokenProviderBase and implement the GetTokenAsync method and potentially override IsTokenExpired. If none of these options meet your needs, implement your own IAccessTokenProvider.

.DI.AddPlatformClients extension

This extension method will register all relevant implementations of client interfaces into the given service collection.

  • by default, all interface implementations are registered as singletons.
  • all client classes are instantiated by resolving a collection of IClientPolicy implementations into its constructor. This means that any implementations of IClientPolicy interface you register will be injected into the clients. You can use that to control the behavior of http calls, log calls or troubleshoot problems.
  • the registration procedure expects one authorization policy to be defined. As of May 2024 there are two ways how to authorize with the SDK - using a Bearer token, or using an Open API key.

Using a Bearer token (requires an implementation of IAccessTokenProvider with Task<string> GetTokenAsync(); method):

collection.AddPlatformClients(o => { o.ConfigureAccessToken(tokenProvider); });

By default, the services are configured against the production environment. If you ever need to choose a different environment, use the PlatformEnvironment configuration option:

collection.AddPlatformClients(o =>
{
    o.ConfigureAccessToken(tokenProvider);
    o.PlatformEnvironment = PlatformEnvironment.Dev;
});

.Spatial.DI.AddPlatformSpatialClients extension

When you install the DHI.Platforms.SDK.Spatial package, you get access to AddPlatformSpatialClients extension. This should be used in conjunction with AddPlatformClients extension described earlier. It is applied to the result object of the AddPlatformClients call, like for example here:

collection.AddPlatformClients(o =>
    {
        o.ConfigureAccessToken(tokenProvider);
    })
    .AddPlatformSpatialClients();

The extension will add the IMultidimensionalClient and IGISClient to the service collection.

Customizing instantiation of client classes

It is possible to register services from the SDK using your own methods, or to replace certain registrations with your custom version. This might be needed if you want to call the service running on a url that your SDK version does not have embedded. By default the service addresses for the predefined environments are either embedded in the SDK, or can be discovered at runtime. If you however need to provide a different url, you can do so.

The following example replaces the singleton registration of the ComputeClient with a transient one that uses a custom url.

var collection = new ServiceCollection();

collection.AddPlatformClients(o =>
{
    o.ConfigureAccessToken(tokenProvider);
});

var descriptor =
    new ServiceDescriptor(
        typeof(IComputeClient),
        sp => new ComputeClient("http://mycustomuri", sp.GetServices<IClientPolicy>()),
        ServiceLifetime.Transient);

collection.Replace(descriptor);

.Configuration.IClientPolicy interface

The IClientPolicy interface is used to modify the properties of outgoing http requests. Some policies are applied by default, out of control of the user. These default policies will:

  • automatically send an SDK version in a header to be logged in the cloud;
  • automatically choose the version of the underlying REST API.

Another implementation of IClientPolicy is typically used for authorization.

  • The most basic policy is OpenApiKeyPolicy, which takes an Open API key (secret string) and passes it in a header to all calls. This allows the metadata service to identify the caller. This can be configured by method ConfigureOpenApiKey on the PlatformOptions object, or by registering the policy in your own way if you do not want to use the PlatformOptions.

  • Alternative way of authorization is to provide a Bearer token. For that there is AccessTokenPolicy, which accepts an implementation of IAccessTokenProvider. For each outgoing call the policy will ask the provider for a token, which is then prefixed with a "Bearer: " string and sent in an Authorization header. You can either register the policy yourself, or you can only implement the IAccessTokenProvider and setup its use by calling a method ConfigureAccessToken on the PlatformOptions object.

Client based on authorization token provided by user

The ClientFactory provides generic methods for creating a client with all supported authorization methods (SAS token, Access token, OpenApiKey) to simplify usage. The example shows the simplicity of creating a project client.

var factory = new ClientFactory(new ClientFactoryOptions()
{
    PlatformEnvironment = PlatformEnvironment.Dev
});

var projectClient = factory.CreateClientSasToken<IProjectClient>("sas-token"); 

Shared Access Signature (SAS) token usage in the SDK

By default the SDK calls to any service in the platform goes through a gateway, which is a single service (single url) that can handle different authentication methods and then proxies calls to other services (such as timeseries service, multidimensional service) as needed. This simplifies the authentication flow, but may impact performance, because all requests have to travel through the gateway. In some cases, it may be beneficial to make the SDK use SAS token flow instead of the proxied calls.

The SAS token usage is controlled by a property called UsePlatformSasTokens.

  • When set to UsePlatformSasTokensOptions.InnerServices, the calls to inner services (such as timeseries or multidimensional service) will try to call those services directly, as opposed to going through the gateway. However, the calls still need to be authorized somehow. The inner services understand only one authorization mechanism - the SAS tokens. A SAS token can be issued to a user and contains information about the user's privileges related to a given resource (a project or a dataset). So for each call to the inner services, a SAS token has to be provided.
  • When set to UsePlatformSasTokensOptions.AllServices, the calls to inner services and also directly metadata service will try to call those services directly as is described above.
  • When set to UsePlatformSasTokensOptions.No, the calls will use the gateway as by default.

There is an interface ISasTokenProvider which is used by the *Client classes to get the sas token for each call. The SDK will provide a default implementation of this interface - one that always asks a remote service whenever a token is requested. So for each request to the inner service, there will also be an extra request to get the SAS token first. This may defeat the objective to achieve better performance, unless some smarter mechanism to get the SAS tokens is employed, like caching.

Caching of the SAS tokens is not implemented by default, because the SAS tokens belong to individual users in the platform. However, the SDK does not know under which user the requests are being made. The SDK only has mechanism for plugging in some authentication headers, but the user can not always be determined from that. If however your application runs only with a single user context, or the user can be somehow determined for each request, you can simply implement SAS token caching. See the below example.

public class CachedSasTokenProvider : ISasTokenProvider
{
    // ISasTokenClient is an interface provided by the SDK that provides access to a remote service issuing the SAS tokens
    private readonly ISasTokenClient _sasTokenClient;
    private readonly IMemoryCache _memoryCache;

    public CachedSasTokenProvider(ISasTokenClient sasTokenClient, IMemoryCache memoryCache)
    {
        _sasTokenClient = sasTokenClient;
        _memoryCache = memoryCache;
    }

    public async Task<string> GetRecursiveSasTokenAsync(Guid projectId, DateTime? expiration = null)
    {
        // The caching does not take the user context into account.
        // Make sure that this type of caching is safe to do, or adjust the mechanism. 
        // Otherwise the access rights may become violated.
        return await _memoryCache.GetOrCreateAsync($"recursivetoken-{projectId}", async entry =>
        {
            var token = await _sasTokenClient.GetRecursiveSasTokenAsync(projectId, expiration);
            if (expiration != null)
                entry.AbsoluteExpiration = expiration; // cache until the requested expiration is reached
            else
                entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(10); // cache for 10 minutes only - to be safe
            return token;
        });
    }

    public async Task<string> GetSasTokenAsync(Guid projectId, Guid? datasetId)
    {
        // The caching does not take the user context into account.
        // Make sure that this type of caching is safe to do, or adjust the mechanism. 
        // Otherwise the access rights may become violated.
        return await _memoryCache.GetOrCreateAsync($"token-{projectId}-{datasetId}", async entry =>
        {
            var token = await _sasTokenClient.GetSasTokenAsync(projectId, datasetId);
            entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(10); // cache for 10 minutes only - to be safe
            return token;
        });
    }
}
var collection = new ServiceCollection();

collection.AddPlatformClients(o =>
{
    o.ConfigureAccessToken(tokenProvider);
    o.PlatformEnvironment = PlatformEnvironment.Dev0;
    o.UsePlatformSasTokens = UsePlatformSasTokensOptions.InnerServices; // turn on sas token usage
});

// add CachedSasTokenProvider to the container
collection.AddSingleton<CachedSasTokenProvider>();

// add cache provider
collection.AddMemoryCache();

// replace the default implementation of the ISasTokenProvider interface with custom implementation
var descriptor =
    new ServiceDescriptor(
        typeof(ISasTokenProvider),
        sp => sp.GetRequiredService<CachedSasTokenProvider>(),
        ServiceLifetime.Singleton);
collection.Replace(descriptor);

Instantiating clients without Dependency Injection

This section provides guidelines on how to setup EventSubscriberClient and ComputeClient in case your application/framework is unable to leverage AddPlatformClients and Microsoft's dependency injection container.

Set up logging provider and creating logger for EventSubscriberClient:

using var loggerFactory = LoggerFactory.Create(builder => builder.AddConsole());
var logger = loggerFactory.CreateLogger<EventSubscriberClient>();

Set up authorization policy. This sample uses OpenApiKeyPolicy and that openapikey has to be provided:

var openApiKeyPolicy = new OpenApiKeyPolicy(openapikey);

Set up ServiceUriProvider and SasTokenClient:

var platformEnvironment = PlatformEnvironment.Prod;
var serviceUriProvider = new ServiceUriProvider(platformEnvironment, openApiKeyPolicy);
var sasTokenClient = new SasTokenClient(platformEnvironment, openApiKeyPolicy);

Instantiate EventSubscriberClient:

var eventSubscriberClient = new EventSubscriberClient(logger, serviceUriProvider, sasTokenClient);

Instantiate ComputeClient and create an execution:

var computeClient = new ComputeClient(serviceUriProvider, sasTokenClient, eventSubscriberClient);
    await computeClient
        .CreateExecution(projectId)
        .WithModelRun(new FemEngineHD(), "https://coreenginedev0inputs.blob.core.windows.net/data/lake-seq/lake1.m21fm")
        .WithRemoteInput(
            new RemoteInput("https://coreenginedev0inputs.blob.core.windows.net/data/lake-seq/lake1.m21fm", "lake1.m21fm"),
            new RemoteInput("https://coreenginedev0inputs.blob.core.windows.net/data/lake-seq/lake2.m21fm", "lake2.m21fm"),
            new RemoteInput("https://coreenginedev0inputs.blob.core.windows.net/data/lake-seq/lake.mesh", "lake.mesh"))
        .WithMessageHandler(new MessageHandler())
        .ExecuteAndWaitAsync();