Implementing Add to Wishlist in Optimizely Commerce

Introduction

Adding a wishlist feature enhances user experience by letting customers save products for later purchase. In Optimizely Commerce, you can leverage the existing cart infrastructure to implement wishlists without creating new database tables or complex logic.

What you'll learn:

  • Use Commerce cart system for wishlists
  • Differentiate wishlist from shopping cart
  • Implement add/remove wishlist operations
  • Display wishlist items

Prerequisites:

  • Optimizely Commerce 14.x
  • .NET 6.0+
  • Basic understanding of Commerce cart system

The Approach

Instead of building a separate wishlist system, we'll use Optimizely Commerce's cart functionality with a unique cart name to distinguish wishlist items from actual shopping cart items.

Key Concept:

Shopping Cart → Cart Name: "Default"
Wishlist      → Cart Name: "Wishlist"

This approach provides:

  • ✅ Reuse existing cart infrastructure
  • ✅ Automatic persistence and user association
  • ✅ Built-in security and validation
  • ✅ Easy migration to cart for purchase

Step 1: Create Wishlist Service

Create a service to manage wishlist operations using the Commerce cart API.

// File: Business/Services/IWishlistService.cs
using Mediachase.Commerce.Orders;

namespace YourProject.Business.Services
{
    public interface IWishlistService
    {
        ICart GetWishlist();
        bool AddToWishlist(string code, decimal quantity = 1);
        bool RemoveFromWishlist(string code);
        bool MoveToCart(string code);
        int GetWishlistItemCount();
    }
}

Step 2: Implement Wishlist Service

// File: Business/Services/WishlistService.cs
using EPiServer.Commerce.Order;
using Mediachase.Commerce;
using Mediachase.Commerce.Orders;
using System.Linq;

namespace YourProject.Business.Services
{
    public class WishlistService : IWishlistService
    {
        private const string WishlistCartName = "Wishlist";
        
        private readonly IOrderRepository _orderRepository;
        private readonly IOrderGroupFactory _orderGroupFactory;
        private readonly ICurrentMarket _currentMarket;

        public WishlistService(
            IOrderRepository orderRepository,
            IOrderGroupFactory orderGroupFactory,
            ICurrentMarket currentMarket)
        {
            _orderRepository = orderRepository;
            _orderGroupFactory = orderGroupFactory;
            _currentMarket = currentMarket;
        }

        public ICart GetWishlist()
        {
            var market = _currentMarket.GetCurrentMarket();
            
            return _orderRepository.LoadOrCreateCart<ICart>(
                market.MarketId,
                WishlistCartName);
        }

        public bool AddToWishlist(string code, decimal quantity = 1)
        {
            var wishlist = GetWishlist();
            
            // Check if item already exists
            var existingItem = wishlist.GetAllLineItems()
                .FirstOrDefault(x => x.Code == code);

            if (existingItem != null)
            {
                // Update quantity if already in wishlist
                existingItem.Quantity += quantity;
            }
            else
            {
                // Add new item
                var lineItem = _orderGroupFactory.CreateLineItem(code, wishlist);
                lineItem.Quantity = quantity;
                wishlist.AddLineItem(lineItem);
            }

            return _orderRepository.Save(wishlist);
        }

        public bool RemoveFromWishlist(string code)
        {
            var wishlist = GetWishlist();
            var lineItem = wishlist.GetAllLineItems()
                .FirstOrDefault(x => x.Code == code);

            if (lineItem == null)
                return false;

            wishlist.RemoveLineItem(lineItem);
            return _orderRepository.Save(wishlist);
        }

        public bool MoveToCart(string code)
        {
            var wishlist = GetWishlist();
            var lineItem = wishlist.GetAllLineItems()
                .FirstOrDefault(x => x.Code == code);

            if (lineItem == null)
                return false;

            // Get shopping cart
            var cart = _orderRepository.LoadOrCreateCart<ICart>(
                _currentMarket.GetCurrentMarket().MarketId,
                "Default");

            // Add to cart
            var cartLineItem = _orderGroupFactory.CreateLineItem(code, cart);
            cartLineItem.Quantity = lineItem.Quantity;
            cart.AddLineItem(cartLineItem);

            // Remove from wishlist
            wishlist.RemoveLineItem(lineItem);

            // Save both
            _orderRepository.Save(cart);
            return _orderRepository.Save(wishlist);
        }

        public int GetWishlistItemCount()
        {
            var wishlist = GetWishlist();
            return wishlist.GetAllLineItems().Count();
        }
    }
}

