Displaying product listings
When building an e-commerce site, you often need to display product catalog and product details pages.
In its most basic implementation, a product catalog is a listing of various products and services offered in a particular store. Customers can filter the catalog according to a product’s properties and specifications. For example by brand, price, size, etc. Individual product details pages then convey more comprehensive information about particular products.
To build a product listing page, you need to ensure:
Note that the example presented on this page uses content tree-based routing.
Displaying product listings
To display a listing of products:
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.
Generate code files for the product page types you intend to display.
- In the Page types application, edit the specific product page type.
- Switch to the Code tab.
- Click Save code.
Open your live site project in Visual Studio.
Include the generated code files to your project.
Add view models for each product you want to display. A view model for a particular product only needs to hold properties you plan to display or otherwise require in related views. If you want to display properties specific to a given product page type, you should create a separate view model for each product.
public class ProductListItemViewModel { public readonly PriceDetailViewModel PriceModel; public string Name; public string ImagePath; public string PublicStatusName; public PageUrl ProductUrl; public bool Available; /// <summary> /// Constructor for the ProductListItemViewModel class. /// </summary> /// <param name="productPage">Product's page.</param> /// <param name="priceDetail">Price of the product.</param> /// <param name="publicStatusName">Display name of the product's public status.</param> public ProductListItemViewModel(SKUTreeNode productPage, ProductCatalogPrices priceDetail, IPageUrlRetriever urlRetriever, string publicStatusName) { // Sets the page information Name = productPage.DocumentName; ProductUrl = urlRetriever.Retrieve(productPage); // Sets the SKU information ImagePath = string.IsNullOrEmpty(productPage.SKU.SKUImagePath) ? null : new FileUrl(productPage.SKU.SKUImagePath, true) .WithSizeConstraint(SizeConstraint.MaxWidthOrHeight(400)) .RelativePath; Available = !productPage.SKU.SKUSellOnlyAvailable || productPage.SKU.SKUAvailableItems > 0; PublicStatusName = publicStatusName; // Sets the price format information PriceModel = new PriceDetailViewModel { Price = priceDetail.Price, ListPrice = priceDetail.ListPrice, CurrencyFormatString = priceDetail.Currency.CurrencyFormatString }; } }
The PriceDetailViewModel property is itself a model that contains select properties of the CMS.ECommerce.ProductCatalogPrices type. Objects of the ProductCatalogPrices type hold the final price values for a product after all price calculations have been applied.
public class PriceDetailViewModel { public decimal Price; public decimal ListPrice; public string CurrencyFormatString; }
Add controllers for each product page type you intend to display. The controller’s action retrieves products of the specific product page type and displays them. The following sample code uses products of the LearningProductType page type as an example.
We recommend using a dependency injection container to initialize instances of used API services (e.g. IShoppingService or ICatalogPriceCalculatorFactory).
public class ProductListingController : Controller { private readonly IShoppingService shoppingService; private readonly ICatalogPriceCalculatorFactory priceCalculatorFactory; private readonly IPageRetriever pageRetriever; private readonly IPageUrlRetriever pageUrlRetriever; /// <summary> /// Initializes instances of services required to manage product price calculation and the shopping cart. /// </summary> public ProductListingController(IShoppingService shoppingService, ICatalogPriceCalculatorFactory priceCalculatorFactory, IPageRetriever pageRetriever, IPageUrlRetriever pageUrlRetriever) { this.shoppingService = shoppingService; this.priceCalculatorFactory = priceCalculatorFactory; this.pageRetriever = pageRetriever; this.pageUrlRetriever = pageUrlRetriever; } /// <summary> /// Displays a product listing page of the class's product page type. /// </summary> public ActionResult Listing() { // Gets products of the product page type (via the generated page type code) List<LearningProductType> products = pageRetriever.Retrieve<LearningProductType>(query => query .CombineWithDefaultCulture() .WhereTrue("SKUEnabled") .OrderByDescending("SKUInStoreFrom")) .ToList(); ShoppingCartInfo cart = shoppingService.GetCurrentShoppingCart(); // Prepares a collection of products of the LearningProductType page type to be sent to a view IEnumerable<ProductListItemViewModel> productListing = products.Select( product => new ProductListItemViewModel( product, GetPrice(product.SKU, cart), pageUrlRetriever, product.Product.PublicStatus?.PublicStatusDisplayName)); // Displays the action's view with an initialized view model return View(productListing); } // Retrieves a ProductCatalogPrices instance that contains calculated price information for the given product private ProductCatalogPrices GetPrice(SKUInfo product, ShoppingCartInfo cart) { return priceCalculatorFactory .GetCalculator(cart.ShoppingCartSiteID) .GetPrices(product, Enumerable.Empty<SKUInfo>(), cart); } }
Using the product catalog price calculation API
The Xperience E-Commerce solution performs all product price calculations via ICatalogPriceCalculatorFactory. First call the GetCalculator(int siteId) method on an instance of ICatalogPriceCalculatorFactory to obtain a calculator of catalog prices for a site with the given site ID, then use the calculator’s GetPrices method to calculate the catalog price for a given product. GetPrices returns an object of the ProductCatalogPrices type that holds the product’s calculated catalog price values.
See the GetPrice method from the code sample above for a working example.
Add a view that sets the appearance of the product listing. For example:
@model IEnumerable<ProductListItemViewModel> <h2>Product listing of the LearningProductType</h2> <div> @* Iterates over all products. *@ @foreach (ProductListItemViewModel product in Model) { @* Generates a URL leading to the product's detail page. *@ <a href="@product.ProductUrl.AbsoluteUrl"> <h3>@product.Name</h3> @* Displays information about the product's public status. *@ @if (!string.IsNullOrEmpty(product.PublicStatusName)) { <span>@product.PublicStatusName</span> } @* Displays the product's image. *@ @if (!string.IsNullOrEmpty(product.ImagePath)) { <img src="@product.ImagePath" alt="@product.Name"> } @* Displays the product's other properties. *@ <div> @if (!product.Available) { <span>Out of stock</span> } <span>@String.Format(product.PriceModel.CurrencyFormatString, product.PriceModel.Price)</span> @if (product.PriceModel.ListPrice > product.PriceModel.Price) { <s>@String.Format(product.PriceModel.CurrencyFormatString, product.PriceModel.ListPrice)</s> } </div> </a> } </div>
Customers can now browse through a listing of products of a particular product page type. Clicking on a product item from the listing sends them to the product’s details page.
Adjusting product image size
To change the size of product images, use the WithSizeConstraint extension methods on the FileUrl object. The system then automatically resizes the image based on the entered size while keeping the image’s aspect ratio.
Construct a FileUrl object by providing the following parameters to the object’s constructor:
- RelativePath – the relative path can be obtained via the SKUInfo.SKUImagePath property.
- isImage – a boolean flag used to determine whether the provided relative path leads to an image.
string relativeUrl = new FileUrl(sku.SKUImagePath, true)
.WithSizeConstraint(SizeConstraint.MaxWidthOrHeight(400))
.RelativePath;