Frontend Integration
Learn how to add public-facing pages and widgets to the customer-side of your nopCommerce store.
Widget Zones
Widgets allow you to render content in predefined areas of the store.
Available Widget Zones
| Zone | Location |
|---|---|
HomepageTop | Top of homepage |
HomepageBottom | Bottom of homepage |
ProductDetailsTop | Above product details |
ProductDetailsBottom | Below product details |
CategoryDetailsTop | Top of category page |
ShoppingCartTop | Above shopping cart |
CheckoutProgressBefore | Before checkout steps |
Implementing a Widget
csharp
// MyWidgetPlugin.cs
public class MyWidgetPlugin : BasePlugin, IWidgetPlugin
{
public Task<IList<string>> GetWidgetZonesAsync()
{
return Task.FromResult<IList<string>>(new List<string>
{
PublicWidgetZones.HomepageTop,
PublicWidgetZones.ProductDetailsBottom
});
}
public Type GetWidgetViewComponent(string widgetZone)
{
return widgetZone switch
{
var z when z == PublicWidgetZones.HomepageTop => typeof(HomeBannerViewComponent),
var z when z == PublicWidgetZones.ProductDetailsBottom => typeof(ProductReviewsViewComponent),
_ => typeof(DefaultWidgetViewComponent)
};
}
public bool HideInWidgetList => false;
}Creating View Components
csharp
// Components/HomeBannerViewComponent.cs
using Microsoft.AspNetCore.Mvc;
using Nop.Web.Framework.Components;
public class HomeBannerViewComponent : NopViewComponent
{
private readonly IMyService _myService;
private readonly IStoreContext _storeContext;
public HomeBannerViewComponent(
IMyService myService,
IStoreContext storeContext)
{
_myService = myService;
_storeContext = storeContext;
}
public async Task<IViewComponentResult> InvokeAsync(string widgetZone, object additionalData)
{
var store = await _storeContext.GetCurrentStoreAsync();
var banners = await _myService.GetActiveBannersAsync(store.Id);
if (!banners.Any())
return Content(string.Empty);
var model = new BannerListModel
{
Banners = banners.Select(b => new BannerModel
{
ImageUrl = b.ImageUrl,
Title = b.Title,
Link = b.LinkUrl
}).ToList()
};
return View("~/Plugins/Widgets.MyPlugin/Views/HomeBanner.cshtml", model);
}
}Widget View
html
<!-- Views/HomeBanner.cshtml -->
@model BannerListModel
<div class="banner-widget">
<div class="banner-carousel">
@foreach (var banner in Model.Banners)
{
<div class="banner-item">
<a href="@banner.Link">
<img src="@banner.ImageUrl" alt="@banner.Title" />
<div class="banner-overlay">
<h2>@banner.Title</h2>
</div>
</a>
</div>
}
</div>
</div>
<style>
.banner-widget {
margin-bottom: 30px;
}
.banner-item img {
width: 100%;
height: auto;
border-radius: 8px;
}
.banner-overlay {
position: absolute;
bottom: 20px;
left: 20px;
color: white;
text-shadow: 0 2px 4px rgba(0,0,0,0.5);
}
</style>Public Controllers
Creating a Public Controller
csharp
// Controllers/MyPluginPublicController.cs
using Microsoft.AspNetCore.Mvc;
using Nop.Web.Framework.Controllers;
public class MyPluginPublicController : BasePublicController
{
private readonly IMyService _myService;
private readonly IWorkContext _workContext;
public MyPluginPublicController(
IMyService myService,
IWorkContext workContext)
{
_myService = myService;
_workContext = workContext;
}
public async Task<IActionResult> Index()
{
var customer = await _workContext.GetCurrentCustomerAsync();
var items = await _myService.GetCustomerItemsAsync(customer.Id);
return View("~/Plugins/Widgets.MyPlugin/Views/Public/Index.cshtml", items);
}
[HttpPost]
public async Task<IActionResult> Submit(SubmitModel model)
{
if (!ModelState.IsValid)
return View("~/Plugins/Widgets.MyPlugin/Views/Public/Index.cshtml", model);
await _myService.ProcessSubmissionAsync(model);
return RedirectToAction("ThankYou");
}
public IActionResult ThankYou()
{
return View("~/Plugins/Widgets.MyPlugin/Views/Public/ThankYou.cshtml");
}
}Custom Routes
csharp
// Infrastructure/RouteProvider.cs
using Microsoft.AspNetCore.Routing;
using Nop.Web.Framework.Mvc.Routing;
public class RouteProvider : IRouteProvider
{
public void RegisterRoutes(IEndpointRouteBuilder endpointRouteBuilder)
{
endpointRouteBuilder.MapControllerRoute(
name: "MyPlugin.Index",
pattern: "my-plugin",
defaults: new { controller = "MyPluginPublic", action = "Index" });
endpointRouteBuilder.MapControllerRoute(
name: "MyPlugin.Item",
pattern: "my-plugin/item/{id:int}",
defaults: new { controller = "MyPluginPublic", action = "Item" });
}
public int Priority => 0;
}Working with Themes
Respecting Theme Styles
html
<!-- Use nopCommerce CSS classes -->
<div class="page my-plugin-page">
<div class="page-title">
<h1>My Plugin</h1>
</div>
<div class="page-body">
<div class="product-grid">
@foreach (var item in Model.Items)
{
<div class="item-box">
<div class="product-item">
<div class="picture">
<img src="@item.ImageUrl" alt="@item.Name" />
</div>
<div class="details">
<h2 class="product-title">@item.Name</h2>
<div class="prices">
<span class="price">@item.Price</span>
</div>
<div class="buttons">
<button class="button-2 product-box-add-to-cart-button">
Add to cart
</button>
</div>
</div>
</div>
</div>
}
</div>
</div>
</div>Adding Custom CSS
csharp
// Infrastructure/PluginStartup.cs
public void Configure(IApplicationBuilder application)
{
// Register CSS file
}
// In plugin install
public override async Task InstallAsync()
{
// Add CSS to <head>
await base.InstallAsync();
}Including in Layout
html
<!-- Reference in your view -->
@section head {
<link href="~/Plugins/Widgets.MyPlugin/Content/styles.css" rel="stylesheet" />
}
@section scripts {
<script src="~/Plugins/Widgets.MyPlugin/Content/scripts.js"></script>
}AJAX Interactions
javascript
// Content/scripts.js
$(document).ready(function() {
$('.add-to-wishlist').on('click', function(e) {
e.preventDefault();
var productId = $(this).data('product-id');
$.ajax({
url: '/MyPluginPublic/AddToWishlist',
type: 'POST',
data: { productId: productId },
success: function(response) {
if (response.success) {
displayNotification(response.message, 'success');
} else {
displayNotification(response.message, 'error');
}
}
});
});
});Best Practices
- Use existing CSS classes - Match the store theme
- Support mobile - Ensure responsive design
- Lazy load content - Don't block page rendering
- Respect customer privacy - Check permissions
- Handle errors gracefully - Show user-friendly messages
Next Steps
- Events - React to store events
- Localization - Multi-language support
- Best Practices - Optimization tips