Step 3: Register Service

// File: Startup.cs
public void ConfigureServices(IServiceCollection services)
{
    // Other services...
    
    services.AddTransient<IWishlistService, WishlistService>();
}

Step 4: Create Wishlist Controller

// File: Controllers/WishlistController.cs
using Microsoft.AspNetCore.Mvc;
using YourProject.Business.Services;

namespace YourProject.Controllers
{
    public class WishlistController : Controller
    {
        private readonly IWishlistService _wishlistService;

        public WishlistController(IWishlistService wishlistService)
        {
            _wishlistService = wishlistService;
        }

        [HttpGet]
        public IActionResult Index()
        {
            var wishlist = _wishlistService.GetWishlist();
            return View(wishlist);
        }

        [HttpPost]
        public IActionResult Add(string code, decimal quantity = 1)
        {
            var success = _wishlistService.AddToWishlist(code, quantity);
            
            if (success)
            {
                return Json(new { 
                    success = true, 
                    message = "Added to wishlist",
                    count = _wishlistService.GetWishlistItemCount()
                });
            }

            return Json(new { success = false, message = "Failed to add" });
        }

        [HttpPost]
        public IActionResult Remove(string code)
        {
            var success = _wishlistService.RemoveFromWishlist(code);
            
            return Json(new { 
                success = success,
                count = _wishlistService.GetWishlistItemCount()
            });
        }

        [HttpPost]
        public IActionResult MoveToCart(string code)
        {
            var success = _wishlistService.MoveToCart(code);
            
            return Json(new { 
                success = success,
                message = success ? "Moved to cart" : "Failed to move"
            });
        }
    }
}

Step 5: Create Wishlist View

@* File: Views/Wishlist/Index.cshtml *@
@model Mediachase.Commerce.Orders.ICart

<div class="wishlist-page">
    <h1>My Wishlist</h1>

    @if (!Model.GetAllLineItems().Any())
    {
        <p class="empty-message">Your wishlist is empty</p>
    }
    else
    {
        <div class="wishlist-items">
            @foreach (var item in Model.GetAllLineItems())
            {
                <div class="wishlist-item" data-code="@item.Code">
                    <img src="@item.GetEntryContent()?.GetDefaultAsset()?.Url" 
                         alt="@item.DisplayName" />
                    
                    <div class="item-details">
                        <h3>@item.DisplayName</h3>
                        <p class="price">@item.PlacedPrice.ToString("C")</p>
                        <p class="quantity">Qty: @item.Quantity</p>
                    </div>

                    <div class="item-actions">
                        <button class="btn-move-to-cart" 
                                data-code="@item.Code">
                            Add to Cart
                        </button>
                        
                        <button class="btn-remove" 
                                data-code="@item.Code">
                            Remove
                        </button>
                    </div>
                </div>
            }
        </div>
    }
</div>

<script>
// Add to Cart from Wishlist
$('.btn-move-to-cart').on('click', function() {
    const code = $(this).data('code');
    
    $.post('/wishlist/movetocart', { code: code })
        .done(function(data) {
            if (data.success) {
                location.reload();
            }
        });
});

// Remove from Wishlist
$('.btn-remove').on('click', function() {
    const code = $(this).data('code');
    
    $.post('/wishlist/remove', { code: code })
        .done(function(data) {
            if (data.success) {
                $(`[data-code="${code}"]`).fadeOut();
                updateWishlistCount(data.count);
            }
        });
});
</script>

Step 6: Add Wishlist Button to Product

@* File: Views/Product/Index.cshtml (excerpt) *@
<button class="btn-add-to-wishlist" 
        data-code="@Model.Code">
    <i class="icon-heart"></i> Add to Wishlist
</button>

<script>
$('.btn-add-to-wishlist').on('click', function() {
    const code = $(this).data('code');
    
    $.post('/wishlist/add', { code: code, quantity: 1 })
        .done(function(data) {
            if (data.success) {
                alert('Added to wishlist!');
                updateWishlistCount(data.count);
            }
        });
});

function updateWishlistCount(count) {
    $('.wishlist-count').text(count);
}
</script>

Common Issues

Issue 1: Items Not Persisting

Problem: Wishlist items disappear after session ends

Cause: Anonymous users' carts may not persist correctly

Solution:

// Ensure user identity is set
public ICart GetWishlist()
{
    var market = _currentMarket.GetCurrentMarket();
    var customerId = GetCurrentCustomerId(); // Get from authentication
    
    return _orderRepository.LoadOrCreateCart<ICart>(
        market.MarketId,
        WishlistCartName,
        customerId);
}

