Giter VIP home page Giter VIP logo

commercetools / commercetools-dotnet-core-sdk Goto Github PK

View Code? Open in Web Editor NEW
10.0 53.0 5.0 16.89 MB

The e-commerce SDK from commercetools running on the .NET Core platform

Home Page: https://docs.commercetools.com/sdk/dotnet-sdk#net-core-sdk

License: Apache License 2.0

C# 99.49% HTML 0.21% PowerShell 0.18% Ruby 0.07% CSS 0.05% JavaScript 0.01%
commercetools sdk dotnet dotnet-core dotnet-standard commercetools-sdk commercetools-dotnet-core-sdk commercetools-dotnet-sdk audit-sdk

commercetools-dotnet-core-sdk's Introduction

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().

commercetools-dotnet-core-sdk's People

Contributors

ashishhk avatar barbara79 avatar btastic avatar celeste-horgan avatar dependabot[bot] avatar hajoeichler avatar jenschude avatar jherey avatar jhumber avatar katstojanovski avatar michelerezk avatar purquhart avatar renovate-bot avatar sshibani avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

commercetools-dotnet-core-sdk's Issues

Querying with datetimes does not have quotes / correct format

Given the following test case:

[Fact]
public void ExpressionWithDateTimeFilters()
{
    DateTime startDate = new DateTime(2020, 11, 21);
    DateTime endDate = startDate.AddMonths(1);

    Expression<Func<Order, bool>> expression = x => x.CreatedAt >= startDate && x.CreatedAt <= endDate;
    IQueryPredicateExpressionVisitor queryPredicateExpressionVisitor = this.linqFixture.GetService<IQueryPredicateExpressionVisitor>();
    string result = queryPredicateExpressionVisitor.Render(expression);
    Assert.Equal("createdAt >= \"2020-11-21\" and createdAt <= \"2020-12-21\"", result);
}

Produces the following outcome:
image

Query with Nested Custom Fields

Hi,

I have the following problem:

Task: get Customer, that has KupCustomerNumber nested in custom field Adresses->AdditionalAddressInfo (This is field contains custom Object).

