Extending Sitecore Experience Accelerator(SXA) Repositories

SXA modules(talking about features mainly) follow the repository pattern. Each api controller depends on a repository to call commerce roles. For example, we have a module Sitecore.Commerce.XA.Feature.Cart which takes care of cart and checkout related operations. There is a CartController which talks to AddToCartRepository for adding a product to the cart.

RepositoryPattern

 

This repository exposes three methods

    AddToCartRenderingModel GetAddToCartModel();

    BaseJsonResult AddLineItemsToCart(
    IStorefrontContext storefrontContext,
    IVisitorContext visitorContext,
    string catalogName,
    string productId,
    string variantId,
    Decimal quantity);

    BaseJsonResult AddBundleToCart(
    IStorefrontContext storefrontContext,
    IVisitorContext visitorContext,
    BundleSelectionInputModel bundle);

While working on a project recently, we had to implement inventory checks for certain products while adding them to the cart. AddLineItmesToCart method doesn’t check for inventory so we decided to overwrite the functionality for this method.

In order to extend AddToCartRepository, we created a feature module in our project calling it MyProject.Commerce.XA.Feature.Cart. Since this module extends the Sitecore.Commerce.XA.Feature.Cart module, we added required references to our module. We only wanted to extend the functionality of AddToCartRepository so we created the ExtendedAddToCartRepository class which inherits from AddToCartRepository.

And rest was simple C# code 🙂

    public class ExtendedAddToCartRepository : AddToCartRepository
    {
        ICartManager cartManager;
        ISiteContext siteContext;
        IStorefrontContext storefrontContext;
        IVisitorContext visitorContext;
        ISearchManager searchManager;
        IInventoryManager inventoryManager;
        public ExtendedAddToCartRepository(IModelProvider modelProvider, ICartManager cartManager, ISiteContext siteContext, IStorefrontContext storefrontContext, IVisitorContext visitorContext, ISearchManager searchManager, IInventoryManager inventoryManager) : base(modelProvider, cartManager, siteContext)
        {
            this.cartManager = cartManager;
            this.siteContext = siteContext;
            this.storefrontContext = storefrontContext;
            this.visitorContext = visitorContext;
            this.searchManager = searchManager;
            this.inventoryManager = inventoryManager;
        }

        public override AddToCartRenderingModel GetAddToCartModel()
        {
            var model = base.GetAddToCartModel();
            if (string.IsNullOrEmpty(model.CatalogName))
            {
                model.CatalogName = storefrontContext?.CurrentStorefront?.Catalog;
            }

            return model;
        }

        public override BaseJsonResult AddLineItemsToCart(IStorefrontContext storefrontContext, IVisitorContext visitorContext, string catalogName, string productId, string variantId, decimal quantity)
        {
            Assert.ArgumentNotNull(storefrontContext, "storefrontContext");
            Assert.ArgumentNotNull(visitorContext, "visitorContext");
            Assert.ArgumentNotNullOrEmpty(catalogName, "catalogName");
            Assert.ArgumentNotNullOrEmpty(productId, "productId");
            Assert.IsTrue(quantity > decimal.Zero, "quantity > 0");

            var model = ModelProvider.GetModel<BaseJsonResult>();
            var currentStorefront = storefrontContext.CurrentStorefront;


            var stockinformations = GetProductStockInformation(productId, variantId);

            if (!string.IsNullOrEmpty(stockinformations.ErrorMessage))
            {
                model.Success = false;
                model.Errors.Add(stockinformations.ErrorMessage);
                return model;
            }
            if (stockinformations.Count < decimal.ToDouble(quantity))
            {
                model.Success = false;
                model.Errors.Add($"Sorry, we only have ({stockinformations.Count}) in stock currently");
                return model;
            }

            var currentCart = CartManager.GetCurrentCart(visitorContext, storefrontContext, false);
            if (currentCart.ServiceProviderResult.Success && currentCart.Result != null)
            {
                var list = new List<CartLineArgument>
                {
                    new CartLineArgument
                    {
                        CatalogName = catalogName,
                        ProductId = productId,
                        VariantId = variantId,
                        Quantity = quantity
                    }
                };
              var managerResponse = CartManager.AddLineItemsToCart(currentStorefront, visitorContext, currentCart.Result, list);
                if (!managerResponse.ServiceProviderResult.Success)
                {
                    model.SetErrors(managerResponse.ServiceProviderResult);
                    return model;
                }

                model.Success = true;
                return model;
            }

            var systemMessage = storefrontContext.GetSystemMessage("Cart Not Found Error", true);

            currentCart.ServiceProviderResult.SystemMessages.Add(new SystemMessage
            {
                Message = systemMessage
            });

            model.SetErrors(currentCart.ServiceProviderResult);
            return model;
        }

        public (double Count, string ErrorMessage) GetProductStockInformation(string productId, string variantId)
        {
            var model = ModelProvider.GetModel<StockInfoListJsonResult>();

            var currentStorefront = storefrontContext.CurrentStorefront;
            string catalog = currentStorefront.Catalog;
            Item product = searchManager.GetProduct(productId, catalog);
            Assert.IsNotNull(product, string.Format(CultureInfo.InvariantCulture, 
                          "Unable to locate the product with id: {0}", productId));
            var list = new List<CommerceInventoryProduct>();
            if (product.HasChildren)
            {
                foreach (Item child in product.Children)
                {
                    list.Add(new CommerceInventoryProduct
                    {
                        ProductId = productId,
                        CatalogName = catalog,
                        VariantId = child.Name
                    });
                }
            }
            else
            {
                list.Add(new CommerceInventoryProduct
                {
                    ProductId = productId,
                    CatalogName = catalog
                });
            }

            ManagerResponse<GetStockInformationResult, IEnumerable<StockInformation>> stockInformation = 
            inventoryManager.GetStockInformation(currentStorefront, list, StockDetailsLevel.All);
            if (!stockInformation.ServiceProviderResult.Success)
            {
                return (0, $"Cannot get stock information for prodcut: {productId}|{variantId}");
            }

            StockInfoJsonResult stockInfoResult = null;

            model.Initialize(stockInformation.Result);


            stockInfoResult = (string.IsNullOrEmpty(variantId)) ? model.StockInformationList.
                              Where(x => x.ProductId == productId).FirstOrDefault() : 
                              model.StockInformationList.Where(x => x.VariantId == variantId).FirstOrDefault();

            return (stockInfoResult == null ? 0 : stockInfoResult.Count, "");
        }

    }

InventoryManager was required to make the inventory checks.

And the last step to replace the dependency injection

    public class ServicesConfigurator : IServicesConfigurator
    {
        public void Configure(IServiceCollection serviceCollection)
        {
            serviceCollection.AddTransient(typeof(IAddToCartRepository), typeof(ExtendedAddToCartRepository));
        }
    }

 

There you are!

RepositoryPattern1

 

Leave a Reply