Services & Dependency Injection
Learn how to create and use services in your nopCommerce plugins using dependency injection.
Overview
nopCommerce uses the built-in ASP.NET Core dependency injection (DI) container. This allows you to:
- Register your own services
- Inject existing nopCommerce services
- Override default implementations
- Manage service lifetimes
Registering Services
Using INopStartup
Create a startup class implementing INopStartup:
csharp
// Infrastructure/PluginStartup.cs
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Nop.Core.Infrastructure;
namespace Nop.Plugin.Widgets.MyPlugin.Infrastructure;
public class PluginStartup : INopStartup
{
public void ConfigureServices(IServiceCollection services, IConfiguration configuration)
{
// Register your services
services.AddScoped<IMyService, MyService>();
services.AddScoped<IDataProcessor, DataProcessor>();
// Register with factory method
services.AddScoped<IComplexService>(sp =>
new ComplexService(
sp.GetRequiredService<ILogger<ComplexService>>(),
sp.GetRequiredService<ISettingService>()
));
}
public void Configure(IApplicationBuilder application)
{
// Configure middleware if needed
}
// Order of startup (lower = earlier)
public int Order => 3000;
}Startup Order
- Core nopCommerce services: 0-100
- Database services: 200-500
- Custom plugins: 1000+
Use higher numbers to ensure core services are available.
Service Lifetimes
| Lifetime | Behavior | Use Case |
|---|---|---|
AddScoped | One instance per HTTP request | Most services (recommended) |
AddTransient | New instance each time | Lightweight, stateless services |
AddSingleton | Single instance for app lifetime | Caches, configuration |
csharp
// Scoped - most common, one per request
services.AddScoped<IOrderProcessor, OrderProcessor>();
// Transient - new instance each injection
services.AddTransient<IRandomGenerator, RandomGenerator>();
// Singleton - single instance (careful with state!)
services.AddSingleton<IExternalApiClient, ExternalApiClient>();Creating a Custom Service
Step 1: Define the Interface
csharp
// Services/IMyService.cs
namespace Nop.Plugin.Widgets.MyPlugin.Services;
public interface IMyService
{
Task<IList<MyDto>> GetAllAsync();
Task<MyDto> GetByIdAsync(int id);
Task CreateAsync(MyDto dto);
Task UpdateAsync(MyDto dto);
Task DeleteAsync(int id);
}Step 2: Implement the Service
csharp
// Services/MyService.cs
using Nop.Core.Caching;
using Nop.Data;
namespace Nop.Plugin.Widgets.MyPlugin.Services;
public class MyService : IMyService
{
#region Fields
private readonly IRepository<MyEntity> _repository;
private readonly IStaticCacheManager _cacheManager;
private readonly ILogger<MyService> _logger;
#endregion
#region Ctor
public MyService(
IRepository<MyEntity> repository,
IStaticCacheManager cacheManager,
ILogger<MyService> logger)
{
_repository = repository;
_cacheManager = cacheManager;
_logger = logger;
}
#endregion
#region Methods
public async Task<IList<MyDto>> GetAllAsync()
{
var cacheKey = new CacheKey("MyPlugin.All");
return await _cacheManager.GetAsync(cacheKey, async () =>
{
var entities = await _repository.GetAllAsync(query => query);
return entities.Select(MapToDto).ToList();
});
}
public async Task<MyDto> GetByIdAsync(int id)
{
var entity = await _repository.GetByIdAsync(id);
return entity != null ? MapToDto(entity) : null;
}
public async Task CreateAsync(MyDto dto)
{
var entity = MapToEntity(dto);
await _repository.InsertAsync(entity);
await _cacheManager.RemoveByPrefixAsync("MyPlugin");
}
public async Task UpdateAsync(MyDto dto)
{
var entity = await _repository.GetByIdAsync(dto.Id);
if (entity == null) return;
entity.Name = dto.Name;
entity.UpdatedOnUtc = DateTime.UtcNow;
await _repository.UpdateAsync(entity);
await _cacheManager.RemoveByPrefixAsync("MyPlugin");
}
public async Task DeleteAsync(int id)
{
var entity = await _repository.GetByIdAsync(id);
if (entity != null)
{
await _repository.DeleteAsync(entity);
await _cacheManager.RemoveByPrefixAsync("MyPlugin");
}
}
#endregion
#region Utilities
private MyDto MapToDto(MyEntity entity) => new()
{
Id = entity.Id,
Name = entity.Name
};
private MyEntity MapToEntity(MyDto dto) => new()
{
Name = dto.Name,
CreatedOnUtc = DateTime.UtcNow
};
#endregion
}Injecting Services
In Controllers
csharp
public class MyPluginController : BasePluginController
{
#region Fields
private readonly IMyService _myService;
private readonly ISettingService _settingService;
private readonly ILocalizationService _localizationService;
#endregion
#region Ctor
public MyPluginController(
IMyService myService,
ISettingService settingService,
ILocalizationService localizationService)
{
_myService = myService;
_settingService = settingService;
_localizationService = localizationService;
}
#endregion
#region Methods
public async Task<IActionResult> Index()
{
var items = await _myService.GetAllAsync();
return View(items);
}
#endregion
}In View Components
csharp
public class MyWidgetViewComponent : NopViewComponent
{
#region Fields
private readonly IMyService _myService;
private readonly IStoreContext _storeContext;
#endregion
#region Ctor
public MyWidgetViewComponent(
IMyService myService,
IStoreContext storeContext)
{
_myService = myService;
_storeContext = storeContext;
}
#endregion
#region Methods
public async Task<IViewComponentResult> InvokeAsync()
{
var store = await _storeContext.GetCurrentStoreAsync();
var items = await _myService.GetAllAsync();
return View(new WidgetModel { Items = items, StoreName = store.Name });
}
#endregion
}Common nopCommerce Services
Core Services
| Service | Purpose |
|---|---|
IStoreContext | Current store information |
IWorkContext | Current customer/language/currency |
IWebHelper | HTTP utilities, URLs |
ILogger | Logging |
Data Services
| Service | Purpose |
|---|---|
IRepository<T> | Generic data access |
IProductService | Product operations |
ICustomerService | Customer operations |
IOrderService | Order operations |
Configuration Services
| Service | Purpose |
|---|---|
ISettingService | Plugin/store settings |
ILocalizationService | Translations |
INotificationService | Admin notifications |
Example: Using Multiple Services
csharp
public class OrderProcessor
{
#region Fields
private readonly IOrderService _orderService;
private readonly ICustomerService _customerService;
private readonly INotificationService _notificationService;
private readonly ILogger<OrderProcessor> _logger;
#endregion
#region Ctor
public OrderProcessor(
IOrderService orderService,
ICustomerService customerService,
INotificationService notificationService,
ILogger<OrderProcessor> logger)
{
_orderService = orderService;
_customerService = customerService;
_notificationService = notificationService;
_logger = logger;
}
#endregion
#region Methods
public async Task ProcessOrderAsync(int orderId)
{
try
{
var order = await _orderService.GetOrderByIdAsync(orderId);
var customer = await _customerService.GetCustomerByIdAsync(order.CustomerId);
// Process the order...
_notificationService.SuccessNotification($"Order {order.Id} processed successfully");
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to process order {OrderId}", orderId);
throw;
}
}
#endregion
}Overriding Default Services
Replace a nopCommerce service with your own implementation:
csharp
public class PluginStartup : INopStartup
{
public void ConfigureServices(IServiceCollection services, IConfiguration configuration)
{
// Override the default tax service
services.AddScoped<ITaxService, MyCustomTaxService>();
}
public int Order => 10000; // Run after default registrations
}Best Practices
- Use interfaces - Always inject interfaces, not concrete classes
- Prefer
AddScoped- Most services should be scoped to request - Avoid circular dependencies - Restructure if services depend on each other
- Use constructor injection - Avoid service locator pattern
- Cache expensive operations - Use
IStaticCacheManager
Next Steps
- Database Operations - Data access patterns
- Events - React to system events
- Configuration - Plugin settings