Event Consumers
Learn how to react to system events in nopCommerce using the event-driven architecture.
Overview
nopCommerce uses a publish-subscribe event model. Events are published when actions occur (orders placed, customers registered, etc.), and your plugin can subscribe to handle these events.
Creating an Event Consumer
Basic Consumer
csharp
using Nop.Core.Events;
using Nop.Services.Events;
public class OrderEventConsumer : IConsumer<OrderPlacedEvent>
{
#region Fields
private readonly ILogger<OrderEventConsumer> _logger;
private readonly IMyService _myService;
#endregion
#region Ctor
public OrderEventConsumer(
ILogger<OrderEventConsumer> logger,
IMyService myService)
{
_logger = logger;
_myService = myService;
}
#endregion
#region Methods
public async Task HandleEventAsync(OrderPlacedEvent eventMessage)
{
var order = eventMessage.Order;
_logger.LogInformation("Order {OrderId} placed for customer {CustomerId}",
order.Id, order.CustomerId);
await _myService.ProcessNewOrderAsync(order);
}
#endregion
}Registering the Consumer
Consumers are automatically discovered, but you can also register manually:
csharp
// Infrastructure/PluginStartup.cs
public void ConfigureServices(IServiceCollection services, IConfiguration configuration)
{
services.AddScoped<IConsumer<OrderPlacedEvent>, OrderEventConsumer>();
}Common Events
Order Events
| Event | Triggered When |
|---|---|
OrderPlacedEvent | Order is created |
OrderPaidEvent | Payment is confirmed |
OrderCancelledEvent | Order is cancelled |
OrderRefundedEvent | Refund processed |
ShipmentSentEvent | Shipment marked as sent |
ShipmentDeliveredEvent | Shipment delivered |
csharp
public class OrderLifecycleConsumer :
IConsumer<OrderPlacedEvent>,
IConsumer<OrderPaidEvent>,
IConsumer<OrderCancelledEvent>
{
public async Task HandleEventAsync(OrderPlacedEvent eventMessage)
{
// Handle new order
}
public async Task HandleEventAsync(OrderPaidEvent eventMessage)
{
// Handle payment confirmation
}
public async Task HandleEventAsync(OrderCancelledEvent eventMessage)
{
// Handle cancellation
}
}Customer Events
| Event | Triggered When |
|---|---|
CustomerRegisteredEvent | New customer registers |
CustomerLoggedinEvent | Customer logs in |
CustomerActivatedEvent | Account activated via email |
csharp
public class CustomerEventConsumer : IConsumer<CustomerRegisteredEvent>
{
private readonly IEmailService _emailService;
public CustomerEventConsumer(IEmailService emailService)
{
_emailService = emailService;
}
public async Task HandleEventAsync(CustomerRegisteredEvent eventMessage)
{
var customer = eventMessage.Customer;
// Send welcome email from your system
await _emailService.SendWelcomeEmailAsync(customer.Email);
}
}Product Events
| Event | Triggered When |
|---|---|
ProductSearchEvent | Customer searches products |
ProductViewedEvent | Product page viewed |
EntityInsertedEvent<Product> | Product created |
EntityUpdatedEvent<Product> | Product updated |
EntityDeletedEvent<Product> | Product deleted |
CRUD Events (Generic)
For any entity, you can subscribe to insert, update, and delete:
csharp
public class ProductChangeConsumer :
IConsumer<EntityInsertedEvent<Product>>,
IConsumer<EntityUpdatedEvent<Product>>,
IConsumer<EntityDeletedEvent<Product>>
{
private readonly ICacheManager _cacheManager;
public ProductChangeConsumer(ICacheManager cacheManager)
{
_cacheManager = cacheManager;
}
public async Task HandleEventAsync(EntityInsertedEvent<Product> eventMessage)
{
await _cacheManager.RemoveByPrefixAsync("MyPlugin.Products");
// Sync to external system
}
public async Task HandleEventAsync(EntityUpdatedEvent<Product> eventMessage)
{
var product = eventMessage.Entity;
await SyncToExternalSystemAsync(product);
}
public async Task HandleEventAsync(EntityDeletedEvent<Product> eventMessage)
{
var product = eventMessage.Entity;
await RemoveFromExternalSystemAsync(product.Id);
}
}Publishing Custom Events
Define the Event
csharp
// Events/MyCustomEvent.cs
namespace Nop.Plugin.Widgets.MyPlugin.Events;
public class MyCustomEvent
{
public MyCustomEvent(MyEntity entity, string action)
{
Entity = entity;
Action = action;
}
public MyEntity Entity { get; }
public string Action { get; }
}Publish the Event
csharp
public class MyService : IMyService
{
#region Fields
private readonly IEventPublisher _eventPublisher;
#endregion
#region Ctor
public MyService(IEventPublisher eventPublisher)
{
_eventPublisher = eventPublisher;
}
#endregion
#region Methods
public async Task ProcessAsync(MyEntity entity)
{
// Do processing...
// Publish event for other consumers
await _eventPublisher.PublishAsync(new MyCustomEvent(entity, "processed"));
}
#endregion
}Consume Custom Events
csharp
public class MyCustomEventConsumer : IConsumer<MyCustomEvent>
{
public async Task HandleEventAsync(MyCustomEvent eventMessage)
{
var entity = eventMessage.Entity;
var action = eventMessage.Action;
// Handle the event
}
}Best Practices
1. Keep Handlers Fast
csharp
// ❌ Don't block the main flow
public async Task HandleEventAsync(OrderPlacedEvent e)
{
await SendToExternalApiAsync(e.Order); // Slow!
}
// ✅ Queue for background processing
public async Task HandleEventAsync(OrderPlacedEvent e)
{
await _backgroundQueue.EnqueueAsync(e.Order.Id);
}2. Handle Exceptions Gracefully
csharp
public async Task HandleEventAsync(OrderPlacedEvent eventMessage)
{
try
{
await _myService.ProcessAsync(eventMessage.Order);
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to process order {OrderId}", eventMessage.Order.Id);
// Don't rethrow - don't break the order flow!
}
}3. Avoid Circular Events
csharp
// ❌ Can cause infinite loop
public async Task HandleEventAsync(EntityUpdatedEvent<Product> e)
{
e.Entity.Description += " Updated!";
await _productService.UpdateAsync(e.Entity); // Triggers another update event!
}
// ✅ Use flags or checks
public async Task HandleEventAsync(EntityUpdatedEvent<Product> e)
{
if (e.Entity.Description.EndsWith(" Updated!"))
return; // Already processed
// Process...
}4. Consider Event Order
Multiple consumers may handle the same event. Don't rely on execution order.
Next Steps
- Scheduled Tasks - Background processing
- Services - Dependency injection
- Best Practices - Design patterns