Changing the Default Basket Behavior of Ucommerce
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 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.
To make things a little easier we’re not going to implement IOrderContext from scratch, rather we’ll modify the default implementation, which comes with Ucommerce called OrderContext. You can use the same strategy if you're only interested in overriding a specific method.
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 UCommerce.Security; namespace MyUCommerceApp.Library { public class MyOrderContext : OrderContext { protected ICatalogContext CatalogContext { get; set; } protected IMemberService MemberService { get; set; } public MyOrderContext(ICatalogContext catalogContext, IMemberService memberService) : base(catalogContext) { CatalogContext = catalogContext; MemberService = memberService; } 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 MemberService API to determine whether a customer is logged in.
using System; using System.Linq; using UCommerce.Entities; using UCommerce.Runtime; using UCommerce.Security; namespace MyUCommerceApp.Library { public class MyOrderContext : OrderContext { protected ICatalogContext CatalogContext { get; set; } protected IMemberService MemberService { get; set; } public MyOrderContext(ICatalogContext catalogContext, IMemberService memberService) : base(catalogContext) { CatalogContext = catalogContext; MemberService = memberService; } public override Basket GetBasket(bool create) { // Member is not logged on use default behavior if (!MemberService.IsLoggedIn()) 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; namespace MyUCommerceApp.Library { public class MyOrderContext : OrderContext { protected ICatalogContext CatalogContext { get; set; } protected IMemberService MemberService { get; set; } public MyOrderContext(ICatalogContext catalogContext, IMemberService memberService) : base(catalogContext) { CatalogContext = catalogContext; MemberService = memberService; } public override Basket GetBasket(bool create) { // Member is not logged on use default behavior if (!MemberService.IsLoggedIn()) 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
Now we only have to register our new service. If you're interested in how to do that, please read How to Register a Component
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 id, 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.