Skip to content

commercetools/commercetools-dotnet-core-sdk

Repository files navigation

Composable Commerce .NET Core SDK

⚠️ **This Composable Commerce .NET Core SDK is deprecated effective 1st September 2022., We recommend to use our .NET Core SDK V2.

CT-logo

Travis Build Status AppVeyor Build Status NuGet Version and Downloads count

The Composable Commerce .NET Core SDK enables developers to easily communicate with the HTTP API. The developers do not need to create plain HTTP requests, but they can instead use the domain specific classes and methods to formulate valid requests.

Installation

  • Download from Nuget

    dotnet add package commercetools.Sdk.All

Technical Overview

The SDK consists of the following projects:

  • commercetools.Sdk.Client: Contains abstract commands which are used to create instances of HTTP API commands. Some commands are implemented as generic types and you must specify a domain object when using them, whereas others are specific to a domain object.
  • commercetools.Sdk.DependencyInjection: Default entry point to start using the SDK. Contains one class, DependencyInjectionSetup, which initializes all services the SDK uses and injects them into the service collection.
  • commercetools.Sdk.Domain: Models Composable Commerce domain objects.
  • commercetools.Sdk.HttpApi: Communicates directly with the HTTP API.
    • Client: Has one method, ExecuteAsync(), which executes commands. Executes commands which calls the HTTP API. Takes commands from classes in the commercetools.Sdk.Client project to construct implementations of the IHttpApiCommand interface.
    • IHttpApiCommand: Classes implementing this interface are containers for a command from a commercetools.Sdk.Client class and a request builder which builds the request to the HTTP API endpoint.
  • commercetools.Sdk.HttpApi.Domain: Handles exceptions and errors from the HTTP API.
  • commercetools.Sdk.HttpApi.Linq: Uses LINQ expressions to build queries for the where fields in the HTTP API.
  • commercetools.Sdk.HttpApi.Registration: Helper classes for things like creating service locators.
  • commercetools.Sdk.HttpApi.Serialization: Serialization and deserialization services for responses and requests to the HTTP API.

In addition, the SDK has the following directories:

  • Examples: Contains examples of common SDK use cases for MVC applications.
  • /Tests: Integration tests for the SDK. A good way for anyone using the .NET SDK to understand it futher.

Getting Started with the .NET SDK

All operations (get, query, create, update, delete and others) are available as commands that can be passed to the client. In order to use the client object, it needs to be setup first through dependency injection setup.

Basic Workflow