Solution:
In normal situation for retrieving Customer we could use lambda expression like:
command.Where(s => s.Addresses.Any(m => m.AdditionalAddressInfo.Contains("CustomerNo:" + customer.KuPCustomerNo + """)));

Could you please say me if that possible to do with your current solution like:
command.Where("custom(fields(Addresses(AdditionalAddressInfo(CustomerNo ="" + customer.KuPCustomerNo + ""))");

Thanks!

OrderLine discountedPrice DTO mismatch

hi,

I'm trying to find the discountedPrice property in the SDK OrderLine DTO from the following json response of GET: /order/{id}

{
    "type": "Order",
....
    "lineItems": [
            "price": {
                "value": {
                    "type": "centPrecision",
                    "currencyCode": "USD",
                    "centAmount": 999,
                    "fractionDigits": 2
                },
                "id": "[REDACTED]"
            },
            "quantity": 1,
            "discountedPrice": {
                "value": {
                    "type": "centPrecision",
                    "currencyCode": "USD",
                    "centAmount": 899,
                    "fractionDigits": 2
                },
                "includedDiscounts": [
                    {
                        "discount": {
                            "typeId": "cart-discount",
                            "id": "[REDACTED]"
                        },
                        "discountedAmount": {
                            "type": "centPrecision",
                            "currencyCode": "USD",
                            "centAmount": 100,
                            "fractionDigits": 2
                        }
                    }
                ]
            },
....
   ]
....
}

While the SDK DTO only provide Price:

public class LineItem
  {
...
    public Price Price { get; set; }

    public TaxedItemPrice TaxedPrice { get; set; }

    public Money TotalPrice { get; set; }

    public List<DiscountedLineItemPriceForQuantity> DiscountedPricePerQuantity { get; set; }
...
  }

Price does include a Discount, however this is not set because it doesn't fit the JSON format.

Parsing complex attributes fails with error

This is related to #131 but now for products that use a complex attribute.

I'm getting:

  Name Value Type
$exception {"Accessed JArray values with invalid key value: "key". Int32 array index expected."} System.ArgumentException
Newtonsoft.Json.dll!Newtonsoft.Json.Linq.JArray.this[object].get(object key)	Unknown
commercetools.Sdk.Serialization.dll!commercetools.Sdk.Serialization.LocalizedEnumConverter<commercetools.Sdk.Domain.Products.Attributes.Attribute, commercetools.Sdk.Domain.Products.Attributes.LocalizedEnumValue>.CanConvert(Newtonsoft.Json.Linq.JToken property) Line 21	C#
commercetools.Sdk.Serialization.dll!commercetools.Sdk.Serialization.MapperTypeRetriever<commercetools.Sdk.Domain.Products.Attributes.Attribute>.GetTypeForTokenFromMapper(Newtonsoft.Json.Linq.JToken token) Line 26	C#
commercetools.Sdk.Serialization.dll!commercetools.Sdk.Serialization.MapperTypeRetriever<commercetools.Sdk.Domain.Products.Attributes.Attribute>.GetTypeForToken(Newtonsoft.Json.Linq.JToken token) Line 19	C#
commercetools.Sdk.Serialization.dll!commercetools.Sdk.Serialization.SetMapperTypeRetriever<commercetools.Sdk.Domain.Products.Attributes.Attribute>.GetTypeForToken(Newtonsoft.Json.Linq.JToken token) Line 22	C#

commercetools.Sdk.Serialization.dll!commercetools.Sdk.Serialization.AttributeConverter.ReadJson(Newtonsoft.Json.JsonReader reader, System.Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer) Line 37 C#

jsonObject inside AttributeConverter.ReadJson:

{
  "name": "printmethod",
  "value": [
    [
      {
        "value": "25 x 10",
        "name": "size"
      },
      {
        "value": "P1",
        "name": "id"
      },
      {
        "value": {
          "nl": "**"
        },
        "name": "name"
      },
      {
        "value": {
          "nl": "Wtte Dop"
        },
        "name": "displaylabel"
      }
    ],
    [
      {
        "value": "25cx",
        "name": "size"
      },
      {
        "value": "P1",
        "name": "id"
      },
      {
        "value": {
          "nl": "Bottle"
        },
        "name": "displaylabel"
      },
      {
        "value": {
          "nl": "pmm"
        },
        "name": "name"
      }
    ]
  ]
}

ShipmentState & PaymentState should be optional / nullable on Order

The API documentation on order specifies that the shipmentState & paymentState are optional attributes on an order.
But in the current version of the SDK the corresponding fields (type=enum) on the order model are not nullable.
This results in a default value of ShipmentState.Shipped and PaymentState.BalanceDue being set on orders where the shipmentState or paymentState is not set (yet).

Let's make these fields nullable.

Multiple Skip and Take won't have expected effect

Currently both extensions SearchProducts and Query won't handle multiple Skip and Take operations as expected from LinQ.

The following lines (regardless of how uncommon they might be) will result in wrong limits and offsets:

query.Take(3).Skip(1).Take(4);  // that should yield 2 items from offset 1
query.Skip(4).Take(3).Skip(1);  // that should yield 3 items from offset 5
query.Take(3).Skip(2);          // that should yield 1 item from offset 2

They result in the following outputs respectively:

?offset=1&limit=4
?offset=1&limit=3
?offset=2&limit=3

Given the above LinQ query though, the results should be the following respectively:

?offset=1&limit=2
?offset=5&limit=3
?offset=2&limit=1

The made PR fixes the mentioned issue by adding appropriate logic to the "Take" and "Skip" cases in ClientQueryProvider and the Offset and Limit extensions on SearchProductProjectionsCommand.

Getting a customer by email address

I am attempting to get a customer by email address from commercetools using a Query Predicate. The search is case-sensitive. For example if CT contains "[email protected]" and I pass "[email protected]" then I do not get a result. How to the search to be case-insensitive? See example below. Thank you.

QueryCommand command = new QueryCommand();
QueryPredicate predicate = new QueryPredicate(c => c.Email == email);
command.SetWhere(predicate);
PagedQueryResult response = await _client.ExecuteAsync(command);

Mobile app Demo for Dotnet CORE SDK

Mobile app Demo development for Dotnet CORE SDK

  • Implement Cart Feature, create cart, add product to cart, anonymous cart creation

Timebox : 1 week.

Unable to resolve service for type 'IClientConfiguration' while attempting to activate 'ApiExceptionFactory'

Hello,

just fiddling around with the this library and just found the first exception..

image

Just configuring it as per the readme file gives me the following error:

InvalidOperationException: Unable to resolve service for type 'commercetools.Sdk.HttpApi.IClientConfiguration' while attempting to activate 'commercetools.Sdk.HttpApi.ApiExceptionFactory'.

I had a look at the code and it seems that you never register the IClientConfiguration as a singleton.

Omit scopes from the get token query when they are empty

Hi 🙌

My understanding of the way the API works is that if no scopes are specified when requesting access tokens then the default set of scopes for that API client are returned.

Currently in the AnonymousSessionTokenProvider it adds the scopes query whether the scopes are present in the configuration or not.

Can we change this to be something more like this?

HttpRequestMessage request = new HttpRequestMessage();
            string requestUri = this.ClientConfiguration.AuthorizationBaseAddress + $"oauth/{this.ClientConfiguration.ProjectKey}/anonymous/token?grant_type=client_credentials";
            if(string.IsNullOrEmpty(this.ClientConfiguration.Scope))
            {
                requestUri += $"&scope={this.ClientConfiguration.Scope}";
            }
            if (!string.IsNullOrEmpty(this.anonymousCredentialsStoreManager.AnonymousId))
            {
                requestUri += $"&anonymous_id={this.anonymousCredentialsStoreManager.AnonymousId}";
            }

Thanks! 😄

Authentication fails on "scope" (vs "projectscope")

Hi,

I'm using CommerceTools authentication URL:
https://auth.sphere.io/oauth/token?grant_type=client_credentials&scope=ManageProject
With basic authentication.
However when using the SDK and Postman, I get a 400 back:

{
    "statusCode": 400,
    "message": "Malformed parameter: scope: Invalid scope.",
    "errors": [
        {
            "code": "invalid_scope",
            "message": "Malformed parameter: scope: Invalid scope."
        }
    ],
    "error": "invalid_scope",
    "error_description": "Malformed parameter: scope: Invalid scope."
}

When performing the query in Postman with projectscope instead of scope, I get back a valid response.

Empty attribute value array throws exception

I don't know if it should even happen for the returned attributes to contain of an empty array in the value object, but it did happen.

If the value object of an attribute is an empty array, the SetMapperTypeReceiver<T> can't determine an appropriate type for that array and returns null, which down the line will cause the AttributeConverter to throw a JsonSerializationExcpetion with no error message whatsoever. (Refer to L38-41 in the AttributeConverter).

To circumvent this case, I made a PR, which adds a special check if the given value is an empty JSON array and will return the type object. Since the array isn't rly deserialized much, since it doesn't contain elements, the object type shouldn't cause any side effects in those cases.

After that, the yielded data was fully deserializable by the rest of the pipeline and ready to use in our business logic.

Migrate Integration tests

Migrate Integration tests for the following API domain models:

  • Customers
  • CustomerGroups
  • Category
  • Payments
  • States
  • Projects
  • Errors
  • GraphQL
  • CustomObjects
  • Products
  • ProductProjections
  • ProductDiscounts
  • Inventory
  • Carts
  • Orders
  • ProductProjectionSearch

kick off auto-generate dotnet core sdk

Kick off Generated Dotnet Core SDK kick off.

Acceptance criteria:

  • Schedule a meeting for kick off of generated SDK for dotnet core.
  • List down what is required and plan the execution. here
  • Start with Dotnet Core SDK Generation

Remove legacy hostname

Description

To improve the developer experience and easen our support and training burden all existing references to *.sphere.io and .commercetools.co host names should be removed in favor of not defaulting to a specific region (a common complaint of US and AWS customers is that EU is defaulted everywhere) or, if needed for backwards compatibility, be replaced with the new *.europe-west1.gcp.commercetools.com notation.

Expected Behavior

full text search over the repository for ".sphere.io" and ".commercetools.co" should yield zero results

Context

https://docs.commercetools.com/release-notes#releases-2020-02-03-aws-and-new-hostnames

Plans on ditching Newtonsoft.Json in favour of new System.Text.Json serializer

Hello,

are there any plans of ditching Newtonsoft.Json in favour of System.Text.Json serializer? We are currently rewriting our business applications to use .NET Core 3.0. When returning commercetools responses we have problems when serializing commercetools objects, as they are usually JObjects and so on. System.Text.Json has problems with that. When everyone uses System.Text.Json this would not be a problem.

So.. are there any plans on that?

Where-In queries with enum-arrays will generate bad requests

Given the following test cases:

[Fact]
public void ExpressionEnumWhereInList()
{
    List<OrderState> orderStates = new[] { OrderState.Open, OrderState.Complete, OrderState.Confirmed }.ToList();
    Expression<Func<Order, bool>> expression = x => x.OrderState.In(orderStates);
    IQueryPredicateExpressionVisitor queryPredicateExpressionVisitor = this.linqFixture.GetService<IQueryPredicateExpressionVisitor>();
    string result = queryPredicateExpressionVisitor.Render(expression);
    Assert.Equal("orderState in (\"Open\", \"Complete\", \"Confirmed\")", result);
}

[Fact]
public void ExpressionEnumWhereInListValueOf()
{
    List<OrderState> orderStates = new[] { OrderState.Open.valueOf(), OrderState.Complete.valueOf(), OrderState.Confirmed.valueOf() }.ToList();
    Expression<Func<Order, bool>> expression = x => x.OrderState.In(orderStates);
    IQueryPredicateExpressionVisitor queryPredicateExpressionVisitor = this.linqFixture.GetService<IQueryPredicateExpressionVisitor>();
    string result = queryPredicateExpressionVisitor.Render(expression);
    Assert.Equal("orderState in (\"Open\", \"Complete\", \"Confirmed\")", result);
}

[Fact]
public void ExpressionEnumWhereInArray()
{
    OrderState[] orderStates = new[] { OrderState.Open, OrderState.Complete, OrderState.Confirmed };
    Expression<Func<Order, bool>> expression = x => x.OrderState.In(orderStates);
    IQueryPredicateExpressionVisitor queryPredicateExpressionVisitor = this.linqFixture.GetService<IQueryPredicateExpressionVisitor>();
    string result = queryPredicateExpressionVisitor.Render(expression);
    Assert.Equal("orderState in (\"Open\", \"Complete\", \"Confirmed\")", result);
}

[Fact]
public void ExpressionEnumWhereInArrayValueOf()
{
    OrderState[] orderStates = new[] { OrderState.Open.valueOf(), OrderState.Complete.valueOf(), OrderState.Confirmed.valueOf() };
    Expression<Func<Order, bool>> expression = x => x.OrderState.In(orderStates);
    IQueryPredicateExpressionVisitor queryPredicateExpressionVisitor = this.linqFixture.GetService<IQueryPredicateExpressionVisitor>();
    string result = queryPredicateExpressionVisitor.Render(expression);
    Assert.Equal("orderState in (\"Open\", \"Complete\", \"Confirmed\")", result);
}

The tests will all fail with something like this:

image

I already provided a fix, I don't know if this is the right way, but the tests won't fail, and our internal application tests also like this fix.

Setup multiple clients with different configurations

Hello, we are currently migrating to the new library. We are using the SDK to create CtpClients (or Client in the old version) different types of clients using different projects.

This is used in a multi-tenancy setup where different customers have different needs but we only have one backend to handle the requests.

Right now I don't see a way to setup multiple clients with different configurations (project key, client secrets and so on).

Can you point me in a direction what I need to do?

Exception getting cart discount

When there is a Cart Discount in commercetools with "Multibuy line items" selected, I get an exception retrieving cart discounts

Sample Code

QueryCommand command = new QueryCommand();
PagedQueryResult response = await client.ExecuteAsync(command);

Exception

The exception occurs in SDK in ReadJson function in JsonConverterDecoratorTypeRetrieverBase.cs file.

System.AggregateException: One or more errors occurred. (Couldn't get type to deserialize property type)
---> Newtonsoft.Json.JsonSerializationException: Couldn't get type to deserialize property type
at commercetools.Sdk.Serialization.JsonConverterDecoratorTypeRetrieverBase`1.ReadJson(JsonReader reader, Type objectType, Object existingValue, JsonSerializer serializer)

CT2
CT1
JsonConverterDecoratorTypeRetrieverBase

Query'ing objects with Where predicate doesn't work

Serializing the following Where predicates don't produce a valid where query.

_client.Query<Product>().Where(x => x.MasterData.Published);
_client.Query<Product>().Where(x => !x.MasterData.Published);

They respectively create the following where query:

where=masterdata(published)
where=not(masterdata(published))

Both where queries are invalid and let the HTTP API return an error message of a malformed where parameter.
While it would be a nice addition to the HTTP API to support those syntax too, the current code would do best to make up for that slight too.

I created a PR with changes, serializing the above Where predicates into the following where queries respectively, which yields the correct data from the API:

where=masterdata(published = true)
where=masterdata(published = false)

attribute type fails to deserialize

When the json parser encounters:

"type": {
            "name": "set",
            "elementType": {
              "name": "nested",
              "typeReference": {
                "typeId": "product-type",
                "id": "5596962f-3969-4d16-838b-df0b598b4971"
              }
            }
          },

It fails on "name": "nested" with this error:

▶ | $exception | {"Couldn't get type to deserialize property name"} |

What can I do to make this work?

Callstack:

>	commercetools.Sdk.Serialization.dll!commercetools.Sdk.Serialization.JsonConverterDecoratorTypeRetrieverBase<commercetools.Sdk.Domain.AttributeType>.ReadJson(Newtonsoft.Json.JsonReader reader, System.Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer) Line 45	C#
 	Newtonsoft.Json.dll!Newtonsoft.Json.Serialization.JsonSerializerInternalReader.DeserializeConvertable(Newtonsoft.Json.JsonConverter converter, Newtonsoft.Json.JsonReader reader, System.Type objectType, object existingValue)	Unknown
 	Newtonsoft.Json.dll!Newtonsoft.Json.Serialization.JsonSerializerInternalReader.SetPropertyValue(Newtonsoft.Json.Serialization.JsonProperty property, Newtonsoft.Json.JsonConverter propertyConverter, Newtonsoft.Json.Serialization.JsonContainerContract containerContract, Newtonsoft.Json.Serialization.JsonProperty containerProperty, Newtonsoft.Json.JsonReader reader, object target)	Unknown
 	Newtonsoft.Json.dll!Newtonsoft.Json.Serialization.JsonSerializerInternalReader.PopulateObject(object newObject, Newtonsoft.Json.JsonReader reader, Newtonsoft.Json.Serialization.JsonObjectContract contract, Newtonsoft.Json.Serialization.JsonProperty member, string id)	Unknown
 	Newtonsoft.Json.dll!Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObject(Newtonsoft.Json.JsonReader reader, System.Type objectType, Newtonsoft.Json.Serialization.JsonContract contract, Newtonsoft.Json.Serialization.JsonProperty member, Newtonsoft.Json.Serialization.JsonContainerContract containerContract, Newtonsoft.Json.Serialization.JsonProperty containerMember, object existingValue)	Unknown
 	Newtonsoft.Json.dll!Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal(Newtonsoft.Json.JsonReader reader, System.Type objectType, Newtonsoft.Json.Serialization.JsonContract contract, Newtonsoft.Json.Serialization.JsonProperty member, Newtonsoft.Json.Serialization.JsonContainerContract containerContract, Newtonsoft.Json.Serialization.JsonProperty containerMember, object existingValue)	Unknown
 	Newtonsoft.Json.dll!Newtonsoft.Json.Serialization.JsonSerializerInternalReader.Deserialize(Newtonsoft.Json.JsonReader reader, System.Type objectType, bool checkAdditionalContent)	Unknown
 	Newtonsoft.Json.dll!Newtonsoft.Json.Serialization.JsonSerializerProxy.DeserializeInternal(Newtonsoft.Json.JsonReader reader, System.Type objectType)	Unknown
 	Newtonsoft.Json.dll!Newtonsoft.Json.JsonSerializer.Deserialize(Newtonsoft.Json.JsonReader reader, System.Type objectType)	Unknown
 	Newtonsoft.Json.dll!Newtonsoft.Json.Linq.JToken.ToObject(System.Type objectType, Newtonsoft.Json.JsonSerializer jsonSerializer)	Unknown
 	commercetools.Sdk.Serialization.dll!commercetools.Sdk.Serialization.JsonConverterDecoratorTypeRetrieverBase<commercetools.Sdk.Domain.AttributeType>.ReadJson(Newtonsoft.Json.JsonReader reader, System.Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer) Line 45	C#
 	Newtonsoft.Json.dll!Newtonsoft.Json.Serialization.JsonSerializerInternalReader.DeserializeConvertable(Newtonsoft.Json.JsonConverter converter, Newtonsoft.Json.JsonReader reader, System.Type objectType, object existingValue)	Unknown
 	Newtonsoft.Json.dll!Newtonsoft.Json.Serialization.JsonSerializerInternalReader.SetPropertyValue(Newtonsoft.Json.Serialization.JsonProperty property, Newtonsoft.Json.JsonConverter propertyConverter, Newtonsoft.Json.Serialization.JsonContainerContract containerContract, Newtonsoft.Json.Serialization.JsonProperty containerProperty, Newtonsoft.Json.JsonReader reader, object target)	Unknown
 	Newtonsoft.Json.dll!Newtonsoft.Json.Serialization.JsonSerializerInternalReader.PopulateObject(object newObject, Newtonsoft.Json.JsonReader reader, Newtonsoft.Json.Serialization.JsonObjectContract contract, Newtonsoft.Json.Serialization.JsonProperty member, string id)	Unknown
 	Newtonsoft.Json.dll!Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObject(Newtonsoft.Json.JsonReader reader, System.Type objectType, Newtonsoft.Json.Serialization.JsonContract contract, Newtonsoft.Json.Serialization.JsonProperty member, Newtonsoft.Json.Serialization.JsonContainerContract containerContract, Newtonsoft.Json.Serialization.JsonProperty containerMember, object existingValue)	Unknown
 	Newtonsoft.Json.dll!Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal(Newtonsoft.Json.JsonReader reader, System.Type objectType, Newtonsoft.Json.Serialization.JsonContract contract, Newtonsoft.Json.Serialization.JsonProperty member, Newtonsoft.Json.Serialization.JsonContainerContract containerContract, Newtonsoft.Json.Serialization.JsonProperty containerMember, object existingValue)	Unknown
 	Newtonsoft.Json.dll!Newtonsoft.Json.Serialization.JsonSerializerInternalReader.PopulateList(System.Collections.IList list, Newtonsoft.Json.JsonReader reader, Newtonsoft.Json.Serialization.JsonArrayContract contract, Newtonsoft.Json.Serialization.JsonProperty containerProperty, string id)	Unknown
 	Newtonsoft.Json.dll!Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateList(Newtonsoft.Json.JsonReader reader, System.Type objectType, Newtonsoft.Json.Serialization.JsonContract contract, Newtonsoft.Json.Serialization.JsonProperty member, object existingValue, string id)	Unknown
 	Newtonsoft.Json.dll!Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal(Newtonsoft.Json.JsonReader reader, System.Type objectType, Newtonsoft.Json.Serialization.JsonContract contract, Newtonsoft.Json.Serialization.JsonProperty member, Newtonsoft.Json.Serialization.JsonContainerContract containerContract, Newtonsoft.Json.Serialization.JsonProperty containerMember, object existingValue)	Unknown
 	Newtonsoft.Json.dll!Newtonsoft.Json.Serialization.JsonSerializerInternalReader.SetPropertyValue(Newtonsoft.Json.Serialization.JsonProperty property, Newtonsoft.Json.JsonConverter propertyConverter, Newtonsoft.Json.Serialization.JsonContainerContract containerContract, Newtonsoft.Json.Serialization.JsonProperty containerProperty, Newtonsoft.Json.JsonReader reader, object target)	Unknown
 	Newtonsoft.Json.dll!Newtonsoft.Json.Serialization.JsonSerializerInternalReader.PopulateObject(object newObject, Newtonsoft.Json.JsonReader reader, Newtonsoft.Json.Serialization.JsonObjectContract contract, Newtonsoft.Json.Serialization.JsonProperty member, string id)	Unknown
 	Newtonsoft.Json.dll!Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObject(Newtonsoft.Json.JsonReader reader, System.Type objectType, Newtonsoft.Json.Serialization.JsonContract contract, Newtonsoft.Json.Serialization.JsonProperty member, Newtonsoft.Json.Serialization.JsonContainerContract containerContract, Newtonsoft.Json.Serialization.JsonProperty containerMember, object existingValue)	Unknown
 	Newtonsoft.Json.dll!Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal(Newtonsoft.Json.JsonReader reader, System.Type objectType, Newtonsoft.Json.Serialization.JsonContract contract, Newtonsoft.Json.Serialization.JsonProperty member, Newtonsoft.Json.Serialization.JsonContainerContract containerContract, Newtonsoft.Json.Serialization.JsonProperty containerMember, object existingValue)	Unknown
 	Newtonsoft.Json.dll!Newtonsoft.Json.Serialization.JsonSerializerInternalReader.PopulateList(System.Collections.IList list, Newtonsoft.Json.JsonReader reader, Newtonsoft.Json.Serialization.JsonArrayContract contract, Newtonsoft.Json.Serialization.JsonProperty containerProperty, string id)	Unknown
 	Newtonsoft.Json.dll!Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateList(Newtonsoft.Json.JsonReader reader, System.Type objectType, Newtonsoft.Json.Serialization.JsonContract contract, Newtonsoft.Json.Serialization.JsonProperty member, object existingValue, string id)	Unknown
 	Newtonsoft.Json.dll!Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal(Newtonsoft.Json.JsonReader reader, System.Type objectType, Newtonsoft.Json.Serialization.JsonContract contract, Newtonsoft.Json.Serialization.JsonProperty member, Newtonsoft.Json.Serialization.JsonContainerContract containerContract, Newtonsoft.Json.Serialization.JsonProperty containerMember, object existingValue)	Unknown
 	Newtonsoft.Json.dll!Newtonsoft.Json.Serialization.JsonSerializerInternalReader.SetPropertyValue(Newtonsoft.Json.Serialization.JsonProperty property, Newtonsoft.Json.JsonConverter propertyConverter, Newtonsoft.Json.Serialization.JsonContainerContract containerContract, Newtonsoft.Json.Serialization.JsonProperty containerProperty, Newtonsoft.Json.JsonReader reader, object target)	Unknown
 	Newtonsoft.Json.dll!Newtonsoft.Json.Serialization.JsonSerializerInternalReader.PopulateObject(object newObject, Newtonsoft.Json.JsonReader reader, Newtonsoft.Json.Serialization.JsonObjectContract contract, Newtonsoft.Json.Serialization.JsonProperty member, string id)	Unknown
 	Newtonsoft.Json.dll!Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObject(Newtonsoft.Json.JsonReader reader, System.Type objectType, Newtonsoft.Json.Serialization.JsonContract contract, Newtonsoft.Json.Serialization.JsonProperty member, Newtonsoft.Json.Serialization.JsonContainerContract containerContract, Newtonsoft.Json.Serialization.JsonProperty containerMember, object existingValue)	Unknown
 	Newtonsoft.Json.dll!Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal(Newtonsoft.Json.JsonReader reader, System.Type objectType, Newtonsoft.Json.Serialization.JsonContract contract, Newtonsoft.Json.Serialization.JsonProperty member, Newtonsoft.Json.Serialization.JsonContainerContract containerContract, Newtonsoft.Json.Serialization.JsonProperty containerMember, object existingValue)	Unknown
 	Newtonsoft.Json.dll!Newtonsoft.Json.Serialization.JsonSerializerInternalReader.Deserialize(Newtonsoft.Json.JsonReader reader, System.Type objectType, bool checkAdditionalContent)	Unknown
 	Newtonsoft.Json.dll!Newtonsoft.Json.JsonSerializer.DeserializeInternal(Newtonsoft.Json.JsonReader reader, System.Type objectType)	Unknown
 	Newtonsoft.Json.dll!Newtonsoft.Json.JsonSerializer.Deserialize(Newtonsoft.Json.JsonReader reader, System.Type objectType)	Unknown
 	Newtonsoft.Json.dll!Newtonsoft.Json.JsonConvert.DeserializeObject(string value, System.Type type, Newtonsoft.Json.JsonSerializerSettings settings)	Unknown
 	Newtonsoft.Json.dll!Newtonsoft.Json.JsonConvert.DeserializeObject<commercetools.Sdk.Domain.PagedQueryResult<commercetools.Sdk.Domain.ProductType>>(string value, Newtonsoft.Json.JsonSerializerSettings settings)	Unknown
 	commercetools.Sdk.Serialization.dll!commercetools.Sdk.Serialization.SerializerService.Deserialize<commercetools.Sdk.Domain.PagedQueryResult<commercetools.Sdk.Domain.ProductType>>(string input) Line 21	C#
 	commercetools.Sdk.HttpApi.dll!commercetools.Sdk.HttpApi.CtpClient.SendRequest<commercetools.Sdk.Domain.PagedQueryResult<commercetools.Sdk.Domain.ProductType>>(System.Net.Http.HttpRequestMessage requestMessage) Line 58	C#

Full attribute info:

{
  "limit": 500,
  "offset": 0,
  "count": 2,
  "results": [
    {
      "id": "5596962f-3969-4d16-838b-df0b598b4971",
      "version": 1,
      "createdAt": "2020-06-11T12:11:24.201Z",
      "lastModifiedAt": "2020-06-11T12:11:24.201Z",
      "name": "name",
      "description": "",
      "classifier": "Complex",
      "attributes": [
        {
          "name": "id",
          "label": {
            "de": "id",
            "nl": "id"
          },
          "inputTip": {
            "de": "",
            "nl": ""
          },
          "isRequired": true,
          "type": { "name": "text" },
          "attributeConstraint": "SameForAll",
          "isSearchable": true,
          "inputHint": "SingleLine",
          "displayGroup": "Other"
        },
        {
          "name": "name",
          "label": {
            "de": "",
            "nl": "naam"
          },
          "inputTip": {
            "de": "",
            "nl": ""
          },
          "isRequired": true,
          "type": { "name": "ltext" },
          "attributeConstraint": "SameForAll",
          "isSearchable": false,
          "inputHint": "SingleLine",
          "displayGroup": "Other"
        },
        {
          "name": "size",
          "label": {
            "de": "",
            "nl": "grootte"
          },
          "inputTip": {
            "de": "",
            "nl": ""
          },
          "isRequired": false,
          "type": { "name": "text" },
          "attributeConstraint": "SameForAll",
          "isSearchable": false,
          "inputHint": "SingleLine",
          "displayGroup": "Other"
        },
        {
          "name": "displaylabel",
          "label": {
            "de": "",
            "nl": "Label"
          },
          "inputTip": {
            "de": "",
            "nl": ""
          },
          "isRequired": false,
          "type": { "name": "ltext" },
          "attributeConstraint": "SameForAll",
          "isSearchable": false,
          "inputHint": "SingleLine",
          "displayGroup": "Other"
        }
      ],
      "key": "pp"
    },
    {
      "id": "588bac60-38b0-40dc-ab52-db152f6b3dcd",
      "version": 3,
      "createdAt": "2020-06-11T12:11:24.215Z",
      "lastModifiedAt": "2020-06-11T12:13:43.168Z",
      "name": "productdata",
      "description": "",
      "classifier": "Complex",
      "attributes": [
        {
          "name": "primary_cat",
          "label": {
            "de": "",
            "nl": "Hoofd category"
          },
          "inputTip": {
            "de": "",
            "nl": ""
          },
          "isRequired": false,
          "type": {
            "name": "reference",
            "referenceTypeId": "category"
          },
          "attributeConstraint": "SameForAll",
          "isSearchable": true,
          "inputHint": "SingleLine",
          "displayGroup": "Other"
        },
        {
          "name": "printMethods",
          "label": {
            "nl": "Print methode",
            "en": "print method"
          },
          "isRequired": false,
          "type": {
            "name": "set",
            "elementType": {
              "name": "nested",
              "typeReference": {
                "typeId": "product-type",
                "id": "5596962f-3969-4d16-838b-df0b598b4971"
              }
            }
          },
          "attributeConstraint": "SameForAll",
          "isSearchable": true,
          "inputHint": "SingleLine",
          "displayGroup": "Other"
        }
      ],
      "key": "productdata"
    }
  ]
}

Write Integration tests for the remaining API domain models

Write Integration tests for the following API domain models:

  • Product Discounts
  • Cart Discounts
  • Product suggestions
  • Reviews
  • Tax category
  • Discound codes
  • Shopping list
  • Shipping methods
  • Shipping zones
  • Order imports
  • Product types
  • Types
  • Messages
  • Subscriptions
  • Stores
  • Channels
  • API Extensions

Using TokenProvider.Token from within a test context causes deadlock

Hi 👋 Just a quick one...

I'm trying to get an anonymous token to pass back to the user from within an API. I am setting everything up as expected in the readme and realised that I can actually resolve the collection of TokenProviders directly in order to get an anonymous token.

My source code...

public AnonymousTokenProvider(IEnumerable<ITokenProvider> tokenProviders)
{
    _tokenProvider = tokenProviders.First(tp => tp.TokenFlow == TokenFlow.AnonymousSession);
}

public Task<Token> GetToken()
{
    var token = _tokenProvider.Token;
    return Task.FromResult(token);
}

I have an integration test that invokes this class which works fine when it runs on its own but fails when it runs with other tests in parallel. I think we're hitting an async/await deadlocking issue here because _tokenProvider.Token is invoking an async call and calling .Result on the task.

I'm fairly certain this is the cause of my issue because when I inline the code from this repo but make it async all the way it solves my locking issue.

Is there a reason why this can't be replaced with an async method call instead? Would anyone mind if I submit a PR to fix this?

Thanks! 👍

Token Refresh

After the token is expired (2dys) the token is not refreshed by the SDK. restarting the app resolve the issue. see below exception details:

2019-03-25 08:32:05.786 +00:00 [Information] Microsoft.AspNetCore.Hosting.Internal.WebHost: Request starting HTTP/1.1 GET http://sl-apifacade-rnd-api.azurewebsites.net/api/Product/GetOutboundRoute?arrivalCountry=denmark  
2019-03-25 08:32:05.865 +00:00 [Information] Microsoft.AspNetCore.Routing.EndpointMiddleware: Executing endpoint ' .APIFacade.Controllers.ProductController.GetOutboundRouteAsync ( .APIFacade)'
2019-03-25 08:32:05.905 +00:00 [Information] Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker: Route matched with {action = "GetOutboundRouteAsync", controller = "Product"}. Executing action  .APIFacade.Controllers.ProductController.GetOutboundRouteAsync ( .APIFacade)
2019-03-25 08:32:05.932 +00:00 [Information] Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker: Executing action method  .APIFacade.Controllers.ProductController.GetOutboundRouteAsync ( .APIFacade) with arguments (denmark) - Validation state: Valid
2019-03-25 08:32:05.991 +00:00 [Information] System.Net.Http.HttpClient.APIFacadeCommerceToolsClient.LogicalHandler: Start processing HTTP request POST https://api.sphere.io/dc-poc/product-projections/search
2019-03-25 08:32:05.991 +00:00 [Information] LoggerHandler: https://api.sphere.io/dc-poc/product-projections/search
2019-03-25 08:32:05.992 +00:00 [Information] LoggerHandler: POST
2019-03-25 08:32:05.992 +00:00 [Information] LoggerHandler: dc-poc/5a765025-b32d-4d1c-8860-85e8998529bc
2019-03-25 08:32:05.992 +00:00 [Information] System.Net.Http.HttpClient.APIFacadeCommerceToolsClient.ClientHandler: Sending HTTP request POST https://api.sphere.io/dc-poc/product-projections/search
2019-03-25 08:32:06.040 +00:00 [Information] System.Net.Http.HttpClient.APIFacadeCommerceToolsClient.ClientHandler: Received HTTP response after 37.2564ms - Unauthorized
2019-03-25 08:32:06.080 +00:00 [Information] Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker: Executed action  .APIFacade.Controllers.ProductController.GetOutboundRouteAsync ( .APIFacade) in 164.1591ms
2019-03-25 08:32:06.081 +00:00 [Information] Microsoft.AspNetCore.Routing.EndpointMiddleware: Executed endpoint ' .APIFacade.Controllers.ProductController.GetOutboundRouteAsync ( .APIFacade)'
2019-03-25 08:32:06.177 +00:00 [Error] Microsoft.AspNetCore.Server.Kestrel: Connection id "0HLLES49IDD5K", Request id "0HLLES49IDD5K:00000004": An unhandled exception was thrown by the application.
commercetools.Sdk.HttpApi.Domain.Exceptions.InvalidTokenException: Request Summary: POST https://api.sphere.io/dc-poc/product-projections/search failed Unauthorized with X-Correlation-ID 'dc-poc/5a765025-b32d-4d1c-8860-85e8998529bc' on 2019-03-25T08:32:06
Response: {"statusCode":401,"message":"invalid_token","errors":[{"code":"invalid_token","message":"invalid_token"}],"error":"invalid_token"}
project: dc-poc
   at commercetools.Sdk.HttpApi.DelegatingHandlers.ErrorHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) in C:\Work\Bridging\ \CommerceToolsSDK\dotnet-core\commercetools.Sdk.HttpApi\DelegatingHandlers\ErrorHandler.cs:line 27
   at commercetools.Sdk.HttpApi.DelegatingHandlers.LoggerHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) in C:\Work\Bridging\ \CommerceToolsSDK\dotnet-core\commercetools.Sdk.HttpApi\DelegatingHandlers\LoggerHandler.cs:line 29
   at commercetools.Sdk.HttpApi.DelegatingHandlers.CorrelationIdHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) in C:\Work\Bridging\ \CommerceToolsSDK\dotnet-core\commercetools.Sdk.HttpApi\DelegatingHandlers\CorrelationIdHandler.cs:line 20
   at commercetools.Sdk.HttpApi.DelegatingHandlers.AuthorizationHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) in C:\Work\Bridging\ \CommerceToolsSDK\dotnet-core\commercetools.Sdk.HttpApi\DelegatingHandlers\AuthorizationHandler.cs:line 25
   at Microsoft.Extensions.Http.Logging.LoggingScopeHttpMessageHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
   at System.Net.Http.HttpClient.FinishSendAsyncBuffered(Task`1 sendTask, HttpRequestMessage request, CancellationTokenSource cts, Boolean disposeCts)
   at commercetools.Sdk.HttpApi.Client.SendRequest[T](HttpRequestMessage requestMessage) in C:\Work\Bridging\ \CommerceToolsSDK\dotnet-core\commercetools.Sdk.HttpApi\Client.cs:line 48
   at commercetools.Sdk.HttpApi.Client.ExecuteAsync[T](Command`1 command)
   at  .APIFacade.Controllers.ProductController.GetOutboundRouteAsync(String arrivalCountry) in C:\Work\Bridging\ \ .APIFacade\Controllers\ProductController.cs:line 35
   at lambda_method(Closure , Object )
   at Microsoft.Extensions.Internal.ObjectMethodExecutorAwaitable.Awaiter.GetResult()
   at Microsoft.AspNetCore.Mvc.Internal.ActionMethodExecutor.AwaitableObjectResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
   at System.Threading.Tasks.ValueTask`1.get_Result()
   at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeActionMethodAsync()
   at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeNextActionFilterAsync()
   at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Rethrow(ActionExecutedContext context)
   at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeInnerFilterAsync()
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeNextResourceFilter()
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Rethrow(ResourceExecutedContext context)
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeFilterPipelineAsync()
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeAsync()
   at Microsoft.AspNetCore.Routing.EndpointMiddleware.Invoke(HttpContext httpContext)
   at Microsoft.AspNetCore.Routing.EndpointRoutingMiddleware.Invoke(HttpContext httpContext)
   at Microsoft.AspNetCore.Server.IISIntegration.IISMiddleware.Invoke(HttpContext httpContext)
   at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol.ProcessRequests[TContext](IHttpApplication`1 application)
2019-03-25 08:32:06.217 +00:00 [Information] Microsoft.AspNetCore.Hosting.Internal.WebHost: Request finished in 480.9585ms 500 

Where clause with enum field creates a bad request

I created a simple test case for this:

public void ExpressionEvaluationWithEnumsFails()
{
    OrderState orderState = OrderState.Open;
    Expression<Func<Order, bool>> expression = x => x.OrderState == orderState;
    IQueryPredicateExpressionVisitor queryPredicateExpressionVisitor = this.linqFixture.GetService<IQueryPredicateExpressionVisitor>();
    string result = queryPredicateExpressionVisitor.Render(expression);
    Assert.Equal("orderState = \"open\"", result);
}

Test result:
image

Fiddler shows the same:
image

commercetools documentation on how to query:
https://docs.commercetools.com/http-api-query-predicates#query-predicate-examples

Using valueOf() does not work this time around.

Create a change log

What?

Create a change log

Why?

So that users can see what was changed and can find breaking changes

Acceptance criteria:

  • User can see see changes

Query'ing without any Take or Skip restriction only loads first page

Using client.Query<T>() one will only receive the default 20 items from the first page. If we want to get more than that or all items of the given T, then we are bound to Skip and Take ourselves in a loop.
This approach however has one major flaw. Before the first execution of the query, you basically have no idea how many items you have in total, nor does the Query<T>() extension return the total value if Queryparameter withTotal=true.

A solution to that would be a client.QueryAll<T>() extension in addition to an appropriate enumerator for that case.
The enumerator will keep track of the current page items and retrieves new ones, if the page is exhausted.

Example:

var productQuery = client.QueryAll<Product>().Where(x => x.MasterData.Published);
foreach(var product in productQuery)
{
    ;  // Do things with the product
}

As you can see, the syntax would be nearly the same, while providing all items of a requested type on demand. So it will also not flood the machines memory with all the requested items at once.

Query Case Sensitive

Hi,
I have a question:
Is it possible to make a query case insensitive?
command.Where("custom(fields(lastName="" + exampleName+ ""))");
And get result for both variants: exampleName and ExampleName

MaxApplications and MaxApplicationsPerCustomer initialized to 0 by default

When creating a DiscountCodeDraft class and not setting MaxApplications or MaxApplicationsPerCustomer properties, these values (which are optional according to documentation) are sent through the API set as 0.
If you then try to apply the given code, the code cannot be applied due to MaxApplicationReached error.

Sorting by ID does not work when querying Messages.Message

Hi,

I'm writing a piece of integration software where we need to pull all changes on Customers and Orders. We need to use the ID into a compound sort statement within the QueryCommand to make the result deterministic, for example (CreatedAt, Name, ID). However I found out that sorting on ID does not work when used as primary sorting key.
I am using a master build from 4-10-2019
thanks,
pepijn

Where clause with "in" and arrays and lists creates a bad request

Given the following test cases:

[Fact]
public void ExpressionWhereInString()
{
    string[] orderNumbers = { "5", "10", "15" };
    Expression<Func<Order, bool>> expression = x => x.OrderNumber.In(orderNumbers);
    IQueryPredicateExpressionVisitor queryPredicateExpressionVisitor = this.linqFixture.GetService<IQueryPredicateExpressionVisitor>();
    string result = queryPredicateExpressionVisitor.Render(expression);
    Assert.Equal("orderNumber in (\"5\", \"10\", \"15\")", result);
}

Outputs the following:
image

[Fact]
public void ExpressionWhereInStringList()
{
    List<string> orderNumbers = new List<string>(){ "5", "10", "15" };
    Expression<Func<Order, bool>> expression = x => x.OrderNumber.In(orderNumbers.ToArray());
    IQueryPredicateExpressionVisitor queryPredicateExpressionVisitor = this.linqFixture.GetService<IQueryPredicateExpressionVisitor>();
    string result = queryPredicateExpressionVisitor.Render(expression);
    Assert.Equal("orderNumber in (\"5\", \"10\", \"15\")", result);
}

Outputs the following:
image

From what I analyzed, it seems that the ConstantPredicateVisitorConverter is not the right job for arrays/collections. I tried implementing a InMethodPredicateVisitorConverter which seems to do a similar thing when filtering, but there are some bits that I don't know :/

If you need anything, shoot me an email @jenschude

Order Get Custom Fields

Hi,

I am trying to use your package in order to work wirh Orders from Merchant Center.

In my VS code I try to work with custom Fields like "KupCustomerNumber" or "AbAccountBusinessPartnerId".
Code Example:
var resultQuery = await _client.ExecuteAsync(command.Where(c => (string)c.Custom.Fields["KupCustomerNumber"]== "xxxxxx|")):

Problem: while executing the above code line, the field "KupCustomerNumber" will be converted in "kupCustomerNumber" (in lower case)! Like that;

custom(fields(kupCustomerNumber="xxxxxx"))

Of course, the returned result is null because of name of custom field.

Custom services Documentation

Acceptance criteria

  • documentation for customization is in README
  • We as a team agree that the documentation is sufficient

Question about Creating a Customer

The code listed below creates customer in commercetools but the customer object is not initialized. This is because the Create Customer method in commertools returns a CustomerSignInResult object and not a Customer object. How do I get the CustomerSignInResult response using the SDK?

CreateCommand command = new CreateCommand(customerDraft);
Customer customer = client.ExecuteAsync(command).Result;

Missing InventoryEntryQuantitySet Message

It appears SDK is missing class for InventoryEntryQuantitySet message. Per commercetools documentation:

InventoryEntryQuantitySet Message

This message is the result of the update InventoryEntry with AddQuantity, RemoveQuantity or ChangeQuantity update action. In addition to the common Message fields, the InventoryEntryQuantitySet message contains the following fields:

type - String - "InventoryEntryQuantitySet"
oldQuantityOnStock - Number
newQuantityOnStock - Number
oldAvailableQuantity - Number
newAvailableQuantity - Number

JsonSerializationException is thrown when no icu-libs are installed on Docker image

I just switched a service from using commercetools.NET.SDK to commercetools.Sdk.All.
All is working fine locally.
My service is shipped using dotnet:2.2-aspnetcore-runtime-alpine Docker image.
When running the service inside the container it throws a JsonSerializationException without any errormessage:

Newtonsoft.Json.JsonSerializationException: Exception of type 'Newtonsoft.Json.JsonSerializationException' was thrown.
   at commercetools.Sdk.Serialization.AttributeConverter.ReadJson(JsonReader reader, Type objectType, Object existingValue, JsonSerializer serializer)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.DeserializeConvertable(JsonConverter converter, JsonReader reader, Type objectType, Object existingValue)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.PopulateList(IList list, JsonReader reader, JsonArrayContract contract, JsonProperty containerProperty, String id)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateList(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, Object existingValue, String id)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.SetPropertyValue(JsonProperty property, JsonConverter propertyConverter, JsonContainerContract containerContract, JsonProperty containerProperty, JsonReader reader, Object target)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.PopulateObject(Object newObject, JsonReader reader, JsonObjectContract contract, JsonProperty member, String id)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObject(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.SetPropertyValue(JsonProperty property, JsonConverter propertyConverter, JsonContainerContract containerContract, JsonProperty containerProperty, JsonReader reader, Object target)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.PopulateObject(Object newObject, JsonReader reader, JsonObjectContract contract, JsonProperty member, String id)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObject(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.SetPropertyValue(JsonProperty property, JsonConverter propertyConverter, JsonContainerContract containerContract, JsonProperty containerProperty, JsonReader reader, Object target)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.PopulateObject(Object newObject, JsonReader reader, JsonObjectContract contract, JsonProperty member, String id)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObject(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.SetPropertyValue(JsonProperty property, JsonConverter propertyConverter, JsonContainerContract containerContract, JsonProperty containerProperty, JsonReader reader, Object target)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.PopulateObject(Object newObject, JsonReader reader, JsonObjectContract contract, JsonProperty member, String id)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObject(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.PopulateList(IList list, JsonReader reader, JsonArrayContract contract, JsonProperty containerProperty, String id)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateList(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, Object existingValue, String id)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.SetPropertyValue(JsonProperty property, JsonConverter propertyConverter, JsonContainerContract containerContract, JsonProperty containerProperty, JsonReader reader, Object target)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.PopulateObject(Object newObject, JsonReader reader, JsonObjectContract contract, JsonProperty member, String id)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObject(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.Deserialize(JsonReader reader, Type objectType, Boolean checkAdditionalContent)
   at Newtonsoft.Json.JsonSerializer.DeserializeInternal(JsonReader reader, Type objectType)
   at Newtonsoft.Json.JsonSerializer.Deserialize(JsonReader reader, Type objectType)
   at Newtonsoft.Json.JsonConvert.DeserializeObject(String value, Type type, JsonSerializerSettings settings)
   at Newtonsoft.Json.JsonConvert.DeserializeObject[T](String value, JsonSerializerSettings settings)\n   at commercetools.Sdk.Serialization.SerializerService.Deserialize[T](String input)
   at commercetools.Sdk.HttpApi.CtpClient.SendRequest[T](HttpRequestMessage requestMessage)
   at commercetools.Sdk.HttpApi.CtpClient.ExecuteAsync[T](Command`1 command)
   at ProductService.Repository.CommerceToolsProductsRepository.<>c__DisplayClass23_0.<<FetchProducts>b__0>d.MoveNext() in /app/ProductService/Repository/CommerceToolsProductsRepository.cs:line 242\n--- End of stack trace from previous location where exception was thrown ---\n   at ...

After lots of investigation I figured out that this is due to the fact that International Components for Unicode library (a.k.a. icu-libs) is missing in dotnet:2.2-aspnetcore-runtime-alpine.
Adding the icu-libs:

FROM microsoft/dotnet:2.2-aspnetcore-runtime-alpine
ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT false
RUN apk add --no-cache icu-libs
ENV LC_ALL en_US.UTF-8
ENV LANG en_US.UTF-8

I believe the error being thrown could be more descriptive about the actual problem. Please have this one fixed, for other developers bumping into this problem.

DiscountCodeState has a wrong value

The DiscountCodeState.DoesNotMatch enum value does not match the API (as the documentation explains). This leads to a deserialization error of the JSON response from Commerce Tools when a Discount Code does not match the Cart in which it has been applied. Based on that, the correct value should be DoesNotMatchCart.

The actual code is

public enum DiscountCodeState
  {
    NotActive,
    NotValid,
    DoesNotMatch,
    MatchesCart,
    MaxApplicationReached,
    ApplicationStoppedByPreviousDiscount,
  }

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.