private Guid GetCurrentCustomerId()
{
    // Return authenticated user ID or anonymous ID
    if (User.Identity.IsAuthenticated)
        return Guid.Parse(User.Identity.Name);
    
    return GetAnonymousCustomerId();
}

Issue 2: Duplicate Items

Problem: Same product added multiple times

Solution:

public bool AddToWishlist(string code, decimal quantity = 1)
{
    var wishlist = GetWishlist();
    var existingItem = wishlist.GetAllLineItems()
        .FirstOrDefault(x => x.Code == code);

    if (existingItem != null)
    {
        // Don't increase quantity for wishlist
        return true; // Already in wishlist
    }

    var lineItem = _orderGroupFactory.CreateLineItem(code, wishlist);
    lineItem.Quantity = 1; // Always set to 1 for wishlist
    wishlist.AddLineItem(lineItem);

    return _orderRepository.Save(wishlist);
}

Issue 3: Wishlist Count Not Updating

Problem: UI counter doesn't reflect current wishlist state

Solution:

// Create a ViewComponent for dynamic wishlist count
public class WishlistCountViewComponent : ViewComponent
{
    private readonly IWishlistService _wishlistService;

    public WishlistCountViewComponent(IWishlistService wishlistService)
    {
        _wishlistService = wishlistService;
    }

    public IViewComponentResult Invoke()
    {
        var count = _wishlistService.GetWishlistItemCount();
        return View(count);
    }
}
@* Use in layout *@
<a href="/wishlist">
    <i class="icon-heart"></i>
    @await Component.InvokeAsync("WishlistCount")
</a>

Best Practices

1. Use Distinct Cart Names

// ✅ DO: Use clear, consistent naming
private const string WishlistCartName = "Wishlist";
private const string DefaultCartName = "Default";

// ❌ DON'T: Use ambiguous names
private const string WishlistCartName = "Cart2";

2. Handle Guest Users

// ✅ DO: Merge wishlist when user logs in
public void MergeAnonymousWishlist(Guid anonymousId, Guid customerId)
{
    var anonymousWishlist = _orderRepository.LoadCart<ICart>(
        anonymousId, WishlistCartName);
    
    var customerWishlist = _orderRepository.LoadCart<ICart>(
        customerId, WishlistCartName);

    foreach (var item in anonymousWishlist.GetAllLineItems())
    {
        AddToWishlist(item.Code, item.Quantity);
    }
    
    _orderRepository.Delete(anonymousWishlist.OrderLink);
}

3. Add Wishlist Status Check

// ✅ DO: Provide method to check if item is in wishlist
public bool IsInWishlist(string code)
{
    var wishlist = GetWishlist();
    return wishlist.GetAllLineItems()
        .Any(x => x.Code == code);
}

Use in view:

@if (await WishlistService.IsInWishlist(Model.Code))
{
    <button class="btn-in-wishlist" disabled>
        <i class="icon-heart-filled"></i> In Wishlist
    </button>
}
else
{
    <button class="btn-add-to-wishlist" data-code="@Model.Code">
        <i class="icon-heart"></i> Add to Wishlist
    </button>
}

Quick Reference

Key Operations

Operation Method Returns
Get wishlist GetWishlist() ICart
Add item AddToWishlist(code, qty) bool
Remove item RemoveFromWishlist(code) bool
Move to cart MoveToCart(code) bool
Get count GetWishlistItemCount() int

Cart Names

Shopping Cart: "Default"
Wishlist:      "Wishlist"
Save for Later: "SavedForLater" // Optional

Verification

After implementation, test:

  1. Add to Wishlist

    • Click "Add to Wishlist" on product
    • Verify item appears in wishlist page
    • Check count updates in header
  2. Remove from Wishlist

    • Click remove button
    • Verify item disappears
    • Check count decreases
  3. Move to Cart

    • Click "Add to Cart" from wishlist
    • Verify item moves to shopping cart
    • Verify item removed from wishlist
  4. Persistence

    • Add items to wishlist
    • Log out and log back in
    • Verify items still in wishlist

Conclusion

Implementing wishlist functionality using Optimizely Commerce's cart system is efficient and maintainable. By using a unique cart name, you leverage existing infrastructure without additional complexity.

Key Benefits:

  • ✅ Reuses proven cart functionality
  • ✅ Automatic user association and persistence
  • ✅ Easy to maintain and extend
  • ✅ Consistent with Commerce architecture
← Back to Blog