# Wednesday, 18 May 2011

Baskets, carts, bags, whatever you want to call them, people have very different expectations of how they should work given the business they’re in. By default uCommerce uses cookies to find the basket for a given customer, which is handy because the basket automatically carries over if the customer logs in. No need to migrate it from an anonymous context to a customer context.

But what if you have scenario where multiple people use the same computer to shop from? Effectively they’d share the same basket because it’s effectively tied to the computer, not the customer.

Fortunately the uCommerce Foundation API makes it simple to override this behavior. So lets take a look at what’s required to change the default basket behavior.

IOrderContext

uCommerce uses the IOrderContext interface to determine the basket of the current customer, specifically the GetBasket() and GetBasket(Boolean) methods are used. Let’s take a look at what the IOrderContext interface contains.

image

To make things are little easier we’re not going to implement IOrderContext from scratch, rather we’ll modify the default implementation, which comes with uCommerce called OrderContext.

OrderContext

As mentioned we’re looking to change the behavior of loading a basket for a customer and OrderContext has two methods for this purpose: GetBasket() and GetBasket(Boolean). Basically what we’re going to do is use the default behavior when a customer is not logged in and use our custom behavior whenever a customer is logged in.

Overriding GetBasket(Boolean)

First we need a new class to inherit the existing OrderContext and second override the two GetBasket methods.

using System;
using System.Linq;
using UCommerce.Entities;
using UCommerce.Runtime;
using umbraco.cms.businesslogic.member;
 
namespace MyUCommerceApp.Library
{
    public class MyOrderContext : OrderContext
    {
        protected ICatalogContext CatalogContext { get; set; }
 
        public MyOrderContext(ICatalogContext catalogContext) : base(catalogContext)
        {
            CatalogContext = catalogContext;
        }
 
        public override Basket GetBasket(bool create) {}
 
        public override Basket GetBasket()
        {
            return GetBasket(false);
        }
    }
}

So right now it doesn’t do much, so lets make it behave like the existing OrderContext when a customer is not logged in. We’re going to use the Umbraco Member API to determine whether a customer is logged in.

using System;
using System.Linq;
using UCommerce.Entities;
using UCommerce.Runtime;
using umbraco.cms.businesslogic.member;
 
namespace MyUCommerceApp.Library
{
    public class MyOrderContext : OrderContext
    {
        protected ICatalogContext CatalogContext { get; set; }
 
        public MyOrderContext(ICatalogContext catalogContext) : base(catalogContext)
        {
            CatalogContext = catalogContext;
        }
 
        public override Basket GetBasket(bool create)
        {
            // Member is not logged on use default behavior
            if (!Member.IsLoggedOn()) return base.GetBasket(create);
        }
 
        public override Basket GetBasket()
        {
            return GetBasket(false);
        }
 
    }
}

Now we’re actually detecting whether the customer is logged in and use the default behavior of OrderContext for customers not logged in.

Right now that means that the code is going to fail if a customer logs in, so lets add the final piece where we load the basket basket based on the current member id. If we can’t find a basket like that we’ll have to create one. Here we go.

using System;
using System.Linq;
using UCommerce.Entities;
using UCommerce.Runtime;
using umbraco.cms.businesslogic.member;
 
namespace MyUCommerceApp.Library
{
    public class MyOrderContext : OrderContext
    {
        protected ICatalogContext CatalogContext { get; set; }
 
        public MyOrderContext(ICatalogContext catalogContext) : base(catalogContext)
        {
            CatalogContext = catalogContext;
        }
 
        public override Basket GetBasket(bool create)
        {
            // Member is not logged on use default behavior
            if (!Member.IsLoggedOn()) return base.GetBasket(create);
 
            // Otherwise try and load a basket for the current member, create one if it doesn't exist
            Basket basket = GetBasketForCurrentMember() ?? CreateBasket();
 
            return basket;
 
        }
        public override Basket GetBasket()
        {
            return GetBasket(false);
        }
 
