Plugin Architecture
Understanding the nopCommerce plugin architecture is essential for building well-integrated extensions.
Architecture Overview
┌─────────────────────────────────────────────────────────────┐
│ Nop.Web (Presentation) │
│ ┌─────────────────────────────────────────────────────────┐│
│ │ Nop.Web.Framework ││
│ │ ┌───────────────┐ ┌───────────────┐ ┌─────────────┐ ││
│ │ │ Your Plugin │ │ Other Plugins │ │ Widgets │ ││
│ │ └───────────────┘ └───────────────┘ └─────────────┘ ││
│ └─────────────────────────────────────────────────────────┘│
├─────────────────────────────────────────────────────────────┤
│ Nop.Services (Business Logic) │
├─────────────────────────────────────────────────────────────┤
│ Nop.Data (Data Access) │
├─────────────────────────────────────────────────────────────┤
│ Nop.Core (Domain Models) │
└─────────────────────────────────────────────────────────────┘Core Libraries
Nop.Core
The foundation layer containing:
- Domain entities (Product, Customer, Order, etc.)
- Interfaces for core functionality
- Configuration classes
- Caching abstractions
- Common utilities
Nop.Data
Data access layer providing:
- Entity Framework Core DbContext
- Repository pattern implementation
- Database migrations
- Data providers (SQL Server, MySQL, PostgreSQL)
Nop.Services
Business logic layer with:
- Service interfaces and implementations
- Event publishing/handling
- Caching strategies
- Logging and auditing
Nop.Web.Framework
Web-specific functionality:
- Base controllers
- Action filters
- Tag helpers
- MVC infrastructure
Virtual Service Methods (New in 4.90)
nopCommerce 4.90 introduces virtual service methods for cleaner extensibility. This allows you to override specific service behaviors without replacing entire service implementations.
Why Virtual Methods?
Virtual methods reduce boilerplate code and make customizations:
- Less error-prone - Override only what you need
- More maintainable - Smaller, focused changes
- Upgrade-friendly - Core updates won't break your overrides
Overriding a Service Method
csharp
// Override specific behavior in a core service
public class CustomProductService : ProductService
{
public CustomProductService(
ICacheKeyService cacheKeyService,
IRepository<Product> productRepository,
// ... other dependencies
) : base(cacheKeyService, productRepository /* ... */)
{
}
// Override just the method you need to customize
public override async Task<IList<Product>> GetProductsByIdsAsync(int[] productIds)
{
// Add custom logic before
var products = await base.GetProductsByIdsAsync(productIds);
// Add custom logic after
return products;
}
}Registering the Override
csharp
public class PluginStartup : INopStartup
{
public void ConfigureServices(IServiceCollection services, IConfiguration configuration)
{
// Replace the default service with your custom implementation
services.AddScoped<IProductService, CustomProductService>();
}
public int Order => 3000; // Higher than core registration
}Plugin Discovery
nopCommerce discovers plugins at application startup:
csharp
// 1. Scans /Plugins directory for plugin.json files
// 2. Loads plugin assemblies dynamically
// 3. Registers plugin services via DI
// 4. Initializes plugin databases (if any)plugin.json Structure
json
{
"Group": "Payment methods",
"FriendlyName": "My Payment Plugin",
"SystemName": "MyCompany.MyPaymentPlugin",
"Version": "1.0.0",
"SupportedVersions": ["4.90"],
"Author": "Your Company",
"DisplayOrder": 1,
"FileName": "MyCompany.MyPaymentPlugin.dll",
"Description": "A custom payment plugin"
}Dependency Injection
Plugins register services using the INopStartup interface:
csharp
public class PluginStartup : INopStartup
{
public void ConfigureServices(IServiceCollection services, IConfiguration configuration)
{
// Register your services
services.AddScoped<IMyService, MyService>();
}
public void Configure(IApplicationBuilder application)
{
// Configure middleware if needed
}
// Lower numbers execute first
public int Order => 3000;
}Service Lifetimes
AddScoped- Created once per request (most common)AddTransient- Created each time requestedAddSingleton- Single instance for application lifetime
Plugin Base Classes
BasePlugin
The foundation for all plugins:
csharp
public class MyPlugin : BasePlugin
{
public override string GetConfigurationPageUrl()
{
return "/Admin/MyPlugin/Configure";
}
public override async Task InstallAsync()
{
// Add settings, locale resources, etc.
await base.InstallAsync();
}
public override async Task UninstallAsync()
{
// Remove settings, locale resources, etc.
await base.UninstallAsync();
}
}Specialized Base Classes
| Base Class | Purpose |
|---|---|
BasePlugin | Standard plugins |
IPaymentMethod | Payment processing |
IShippingRateComputationMethod | Shipping calculation |
ITaxProvider | Tax calculation |
IWidgetPlugin | Widget rendering |
IExternalAuthenticationMethod | OAuth/External auth |
Event System
nopCommerce uses a publish-subscribe event model:
csharp
// Subscribe to events
public class MyEventConsumer : IConsumer<OrderPlacedEvent>
{
public async Task HandleEventAsync(OrderPlacedEvent eventMessage)
{
var order = eventMessage.Order;
// Process the order event
}
}
// Events are published automatically for CRUD operations
// Or manually via IEventPublisherWidget Zones
Plugins can render content in predefined zones:
csharp
public class MyWidgetPlugin : BasePlugin, IWidgetPlugin
{
public Task<IList<string>> GetWidgetZonesAsync()
{
return Task.FromResult<IList<string>>(new List<string>
{
PublicWidgetZones.HomepageTop,
PublicWidgetZones.ProductDetailsTop
});
}
public Type GetWidgetViewComponent(string widgetZone)
{
return typeof(MyWidgetViewComponent);
}
}Next Steps
- Creating a Plugin - Build your first plugin
- Plugin Structure - Understand file organization
- Services & DI - Master dependency injection