Skip to content

Views & ViewComponents

Learn how to organize views and create ViewComponents in your nopCommerce plugins.

View Organization

Instead of specifying explicit view paths in controllers, use ViewLocationExpander to automatically locate views. This is the recommended approach for plugin view management.

ViewLocationExpander.cs

csharp
// Infrastructure/ViewLocationExpander.cs
using Microsoft.AspNetCore.Mvc.Razor;
using Nop.Core.Infrastructure;
using Nop.Web.Framework;
using Nop.Web.Framework.Themes;

namespace NopStation.Plugin.Misc.PluginTemplate.Infrastructure;

public class ViewLocationExpander : IViewLocationExpander
{
    protected const string THEME_KEY = "nop.themename";

    public IEnumerable<string> ExpandViewLocations(
        ViewLocationExpanderContext context, 
        IEnumerable<string> viewLocations)
    {
        // Add plugin-specific view locations
        return new[] 
        {
            $"~/Plugins/NopStation.Plugin.Misc.PluginTemplate/Areas/Admin/Views/Shared/{{0}}.cshtml",
            $"~/Plugins/NopStation.Plugin.Misc.PluginTemplate/Areas/Admin/Views/{{1}}/{{0}}.cshtml"
        }.Concat(viewLocations);
    }

    public void PopulateValues(ViewLocationExpanderContext context)
    {
        // Skip theme resolution for admin area (admin is not themeable)
        if (context.AreaName?.Equals(AreaNames.ADMIN) ?? false)
            return;

        // Resolve theme for public views
        context.Values[THEME_KEY] = EngineContext.Current
            .Resolve<IThemeContext>()
            .GetWorkingThemeNameAsync().Result;
    }
}

Register in NopStartup.cs

csharp
// Infrastructure/NopStartup.cs
using Microsoft.AspNetCore.Mvc.Razor;

public class NopStartup : INopStartup
{
    public void ConfigureServices(IServiceCollection services, IConfiguration configuration)
    {
        // Register ViewLocationExpander
        services.Configure<RazorViewEngineOptions>(options =>
        {
            options.ViewLocationExpanders.Add(new ViewLocationExpander());
        });
    }

    public void Configure(IApplicationBuilder application) { }

    public int Order => 100;
}

Benefits

With ViewLocationExpander registered, you can return views by name only:

csharp
// ❌ Without ViewLocationExpander (explicit paths)
return View("~/Plugins/NopStation.Plugin.Misc.PluginTemplate/Areas/Admin/Views/Configure.cshtml", model);

// ✅ With ViewLocationExpander (automatic discovery)
return View("Configure", model);
// Or simply:
return View(model);  // Uses action name as view name

View Location Patterns

The placeholders in view paths work as follows:

  • {0} - View name (e.g., "Configure", "Index")
  • {1} - Controller name (e.g., "PluginName", "Settings")

Manual View Paths (Alternative)

If you prefer explicit control, reference views with full paths:

csharp
// In controller
return View("~/Plugins/Widgets.MyAwesome/Views/Configure.cshtml", model);

ViewComponents

ViewComponents are used to render widget content in nopCommerce. They inherit from NopViewComponent and integrate with the widget zone system.

ViewComponent Structure

Components/
└── CheckoutMessageViewComponent.cs    # ViewComponent class

Views/
└── Shared/
    └── Components/
        └── CheckoutMessage/           # Named after component (minus "ViewComponent")
            └── Default.cshtml         # Default view

Creating a ViewComponent

csharp
// Components/CheckoutMessageViewComponent.cs
using Microsoft.AspNetCore.Mvc;
using Nop.Core;
using Nop.Services.Orders;
using Nop.Web.Framework.Components;
using Nop.Web.Models.ShoppingCart;

namespace NopStation.Plugin.Misc.CheckoutMessage.Components;

public class CheckoutMessageViewComponent : NopViewComponent
{
    #region Fields

    private readonly ICheckoutMessageService _checkoutMessageService;
    private readonly IShoppingCartService _shoppingCartService;
    private readonly IStoreContext _storeContext;
    private readonly IWorkContext _workContext;

    #endregion

    #region Ctor

    public CheckoutMessageViewComponent(
        ICheckoutMessageService checkoutMessageService,
        IShoppingCartService shoppingCartService,
        IStoreContext storeContext,
        IWorkContext workContext)
    {
        _checkoutMessageService = checkoutMessageService;
        _shoppingCartService = shoppingCartService;
        _storeContext = storeContext;
        _workContext = workContext;
    }

    #endregion

    #region Methods

    public async Task<IViewComponentResult> InvokeAsync(string widgetZone, object additionalData)
    {
        // Validate additional data passed from widget zone
        if (!(additionalData is ShoppingCartModel shoppingCartModel))
            return Content("");

        var customer = await _workContext.GetCurrentCustomerAsync();
        var store = await _storeContext.GetCurrentStoreAsync();

        var cart = await _shoppingCartService.GetShoppingCartAsync(
            customer, 
            ShoppingCartType.ShoppingCart, 
            store.Id);

        var model = await _checkoutMessageService.GetCheckoutMessageAsync(customer, cart);

        // Return empty content if not visible
        if (!model.IsVisible)
            return Content("");

        // Uses Views/Shared/Components/CheckoutMessage/Default.cshtml
        return View(model);
    }

    #endregion
}

ViewComponent View Location

Views are automatically located based on the component name:

Component ClassView Path
CheckoutMessageViewComponentViews/Shared/Components/CheckoutMessage/Default.cshtml
CustomWidgetViewComponentViews/Shared/Components/CustomWidget/Default.cshtml

Naming Convention

Remove the ViewComponent suffix from the class name to get the view folder name.

Registering with Widget Zones

In your main Plugin.cs, implement IWidgetPlugin:

csharp
public class Plugin : BasePlugin, IWidgetPlugin
{
    public bool HideInWidgetList => false;

    public Task<IList<string>> GetWidgetZonesAsync()
    {
        return Task.FromResult<IList<string>>(new List<string>
        {
            PublicWidgetZones.OrderSummaryContentBefore,
            PublicWidgetZones.CheckoutConfirmTop
        });
    }

    public Type GetWidgetViewComponent(string widgetZone)
    {
        return typeof(CheckoutMessageViewComponent);
    }
}

View Files Required

Ensure these files exist in Views/:

Views/
├── _ViewImports.cshtml    # Tag helpers and namespaces
├── _ViewStart.cshtml      # Layout configuration (optional)
└── Shared/
    └── Components/
        └── {ComponentName}/   # Without "ViewComponent" suffix
            └── Default.cshtml # Default view name

Sample Default.cshtml

html
@model CheckoutMessageModel

@if (Model.IsVisible)
{
    <div class="checkout-message">
        <p>@Model.Message</p>
    </div>
}

Next Steps

Released under the nopCommerce Public License.