Displaying product details
When building an MVC e-commerce site, you may want 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 an MVC application connected to Kentico.
To build product detail pages in the Kentico MVC framework, you need to ensure the following:
- Displaying product details pages
- (Optional) Customizing URLs of product details pages
- (Optional) Getting URLs of product pages from SKU data
Displaying product details pages
To display product details pages:
Tip: To view the full code of a functional example, you can inspect and download the LearningKit project on GitHub. You can also run the LearningKit website by connecting the project to a Kentico database.
Open your MVC 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 Guid ProductPageGuid; public readonly string ProductPageAlias; 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; ProductPageGuid = productPage.NodeGUID; ProductPageAlias = productPage.NodeAlias; // Fills the SKU information SKUInfo sku = productPage.SKU; SKUID = sku.SKUID; ImagePath = sku.SKUImagePath; 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() { // Initializes instances of services required to manage product price calculation and the shopping cart // For real-world projects, we recommend using a dependency injection container to initialize service instances shoppingService = Service.Resolve<IShoppingService>(); calculatorFactory = Service.Resolve<ICatalogPriceCalculatorFactory>(); }
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 specified by the GUID of the product's page. /// </summary> /// <param name="guid">Node GUID of the product's page.</param> /// <param name="productAlias">Node alias of the product's page.</param> public ActionResult Detail(Guid guid, string productAlias) { // Gets the product from the connected Kentico database SKUTreeNode product = GetProduct(guid); // 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(); } // Redirects if the specified page alias does not match if (!string.IsNullOrEmpty(productAlias) && !product.NodeAlias.Equals(productAlias, StringComparison.InvariantCultureIgnoreCase)) { return RedirectToActionPermanent("Detail", new { guid = product.NodeGUID, productAlias = product.NodeAlias }); } // Initializes the view model of the product with a calculated price ShoppingCartInfo cart = shoppingService.GetCurrentShoppingCart(); ProductCatalogPrices price = calculatorFactory .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(viewModel); } /// <summary> /// Retrieves the product specified by GUID of the product's page. /// </summary> /// <param name="nodeGuid">Node GUID of the product's page.</param> private SKUTreeNode GetProduct(Guid nodeGuid) { // Gets the page with the node GUID TreeNode node = DocumentHelper.GetDocuments() .LatestVersion(false) .Published(true) .OnSite(SiteContext.CurrentSiteName) .Culture("en-US") .CombineWithDefaultCulture() .WhereEquals("NodeGUID", nodeGuid) .FirstOrDefault(); // 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="@Url.Kentico().ImageUrl(Model.ImagePath, SizeConstraint.MaxWidthOrHeight(500))" 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 Using a shopping cart on MVC sites 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 Kentico objects.
Customizing URLs of product details pages
To provide SEO-friendly URLs, create a custom route according to your specifications.
For example, if you want to have your product details pages follow this pattern: <your domain>/Product/<node guid>/<node alias>, register the following route in the RouteConfig class in the App_Start folder:
routes.MapRoute(
name: "Product",
url: "Product/{guid}/{productAlias}",
defaults: new { controller = "Product", action = "Detail" },
constraints: new { guid = new GuidRouteConstraint() }
);
The example assumes that the MVC application displays product details pages with a Product controller and its Detail action (as used in Displaying product details pages).
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 its properties 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 = SKUInfoProvider.GetSKUInfo(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 = DocumentHelper.GetDocuments() .LatestVersion(false) .Published(true) .OnSite(siteName) .Culture("en-us") .CombineWithDefaultCulture() .WhereEquals("NodeSKUID", skuID) .FirstOrDefault(); // Returns 404 if no page for the specified product exists if (node == null) { return HttpNotFound(); } // Redirects to the product details page action method with the product information return RedirectToAction("Detail", "Product", new { guid = node.NodeGUID, productAlias = node.NodeAlias }); }
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.