        private Basket GetBasketForCurrentMember()
        {
            PurchaseOrder order = PurchaseOrder.SingleOrDefault(
                x => x.OrderProperties.Where(y => y.OrderId == x.OrderId && y.Key == "MemberId" && y.Value == Member.CurrentMemberId().ToString()).Count() > 0 
                    && x.OrderStatusId == 1);
 
            if (order != null) return new Basket(order);
 
            return null;
        }
 
        private Basket CreateBasket()
        {
            var catalogId = 0;
            int.TryParse(CatalogContext.CurrentCatalogName, out catalogId);
 
            var catalog = ProductCatalog.SingleOrDefault(c => c.Name.Equals(CatalogContext.CurrentCatalogName)) ??
                ProductCatalog.SingleOrDefault(c => c.ProductCatalogId == catalogId);
 
            if (catalog == null)
                throw new InvalidOperationException("Cannot create basket when not in product catalog. Make sure that SiteContext.Current.CatalogContext.CurrentCatalogName contains the name of the current product catalog.");
 
            return CreateBasket(catalog.PriceGroups.First().Currency);
        }
 
        private Basket CreateBasket(Currency currency)
        {
            var catalogGroup = ProductCatalogGroup.SingleOrDefault(g => g.Name.Equals(CatalogContext.CurrentCatalogGroupName));
 
            if (catalogGroup == null)
                throw new InvalidOperationException("Cannot create basket without product catalog group. Make sure that SiteContext.Current.CatalogContext.CurrentCatalogGroupName contains the name of the current product catalog group.");
 
            PurchaseOrder order = new PurchaseOrder();
            order.OrderStatusId = 1;// (int)PurchaseOrder.StatusCode.Basket;
            order.ProductCatalogGroup = catalogGroup;
            order.CurrencyId = currency.CurrencyId;
            order.BasketId = Guid.NewGuid();
            order.CreatedDate = DateTime.Now;
            order.Save();
 
            // Set the member id on the order so we can retrieve it later on
            order["MemberId"] = Member.CurrentMemberId().ToString();
 
            return new Basket(order);
        }
    }
}

The thing that makes the code above tick is the used of dynamic order properties. Basically a way of setting custom data on the order or individual order lines. Whenever a new basket is created by MyOrderContext it will store the current member id on the order itself and use that to load the basket the next time around.

Registering MyOrderContext with uCommerce

To test our new context class we’ll have to let uCommerce know it exists. The way to do this is to edit the components.config file, which holds information about all the components of uCommerce. You’ll the file in /umbraco/ucommerce/configuration/components.config and this is what it looks like.

image

We’re looking for the IOrderContext registration.

image

A registration basically refers to a service, typically an interface like IOrderContext, a type, which is the actual implementation to use for that particular interface, and a lifestyle, which is how long the object will live. You can read more about lifestyle if you’re interested, but for now just leave that attribute alone.

We’ll have to put in our type and assembly for IOrderContext. For this example my code compiles to MyUCommerceApp.Library.dll and the type is called MyUCommerceApp.Library.MyOrderContext, so that’s what we’re going to swap in to replace the current order context.

<component id="OrderContext"
    service="UCommerce.Runtime.IOrderContext, UCommerce"
    type="MyUCommerceApp.Library.MyOrderContext, MyUCommerceApp.Library" lifestyle="PerWebRequest"/>

What that we’re ready to test our site. The behavior you’ll see is that you’ll get one basket when you’re not logged in, the default behavior, and another basket when you log in, our new custom behavior.

In Summary

As you can see overriding the default basket behavior of uCommerce is straightforward and you can achieve exactly the behavior you want. You saw a couple of interesting pieces put together to make it work: The IOrderContext and OrderContext, which were overridden to inject our custom behavor, the dynamic order property for storing our member if, and finally the component registration to tell uCommerce about our custom component.

Components in particular is a useful thing to know about as there are many opportunities to override the default behavior almost any aspect of uCommerce in there.

Other interesting examples for overriding GetBasket() might be to:

  • Do individual baskets between stores when working with a multiple store setup in the same uCommerce installation to replace the default shared baskets.
  • Work with multiple baskets per customer for wishlist and gift registry scenarios.
  • Support impersonation to enable a customer representative take over the customer basket to help her through checkout.

The sky’s the limit.

Comments are closed.