Displaying product details
When building e-commerce sites, you often need to display product catalog and product details pages. Product details pages usually include more comprehensive information about specific products.
This page describes how to display products from the Products application in a live site application connected to Xperience.
Note that the examples were created for a site that uses content tree-based routing.
Displaying product details pages
Tip: View the full code of a functional example in the LearningKit project on GitHub. You can also run the LearningKit website by connecting the project to a Xperience database. Follow the instructions in the repository’s README file.
Open your live site project in Visual Studio.
Add a generic view model representing products. Only include the properties that you wish to display or otherwise require. Create separate view models if you require properties specific to a given product.
public readonly PriceDetailViewModel PriceDetail; public readonly string Name; public readonly string Description; public readonly string ShortDescription; public readonly int SKUID; public readonly string ImagePath; public readonly bool IsInStock; /// <summary> /// Creates a new product model. /// </summary> /// <param name="productPage">Product's page.</param> /// <param name="priceDetail">Price of the product.</param> public ProductViewModel(SKUTreeNode productPage, ProductCatalogPrices priceDetail) { // Fills the page information Name = productPage.DocumentName; Description = productPage.DocumentSKUDescription; ShortDescription = productPage.DocumentSKUShortDescription; // Fills the SKU information SKUInfo sku = productPage.SKU; SKUID = sku.SKUID; ImagePath = string.IsNullOrEmpty(sku.SKUImagePath) ? null : new FileUrl(sku.SKUImagePath, true) .WithSizeConstraint(SizeConstraint.MaxWidthOrHeight(400)) .RelativePath; IsInStock = sku.SKUTrackInventory == TrackInventoryTypeEnum.Disabled || sku.SKUAvailableItems > 0; PriceDetail = new PriceDetailViewModel() { Price = priceDetail.Price, ListPrice = priceDetail.ListPrice, CurrencyFormatString = priceDetail.Currency.CurrencyFormatString }; }
Add a new controller with an action that displays the product details page. The example also retrieves the coupled product page.
Initializing services/// <summary> /// Constructor for the ProductController class. /// </summary> public ProductController(IShoppingService shoppingService, ICatalogPriceCalculatorFactory priceCalculatorFactory, IPageDataContextRetriever pageRetriever, ISiteService siteService, ISKUInfoProvider skuInfoProvider) { // Initializes instances of services required to manage product price calculation and the shopping cart this.shoppingService = shoppingService; this.priceCalculatorFactory = priceCalculatorFactory; this.pageRetriever = pageRetriever; this.siteService = siteService; this.skuInfoProvider = skuInfoProvider; }
We recommend using a dependency injection container to initialize instances of used API services (e.g. IShoppingService ).
/// <summary> /// Displays a product detail page of a product. /// </summary> public ActionResult Detail() { // Gets the product from the data context SKUTreeNode product = GetProduct(); // If the product is not found or if it is not allowed for sale, redirects to error 404 if ((product == null) || (product.SKU == null) || !product.SKU.SKUEnabled) { return HttpNotFound(); } // Initializes the view model of the product with a calculated price ShoppingCartInfo cart = shoppingService.GetCurrentShoppingCart(); ProductCatalogPrices price = catalogPriceCalculatorFactory .GetCalculator(cart.ShoppingCartSiteID) .GetPrices(product.SKU, Enumerable.Empty<SKUInfo>(), cart); // Fills the product model with retrieved data ProductViewModel viewModel = new ProductViewModel(product, price); // Displays the product details page return View("Detail", viewModel); } /// <summary> /// Retrieves the product. /// </summary> private SKUTreeNode GetProduct() { // Gets the current page from the router data context TreeNode node = pageRetriever.Retrieve<TreeNode>().Page; // If the found page is not a product, returns null if (node == null || !node.IsProduct()) { return null; } // Loads specific fields of the product's product page type from the database node.MakeComplete(true); // Returns the found page as a product page return node as SKUTreeNode; }
Add a view that sets the appearance of the product details page.
@model LearningKit.Models.Products.ProductViewModel <h2>@Model.Name</h2> @* Renders product details. *@ @if (!string.IsNullOrEmpty(Model.ImagePath)) { <img src="@Model.ImagePath" alt="@Model.Name"> } <ul> <li> Description: @Html.Raw(@Model.Description) </li> <li> In stock: @if (!Model.IsInStock) { <span id="stockMessage">Yes</span> } else { <span id="stockMessage">No</span> } </li> <li> Total price: <span id="totalPrice">@String.Format(Model.PriceDetail.CurrencyFormatString, Model.PriceDetail.Price)</span> </li> </ul>
Include a button that adds the product to a shopping cart. The button in the example calls the AddItem POST action from the CheckoutController implemented in Integrating the shopping cart as part of the Implementing a checkout process series.
@* Renders an add to cart button. *@ using (Html.BeginForm("AddItem", "Checkout", FormMethod.Post)) { <input type="hidden" name="itemSkuId" value="@Model.SKUID" /> <label>Qty</label> <input type="text" name="itemUnits" value="1" /> <input type="submit" name="AddItem" value="Add to cart" /> }
Customers can now browse product details pages. They are also able to add a product to their shopping cart directly from a product’s detail page.
You can access the product page type’s fields through generated model classes. See more in Generating classes for Xperience objects.
Getting URLs of product pages from SKU data
When generating links to product details pages from pages that only hold a product’s SKU information, you first need to retrieve the coupled product page and use it to build a qualified URL:
Edit the controller class you want to modify.
Add an action that retrieves a product page from a given SKU identifier.
/// <summary> /// Redirects to a product detail page based on the ID of a product's SKU object. /// </summary> /// <param name="skuID">ID of the product's SKU object.</param> public ActionResult ItemDetail(int skuID) { // Gets the SKU object SKUInfo sku = skuInfo.Get(skuID); // If the SKU does not exist or it is a product option, returns error 404 if (sku == null || sku.IsProductOption) { return HttpNotFound(); } // If the SKU is a product variant, uses its parent product's ID if (sku.IsProductVariant) { skuID = sku.SKUParentSKUID; } // Gets the product's page TreeNode node = pageRetriever.Retrieve<TreeNode>(query => query .Culture("en-us") .CombineWithDefaultCulture() .WhereEquals("NodeSKUID", skuID)) .FirstOrDefault(); // Returns 404 if no page for the specified product exists if (node == null) { return HttpNotFound(); } // Gets the product page's URL string pageUrl = pageUrlRetriever.Retrieve(node).AbsoluteUrl; return Redirect(pageUrl); }
If you do not use product variants in your store, you can move the variant check to the condition that returns a Not Found response, or remove it completely.
Add a link to the ItemDetail action method to views where you only have access to the SKU identifier value.
@Html.ActionLink(model.SKUName, "ItemDetail", new { skuId = model.SKUID })
OR
<a href="@Url.Action("ItemDetail", new { skuId = model.SKUID })">link text</a>
You are now able to build valid URLs to product details pages even when you do not have the product’s page information available.