Product recommendation banners in code cditor
The code editor allows you to create dynamic product recommendation banners using a combination of HTML, CSS, JavaScript, and Razor syntax. This powerful feature enables you to display personalized product recommendations directly on your website or in email campaigns.
The variables in the code editor preview will show as ‘@products[0]?.Name‘ instead of actual product names because the code isn’t compiled in the editor.
To see the banner with real product data, use the Live preview option, or test the banner on your actual website.
Supported technologies
The code editor supports the following technologies:
- HTML5 – for structuring your banner content.
- CSS3 – for styling and responsive design.
- JavaScript – for interactive functionality and dynamic behavior.
- Razor Syntax – for server-side logic and data binding using
@{}
blocks.
Initializing product recommendations#
To retrieve product recommendations, use the following Razor syntax within a code block:
@{
ExpertSender.Cdp.Common.Interfaces.DynamicContent.IProduct[] products = Model.GetRecommendedProducts(4).ToArray();
}
This code retrieves up to 4 recommended products and stores them in an array. You can adjust the number based on your banner design requirements.
Available product variables
The following table shows all available properties for each recommended product:
Variable | Type | Description | Example usage |
---|---|---|---|
Id | string | Unique product identifier | products[0].Id |
Name | string | Product name/title | products[0]?.Name |
ImageUrl | string | URL to product image | products[0]?.ImageUrl |
Url | string | Product page URL | products[0]?.Url |
PriceS | string | Regular price as formatted string | products[0]?.PriceS |
SalePriceS | string | Sale/discount price as formatted string | products[0]?.SalePriceS |
OmnibusPriceS | string | Omnibus price (EU compliance) | products[0]?.OmnibusPriceS |
CurrencySymbol | string | Currency symbol (€, $, £, etc.) | products[0]?.CurrencySymbol |
Common use cases#
Checking if product exists and displaying sale price
@if (products[0] != null)
{
Show your product here
}
Checking if sale price should be displayed#
To determine whether to show a sale price, check if both regular price and sale price are available:
@if (!string.IsNullOrWhiteSpace(products[0]?.SalePriceS))
{
@products[0]?.SalePriceS @products[0]?.CurrencySymbol
}
else
{
@products[0]?.PriceS @products[0]?.CurrencySymbol
}
Displaying omnibus price when available
The omnibus price is required for EU compliance when showing discounted prices:
@if (!string.IsNullOrWhiteSpace(products[0]?.OmnibusPriceS))
{
Lowest price in 30 days: @products[0]?.OmnibusPriceS @products[0]?.CurrencySymbol
}
Calculating percentage discount
To calculate and display the discount percentage:
@{
decimal regularPrice = 0;
decimal salePrice = 0;
decimal discountPercentage = 0;
if (decimal.TryParse(products[0]?.PriceS, out regularPrice) &&
decimal.TryParse(products[0]?.SalePriceS, out salePrice) &&
regularPrice > 0 && salePrice > 0)
{
discountPercentage = Math.Round(((regularPrice - salePrice) / regularPrice) * 100, 0);
}
}
@if (discountPercentage > 0)
{
-@discountPercentage%
}
Complete price display logic
Here’s a comprehensive example that handles all price scenarios:
@if (!string.IsNullOrWhiteSpace(rec2[0]?.SalePriceS))
{
@rec2[0]?.SalePriceS @rec2[0]?.CurrencySymbol
@rec2[0]?.PriceS @rec2[0]?.CurrencySymbol
@if (!string.IsNullOrWhiteSpace(rec2[0]?.OmnibusPriceS))
{
@rec2[0]?.OmnibusPriceS @rec2[0]?.CurrencySymbol
}
}
else
{
@rec2[0]?.PriceS @rec2[0]?.CurrencySymbol
}
Looping through all products
Here’s how to loop through all retrieved products and display their properties:
@{ ExpertSender.Cdp.Common.Interfaces.DynamicContent.IProduct[] products = Model.GetRecommendedProducts(4).ToArray();
}
@for (int i = 0; i < products.Length; i++)
{
if (products[i] != null)
{
@products[i].Id
@products[i].Name
@products[i].ImageUrl
@products[i].Url
@products[i].PriceS
@products[i].SalePriceS
@products[i].OmnibusPriceS
@products[i].CurrencySymbol
}
}
Sample banner structure
Here’s a complete example of a responsive 4-product recommendation banner:
<style>
.banner-header {
text-align: center;
padding: 20px;
background: white;
}
.banner-header h1 {
color: #2c3e50;
font-size: 24px;
margin-bottom: 5px;
font-weight: 600;
}
.banner-header p {
color: #7f8c8d;
font-size: 14px;
margin: 0;
}
.expertsender-banner {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 0;
width: 100%;
max-width: 1200px;
margin: 0 auto;
background: white;
box-sizing: border-box;
}
.banner-title {
grid-column: 1 / -1;
text-align: center;
font-size: 32px;
color: #2c3e50;
margin: 40px 0 30px 0;
font-weight: 300;
letter-spacing: -0.5px;
}
.product-card {
background: white;
padding: 30px 20px;
text-align: center;
transition: all 0.3s ease;
position: relative;
display: flex;
flex-direction: column;
height: 520px;
border-right: 1px solid #f1f3f4;
}
.product-card:last-child {
border-right: none;
}
.product-card:hover {
background: #fafbfc;
transform: translateY(-2px);
}
.discount-badge {
position: absolute;
top: 20px;
right: 20px;
background: #e74c3c;
color: white;
padding: 6px 12px;
border-radius: 20px;
font-size: 12px;
font-weight: 600;
box-shadow: 0 2px 8px rgba(231, 76, 60, 0.25);
}
.product-image {
width: 220px;
height: 220px;
object-fit: cover;
border-radius: 8px;
margin: 0 auto 20px auto;
transition: transform 0.3s ease;
background: #f8f9fa;
}
.product-card:hover .product-image {
transform: scale(1.02);
}
.product-name {
font-size: 18px;
font-weight: 500;
color: #2c3e50;
margin-bottom: 15px;
height: 50px;
overflow: hidden;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
line-height: 1.4;
}
.price-section {
margin-bottom: 25px;
flex-grow: 1;
display: flex;
flex-direction: column;
justify-content: center;
}
.current-price {
font-size: 24px;
font-weight: 600;
color: #2c3e50;
margin-bottom: 5px;
}
.sale-price {
font-size: 24px;
font-weight: 600;
color: #e74c3c;
margin-bottom: 5px;
}
.original-price {
font-size: 16px;
text-decoration: line-through;
color: #95a5a6;
}
.buy-button {
background: #2c3e50;
color: white;
padding: 12px 32px;
border: none;
border-radius: 4px;
text-decoration: none;
display: inline-block;
font-size: 14px;
font-weight: 500;
transition: all 0.3s ease;
text-transform: uppercase;
letter-spacing: 1px;
margin-top: auto;
}
.buy-button:hover {
background: #34495e;
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(44, 62, 80, 0.25);
}
.code-section {
margin-top: 40px;
background: white;
padding: 30px;
margin-left: 20px;
margin-right: 20px;
border-radius: 8px;
border: 1px solid #e9ecef;
}
.code-section h3 {
color: #2c3e50;
margin-bottom: 20px;
font-size: 18px;
font-weight: 500;
}
.code-block {
background: #f8f9fa;
color: #2c3e50;
padding: 20px;
border-radius: 6px;
overflow-x: auto;
font-family: 'Courier New', monospace;
font-size: 13px;
line-height: 1.6;
border: 1px solid #e9ecef;
}
@media (max-width: 768px) {
.product-card {
border-right: none;
border-bottom: 1px solid #f1f3f4;
height: auto;
min-height: 420px;
}
.product-card:last-child {
border-bottom: none;
}
.banner-title {
font-size: 28px;
margin: 30px 0 20px 0;
}
}
@media (max-width: 480px) {
.expertsender-banner {
grid-template-columns: 1fr;
}
.product-image {
width: 200px;
height: 200px;
}
}
</style>
@{
ExpertSender.Cdp.Common.Interfaces.DynamicContent.IProduct[] products = Model.GetRecommendedProducts(4).ToArray();
}
<div class="expertsender-banner">
<h3 class="banner-title">Featured Products</h3>
@foreach (var product in products)
{
<div class="product-card">
@if (decimal.TryParse(product?.PriceS, out decimal regularPrice) &&
decimal.TryParse(product?.SalePriceS, out decimal salePrice) &&
regularPrice > 0 && salePrice > 0)
{
<div class="discount-badge">@Math.Round(((regularPrice - salePrice) / regularPrice) * 100, 0)% OFF</div>
}
<img src="@product?.ImageUrl" alt="@product?.Name" class="product-image">
<div class="product-name">@product?.Name</div>
<div class="price-section">
@if (!string.IsNullOrWhiteSpace(product?.SalePriceS))
{
<div class="sale-price">@product?.SalePriceS @product?.CurrencySymbol</div>
<div class="original-price">@product?.PriceS @product?.CurrencySymbol</div>
}
else
{
<div class="current-price">@product?.PriceS @product?.CurrencySymbol</div>
}
</div>
<a href="@product?.Url" class="buy-button">Shop Now</a>
</div>
}
</div>
Banner preview:
Product data debug table
This table is useful for debugging and inspecting all product properties returned by the recommendation engine. It displays all available product data in a structured format, making it easy to verify that the API is returning the expected information.
@{
ExpertSender.Cdp.Common.Interfaces.DynamicContent.IProduct[] products = Model.GetRecommendedProducts(4).ToArray();
}
<style>
table.custom-table {
border-collapse: collapse;
width: 100%;
}
table.custom-table, table.custom-table th, table.custom-table td {
border: 1px solid black;
}
table.custom-table th, table.custom-table td {
padding: 8px;
}
</style>
<table class="custom-table">
<thead>
<tr>
<th>Product</th>
<th>Image</th>
<th>Name</th>
<th>Link</th>
<th>ID</th>
<th>Regular Price</th>
<th>Sale Price</th>
<th>Omnibus Price</th>
</tr>
</thead>
<tbody>
@foreach (var product in products.Where(p => p != null).Select((p, index) => new { Product = p, Index = index }))
{
<tr>
<td>Product @product.Index</td>
<td><img src="@product.Product.ImageUrl" alt="Product @product.Index" width="100"/></td>
<td>@product.Product.Name</td>
<td><a href="@product.Product.Url">@product.Product.Url</a></td>
<td>@product.Product.Id</td>
<td>@product.Product.PriceS @product.Product.CurrencySymbol</td>
<td>@product.Product.SalePriceS @product.Product.CurrencySymbol</td>
<td>@product.Product.OmnibusPriceS @product.Product.CurrencySymbol</td>
</tr>
}
</tbody>
</table>
Best practices#
- Always use null-conditional operators (
?.
) when accessing product properties to prevent errors. - Check for null or empty values before displaying content.
- Use responsive design principles to ensure your banners work across all devices.
- Test your banners with different numbers of returned products (the API might return fewer than requested).
- Consider loading states and empty states in your implementation.