Commerce Controller Types in Sitecore Commerce

In this article, we review the Commerce Controller types to identify the purpose behind each controller type and assist with solution design in order to implement Commerce Controllers correctly in your Commerce Engine Plugin projects.

Entity Controllers

Entity Controllers, e.g. CartsController, implement Get methods and should only retrieve data, returning specific entitieslists of entities, or managed lists of entities. They should not manipulate data in the commerce databases or trigger behaviour within the Commerce Engine.

The following code snippet shows the Get Carts endpoint in the CartsController from the Carts Plugin.

public async Task<IEnumerable<Cart>> Get()
{
	CommerceList<Cart> commerceList = await Command<FindEntitiesInListCommand>()?.Process<Cart>(CurrentContext, CommerceEntity.ListName<Cart>(), 0, int.MaxValue);

	return (commerceList?.Items.ToList()) ?? new List<Cart>();
}

Api and CommerceOps Controllers

Api and CommerceOps Controllers implement methods that return non-OData entities only. The only difference between the two controllers is the Api and CommerceOps controller methods are routed via the '/api' and /commerceops' URL segments respectively.

The following code snippet shows the GetBulkPrices endpoint in the ApiController from the Catalog plugin.

public async Task<IActionResult> GetBulkPrices([FromBody] ODataActionParameters value)
{
	if (!ModelState.IsValid)
		return new BadRequestObjectResult(ModelState);
	if (!value.ContainsKey("itemIds") || !(value["itemIds"] is JArray))
		return new BadRequestObjectResult(value);

	var jarray = (JArray)value["itemIds"];
	IEnumerable<SellableItemPricing> bulkPrices = await Command<GetBulkPricesCommand>().Process(CurrentContext, jarray?.ToObject<IEnumerable<string>>());

	return new ObjectResult(bulkPrices);
}

Commands Controllers

Commands Controllers implement OData actions for manipulating data through Commands. They should only return Command OData entities, not commerce entities like carts. 

The command object contains information about the execution of the command, i.e. ResponseCodeStatusisCancelledisCompleted, etc.

Some APIs do not wait for the command to complete and will return the command with the Status property as WaitingForActivation. These long running commands can be followed up on using the CheckCommandStatus() API with taskId parameter. See the Check Long Running Command Status API in the Sitecore DevOps postman collection.

The following code snippet shows the AddFederatedPayment endpoint in the CommandsController from the Payments plugin.

public async Task<IActionResult> AddFederatedPayment([FromBody] ODataActionParameters value)
{
	if (!ModelState.IsValid || value == null)
		return new BadRequestObjectResult(ModelState);
	if (!value.ContainsKey("cartId") || (string.IsNullOrEmpty(value["cartId"]?.ToString()) || !value.ContainsKey("payment")) || string.IsNullOrEmpty(value["payment"]?.ToString()))
		return new BadRequestObjectResult(value);
	
	var cartId = value["cartId"].ToString();
	var paymentComponent = JsonConvert.DeserializeObject<FederatedPaymentComponent>;(value["payment"].ToString());
	var command = Command<AddPaymentsCommand>();
	
	await command.Process(CurrentContext, cartId, new List<PaymentComponent>() { paymentComponent });
	
	return new ObjectResult(command);
}

Summary

While there's nothing preventing developers from incorrectly using the Commerce Controllers, these coding standards will contribute to the long term maintainability of your Sitecore Commerce solutions.