At a high level, to make a basic call to the API, do the following:

  1. Use the dependency injection class to set things up.
  2. get a client object from the services responsible for calling requests to the API
  3. If needed – Create a draft object as a required for the command, as needed.
  4. Use executeAsync(Command) (find commands in the Client project – specify the domain obj you're working on)
  5. Receive the response as a model.

Dependency Injection Setup

In the ConfigureServices method of Startup.cs add the following:

services.UseCommercetools(
    this.configuration); // replace with your instance of IConfiguration

This code sets "CommercetoolsClient" as the default configuration section name and the Client Credentials as the initial token flow. If other values should be set, the following method overload can be used:

services.UseCommercetools(
    this.configuration, // replace with your instance of IConfiguration
    "Client", // replace with your name of the Composable Commerce configuration section
    TokenFlow.AnonymousSession); // replace with your initial token flow

More information about token flow can be found in this document.

The previous coding examples set one client only. More information about multiple clients can be found in this document.

Default HttpClient name

The HttpClient is added by using the built-in AddHttpClient extension. In case the name of the client is not provided, the default client names are used:

  • CommercetoolsClient
  • CommercetoolsAuth

This means that no other HttpClient can have these names.

Multiple Clients

It is possible to use more than one client in the same application. The following code can be used to set it up:

IDictionary<string, TokenFlow> clients = new Dictionary<string, TokenFlow>()
{
    { "client1", TokenFlow.AnonymousSession },
    { "client2", TokenFlow.ClientCredentials }
};
services.UseCommercetools(this.configuration, clients);

The appsettings.json then needs to contain the configuration sections named the same. The clients can then be injected by using IEnumerable.

public MyController(IEnumerable<IClient> clients)

Attaching Delegating Handlers

When wanting to attach further Handlers to the HttpClient this is possible by using the service collection. E.g. adding a Polly Retry policy and Timeout policy using Microsoft.Extensions.Http.Polly:

var registry = services.AddPolicyRegistry();
var retryPolicy = HttpPolicyExtensions
    .HandleTransientHttpError()
    .OrResult(msg => msg.StatusCode == System.Net.HttpStatusCode.NotFound)
    .Or<TimeoutRejectedException>()
    .RetryAsync(3, onRetry: (exception, retryCount, context) =>
                    {
                        // logging here
                    });
registry.Add("retryPolicy", retryPolicy);

var timeOutPolicy = Policy.TimeoutAsync<HttpResponseMessage>(TimeSpan.FromSeconds(60));
registry.Add("timeoutPolicy", timeOutPolicy);

services.UseCommercetools(configuration, "Client", TokenFlow.ClientCredentials)
    .AddPolicyHandlerFromRegistry("retryPolicy")
    .AddPolicyHandlerFromRegistry("timeoutPolicy");

// for multiple clients
services.UseCommercetools(configuration, new Dictionary<string, TokenFlow>()
    {
        {"client1", TokenFlow.AnonymousSession},
        {"client2", TokenFlow.ClientCredentials}
    })
    // configure single client
    .ConfigureClient("client1", builder => builder
                                            .AddPolicyHandlerFromRegistry("retryPolicy")
                                            .AddPolicyHandlerFromRegistry("timeoutPolicy"));
    // configure all clients
    .ConfigureAllClients(builder => builder
                                            .AddPolicyHandlerFromRegistry("retryPolicy")
                                            .AddPolicyHandlerFromRegistry("timeoutPolicy"));

Please notice that default timeout of HttpClient is 100 seconds and you can change it like:

services.UseCommercetools(configuration, "Client")
               .ConfigureHttpClient(c => c.Timeout = TimeSpan.FromSeconds(60));

Configuration

The client configuration needs to be added to appsettings.json in order for the client to work. The structure is as follows:

{
    "Client": {
        "ClientId": "", // replace with your client ID
        "ClientSecret": "", // replace with your client secret
        "AuthorizationBaseAddress": "https://auth.europe-west1.gcp.commercetools.com/", // replace if needed
        "Scope": "", // replace with your scope
        "ProjectKey": "", // replace with your project key
        "ApiBaseAddress": "https://api.europe-west1.gcp.commercetools.com/"  // replace if needed
    }
}

Note! The property name "Client" can be replaced with something more specified and it can be configured in the dependency injection setup.

Using the Client

The IClient interface can be used by injecting it and calling its ExecuteAsync method for different commands.

Note! Right now only the asynchronous client exists.

The following code show the constructor injection:

private readonly IClient client;
public CategoryController(IClient client)
{
    this.client = client;
}

The following line of code gets a category by ID:

string id = "2b327437-702e-4ab2-96fc-a98afa860b36";
Category category = this.client.ExecuteAsync(new GetByIdCommand<Category>(new Guid(id))).Result;

Note! Not all commands are available for all domain types.

The following line of code gets a category by ID using command builders:

string id = "2b327437-702e-4ab2-96fc-a98afa860b36";
Category category = await this.client
                                .Builder()
                                .Categories()
                                .GetById(id)
                                .ExecuteAsync();

Note! For more examples of command builders you can check integration tests in CustomersIntegrationTestsUsingCommandBuilder.

In case the injection of the client is not possible, the client should then be retrieved from the service provider:

IClient client = serviceProvider.GetService<IClient>();

Note! Some working examples can be found in the Examples folder of the solution or in the integration tests project.

Token Flow

There are three different token flows available as enum:

  • TokenFlow.ClientCredentials
  • TokenFlow.Password
  • TokenFlow.AnonymousSession

The initial flow is set at the start of the application but it can be changed while the application is running (for example, from anonymous to password). The flow is changed per client (in case there are multiple clients).

Changing the Flow

The token flow can be changed by using the ITokenFlowRegister. This interface is used for the mapping of the clients to their token flows. It can be injected in the same way as the IClient interface. The following code changes the token flow in applications that use a single client only:

private readonly ITokenFlowRegister tokenFlowRegister;
private readonly IClient client;
...
this.tokenFlowRegister.TokenFlow = TokenFlow.Password;

In case there are more clients, the following code sets the TokenFlow for a specific client, by using the ITokenFlowRegister through ITokenFlowMapper.

this.tokenFlowMapper.GetTokenFlowRegisterForClient(this.client.Name).TokenFlow = TokenFlow.Password;

Storing the Token

By default all token related data is saved in memory (and also retrieved from it). It is possible to change this. More information about this can be found in this document about overriding the defaults.

Overriding the Defaults

There are a few interfaces that can have custom implementations:

  • ITokenStoreManager
  • IUserCredentialsStoreManager
  • IAnonymousCredentialsStoreManager

Token

If you want to store token elsewhere other than in memory (for example, to persist it in case it has a refresh token), you should create a custom class that implements ITokenStoreManager. You can override the default implementation by adding the new one in the dependency injection setup:

services.UseCommercetools(this.configuration, "Client", TokenFlow.AnonymousSession);
services.AddSingleton<ITokenStoreManager, CustomTokenStoreManager>();

User credentials

The interface IUserCredentialsStoreManager should normally be implemented in a custom class. That is because the username and password are sometimes entered by users of applications and not stored in configuration and therefore it is not known by the SDK where they come from. For example, they can some from Request (in case they are entered in a form) or Session objects. You should override the default implementation by adding the new one in the dependency injection setup:

services.UseCommercetools(this.configuration, "Client", TokenFlow.Password);
services.AddHttpContextAccessor(); // custom class requires IHttpContextAccessor
services.AddSingleton<IUserCredentialsStoreManager, CustomUserCredentialsStoreManager>();

Note! This interfaces only requires the getter, since the SDK does not need to store the username and password anywhere.

Anonymous Session ID

The interface IAnonymousCredentialsStoreManager should normally be implemented in a custom class. You can override the default implementation by adding the new one in the dependency injection setup:

services.UseCommercetools(this.configuration, "Client", TokenFlow.AnonymousSession);
services.AddSingleton<IAnonymousCredentialsStoreManager, AnonymousCredentialsStoreManager>();

There are numerous commands and objects that accept predicates. In order to facilitate the developers, allow auto-completion and reduces the amount of errors, these predicates can be defined as lambda expressions. In case a predicate definition is not supported (that is, a NotSupportedException is thrown), a manually constructed string can be passed as well. The following example shows how a query predicate can be created using lambda expressions.

string key = category.Key;
QueryPredicate<Category> queryPredicate = new QueryPredicate<Category>(c => c.Key == key);
QueryCommand<Category> queryCommand = new QueryCommand<Category>();
queryCommand.SetWhere(queryPredicate);

In order to use values from objects directly use the valueOf extension method:

c => c.Key == category.Key.valueOf()

Setting the "where" query string parameter as a string can be done in the following way:

queryCommand.Where = "key = \"c14\"";

Note! For more examples of predicates using lambda expressions check this or take a look at the unit tests.

There are numerous extension methods created for domain specific operations. They can also be found by importing the commercetools.Sdk.Domain.Predicates namespace. The extension WithinCircle is one of them, for example:

c => c.GeoLocation.WithinCircle(13.37774, 52.51627, 1000)

LINQ

Experimental support for querying the API using LINQ is provided by the commercetools.Sdk.Client.Extensions

var query = from c in client.Query<Category>()
                where c.Key == "c14"
                select c;

foreach(Category c in query)
{
    var c = c.Key;
}

Accessing the command built using LINQ

var command = ((ClientQueryProvider<Category>) query.Provider).Command;

Custom Services

To support services endpoints the SDK does not currently support, like the Stores endpoint, create the domain models as follows:

    [Endpoint("stores")]
    public class Store : Resource<Store>
    {
        public string Key { get; set; }
        public LocalizedString Name { get; set; }
    }

    public class StoreDraft : IDraft<Store>
    {
        public string Key { get; set; }
        public LocalizedString Name { get; set; }
    }

Note that you have to add the Endpoint attribute above the main domain model. The Endpoint attribute then must inherit from the Resource class, and the draft model should implement the IDraft interface.

After this you can use the custom services in different commands as follows:

 var storeDraft = new StoreDraft
            {
                Name = new LocalizedString {{"en", $"Store1"}},
                Key = $"StoreKey"
            };
 var store = this.client.ExecuteAsync(new CreateCommand<Store>(storeDraft)).Result;

or like this:

 var store = this.client..ExecuteAsync(new GetByIdCommand<Store>(id)).Result;

You can also create custom Commands, HttpApiCommands, Request Builders and Additional Parameters builders for them, but don't forget to inject them into services container before calling services.UseCommercetools().