Troubleshoot and fix the most common nopCommerce plugin development errors across versions 4.30 through 4.70+. This guide covers breaking API changes, dependency injection failures, database migration exceptions, and the exact error messages you will encounter during plugin development and version upgrades.
ILocalizationService Breaking Changes
AddOrUpdateLocaleResourceAsync Removed in 4.50+
The most frequently encountered breaking change when upgrading nopCommerce plugins is the removal of AddOrUpdateLocaleResourceAsync from ILocalizationService. This method was the standard way to register locale resources during plugin installation in nopCommerce 4.40 and earlier.
Error Message:
'ILocalizationService' does not contain a definition for 'AddOrUpdateLocaleResourceAsync'
and no accessible extension method 'AddOrUpdateLocaleResourceAsync' accepting a first argument
of type 'ILocalizationService' could be found
Why This Happened:
In nopCommerce 4.50, the localization subsystem was refactored. Locale resource management was separated from ILocalizationService into ILocaleResourceService. The bulk resource methods were also restructured to reduce database round-trips during plugin installation.
Migration Path: 4.40 to 4.50+
BEFORE (nopCommerce 4.40 and earlier -- broken in 4.50+):
public class MyPlugin : BasePlugin
{
private readonly ILocalizationService _localizationService;
public MyPlugin(ILocalizationService localizationService)
{
_localizationService = localizationService;
}
public override async Task InstallAsync()
{
// THIS NO LONGER WORKS IN 4.50+
await _localizationService.AddOrUpdateLocaleResourceAsync(new Dictionary<string, string>
{
["Plugins.MyPlugin.Title"] = "My Plugin",
["Plugins.MyPlugin.Description"] = "Plugin description",
["Plugins.MyPlugin.Settings.ApiKey"] = "API Key",
["Plugins.MyPlugin.Settings.Enabled"] = "Enable Plugin"
});
await base.InstallAsync();
}
public override async Task UninstallAsync()
{
// THIS ALSO BREAKS IN 4.50+
await _localizationService.DeleteLocaleResourcesAsync("Plugins.MyPlugin");
await base.UninstallAsync();
}
}
AFTER (nopCommerce 4.50+):
public class MyPlugin : BasePlugin
{
private readonly ILocaleResourceService _localeResourceService;
private readonly ILanguageService _languageService;
public MyPlugin(
ILocaleResourceService localeResourceService,
ILanguageService languageService)
{
_localeResourceService = localeResourceService;
_languageService = languageService;
}
public override async Task InstallAsync()
{
// 4.50+ approach: use ILocaleResourceService
var languages = await _languageService.GetAllLanguagesAsync(true);
var resources = new List<(string Name, string Value)>
{
("Plugins.MyPlugin.Title", "My Plugin"),
("Plugins.MyPlugin.Description", "Plugin description"),
("Plugins.MyPlugin.Settings.ApiKey", "API Key"),
("Plugins.MyPlugin.Settings.Enabled", "Enable Plugin")
};
foreach (var language in languages)
{
foreach (var (name, value) in resources)
{
var resource = await _localeResourceService
.GetLocaleResourceByNameAsync(name, language.Id, false);
if (resource == null)
{
await _localeResourceService.InsertLocaleResourceAsync(
new LocaleStringResource
{
LanguageId = language.Id,
ResourceName = name,
ResourceValue = value
});
}
else
{
resource.ResourceValue = value;
await _localeResourceService.UpdateLocaleResourceAsync(resource);
}
}
}
await base.InstallAsync();
}
public override async Task UninstallAsync()
{
// 4.50+ approach to removing resources
var languages = await _languageService.GetAllLanguagesAsync(true);
foreach (var language in languages)
{
var resources = (await _localeResourceService
.GetAllResourceValuesAsync(language.Id, null))
.Where(r => r.Key.StartsWith("Plugins.MyPlugin"))
.ToList();
foreach (var resource in resources)
{
var localeResource = await _localeResourceService
.GetLocaleResourceByNameAsync(resource.Key, language.Id);
if (localeResource != null)
await _localeResourceService.DeleteLocaleResourceAsync(localeResource);
}
}
await base.UninstallAsync();
}
}
Extension Method Approach (4.50+)
If you prefer a cleaner pattern similar to the old API, create an extension method:
public static class LocaleResourceExtensions
{
public static async Task AddOrUpdatePluginResourcesAsync(
this ILocaleResourceService localeResourceService,
ILanguageService languageService,
Dictionary<string, string> resources)
{
var languages = await languageService.GetAllLanguagesAsync(true);
foreach (var language in languages)
{
foreach (var kvp in resources)
{
var existing = await localeResourceService
.GetLocaleResourceByNameAsync(kvp.Key, language.Id, false);
if (existing == null)
{
await localeResourceService.InsertLocaleResourceAsync(
new LocaleStringResource
{
LanguageId = language.Id,
ResourceName = kvp.Key,
ResourceValue = kvp.Value
});
}
else
{
existing.ResourceValue = kvp.Value;
await localeResourceService.UpdateLocaleResourceAsync(existing);
}
}
}
}
public static async Task DeletePluginResourcesAsync(
this ILocaleResourceService localeResourceService,
ILanguageService languageService,
string prefix)
{
var languages = await languageService.GetAllLanguagesAsync(true);
foreach (var language in languages)
{
var allResources = await localeResourceService
.GetAllResourceValuesAsync(language.Id, null);
foreach (var resource in allResources
.Where(r => r.Key.StartsWith(prefix)))
{
var localeResource = await localeResourceService
.GetLocaleResourceByNameAsync(resource.Key, language.Id);
if (localeResource != null)
await localeResourceService.DeleteLocaleResourceAsync(localeResource);
}
}
}
}
Usage with extension methods:
public override async Task InstallAsync()
{
await _localeResourceService.AddOrUpdatePluginResourcesAsync(
_languageService,
new Dictionary<string, string>
{
["Plugins.MyPlugin.Title"] = "My Plugin",
["Plugins.MyPlugin.Settings.ApiKey"] = "API Key"
});
await base.InstallAsync();
}
Version-Specific Localization API Changes
nopCommerce 4.50 to 4.60:
In 4.60, GetAllResourceValuesAsync changed its signature. The loadPublicLocales parameter was removed:
// 4.50
var resources = await _localeResourceService.GetAllResourceValuesAsync(languageId, loadPublicLocales: null);
// 4.60+ -- parameter removed
var resources = await _localeResourceService.GetAllResourceValuesAsync(languageId);
nopCommerce 4.60 to 4.70:
In 4.70, the caching mechanism for locale resources changed. If you were manually interacting with the locale resource cache, you need to update:
// 4.60
await _staticCacheManager.RemoveByPrefixAsync(NopLocalizationDefaults.LocaleStringResourcesAllPrefix);
// 4.70+ -- cache key structure changed
await _staticCacheManager.RemoveByPrefixAsync(
NopLocalizationDefaults.LocaleStringResourcesAllCacheKey.Prefix);
Plugin Lifecycle Errors
InstallAsync Failures
Error: Plugin install hangs or times out
System.OperationCanceledException: The operation was canceled.
at Nop.Services.Plugins.PluginService.InstallPluginAsync(PluginDescriptor pluginDescriptor)
Common causes:
- Long-running database operations in
InstallAsyncwithout cancellation token support - Circular dependency in DI container during install
- Missing database table that migration should have created
Fix -- add cancellation token support:
public override async Task InstallAsync()
{
// Break large resource inserts into batches
var resources = GetAllResources();
var batchSize = 50;
for (int i = 0; i < resources.Count; i += batchSize)
{
var batch = resources.Skip(i).Take(batchSize);
foreach (var resource in batch)
{
await _localeResourceService.InsertLocaleResourceAsync(resource);
}
}
await base.InstallAsync();
}
UninstallAsync Failures
Error: Cannot uninstall plugin -- foreign key constraint
Microsoft.Data.SqlClient.SqlException: The DELETE statement conflicted with the
REFERENCE constraint "FK_MyPlugin_Table_Customer". The conflict occurred in database
"nopCommerce", table "dbo.MyPlugin_Records", column 'CustomerId'.
Fix -- clean up plugin data before uninstall:
public override async Task UninstallAsync()
{
// Delete plugin data BEFORE removing schema
var repository = EngineContext.Current.Resolve<IRepository<MyPluginRecord>>();
await repository.TruncateAsync();
// Now remove locale resources
// ... resource cleanup ...
// Remove settings
await _settingService.DeleteSettingAsync<MyPluginSettings>();
await base.UninstallAsync();
}
PluginDescriptor Version Mismatch
Error: Plugin is not compatible with the current version of nopCommerce
Plugin 'MyPlugin' is not compatible with the current version of nopCommerce.
Supported versions: 4.40. Current version: 4.60.
Fix -- update plugin.json:
{
"Group": "Widgets",
"FriendlyName": "My Plugin",
"SystemName": "Widgets.MyPlugin",
"Version": "2.00",
"SupportedVersions": [ "4.60", "4.70" ],
"Author": "Your Name",
"DisplayOrder": 1,
"FileName": "Nop.Plugin.Widgets.MyPlugin.dll",
"Description": "My plugin description",
"LimitedToStores": [],
"LimitedToCustomerRoles": [],
"DependsOnSystemNames": []
}
Important: The SupportedVersions array must include the exact major.minor version of the nopCommerce instance. "4.60" will not match a 4.70 instance.
Dependency Injection Errors
Unable to Resolve Service
Error:
System.InvalidOperationException: Unable to resolve service for type
'Nop.Plugin.Widgets.MyPlugin.Services.IMyCustomService' while attempting to activate
'Nop.Plugin.Widgets.MyPlugin.Controllers.MyPluginController'.
Cause: Service not registered in the DI container.
Fix -- create or update DependencyRegistrar:
using Microsoft.Extensions.DependencyInjection;
using Nop.Core.Configuration;
using Nop.Core.Infrastructure;
using Nop.Core.Infrastructure.DependencyManagement;
namespace Nop.Plugin.Widgets.MyPlugin.Infrastructure
{
public class DependencyRegistrar : IDependencyRegistrar
{
public int Order => 1;
public void Register(
IServiceCollection services,
ITypeFinder typeFinder,
AppSettings appSettings)
{
services.AddScoped<IMyCustomService, MyCustomService>();
services.AddScoped<IMyPluginRepository, MyPluginRepository>();
// If you need to register a factory
services.AddScoped<IMyServiceFactory>(provider =>
new MyServiceFactory(
provider.GetRequiredService<IStaticCacheManager>(),
provider.GetRequiredService<ILogger>()));
}
}
}
Missing INopStartup Implementation
Error: Plugin routes not registering or middleware not loading
// No explicit error, but plugin endpoints return 404
GET /MyPlugin/Configure -> 404 Not Found
Fix -- implement INopStartup:
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Nop.Core.Infrastructure;
namespace Nop.Plugin.Widgets.MyPlugin.Infrastructure
{
public class NopStartup : INopStartup
{
public int Order => 100;
public void ConfigureServices(
IServiceCollection services,
IConfiguration configuration)
{
// Register services here as alternative to DependencyRegistrar
services.AddScoped<IMyCustomService, MyCustomService>();
}
public void Configure(IApplicationBuilder application)
{
// Configure middleware or routes
}
}
}
Common DI Registration Mistakes
Mistake 1: Registering as wrong lifetime
// WRONG: Singleton holds DbContext which is Scoped
services.AddSingleton<IMyService, MyService>();
// RIGHT: Match or use shorter lifetime than dependencies
services.AddScoped<IMyService, MyService>();
Mistake 2: Forgetting to register the interface mapping
// WRONG: Only registers concrete type
services.AddScoped<MyService>();
// RIGHT: Register interface-to-implementation mapping
services.AddScoped<IMyService, MyService>();
Mistake 3: Registering in wrong class for nopCommerce version
// nopCommerce 4.40 and earlier: use Autofac ContainerBuilder in DependencyRegistrar
public void Register(ContainerBuilder builder, ITypeFinder typeFinder, NopConfig config)
{
builder.RegisterType<MyService>().As<IMyService>().InstancePerLifetimeScope();
}
// nopCommerce 4.50+: use IServiceCollection in DependencyRegistrar
public void Register(IServiceCollection services, ITypeFinder typeFinder, AppSettings appSettings)
{
services.AddScoped<IMyService, MyService>();
}
The switch from Autofac to the built-in Microsoft DI container in 4.50 is a major migration point. If your DependencyRegistrar still references ContainerBuilder, it will not compile.
Database Migration Errors
FluentMigrator Exceptions
Error: Migration failed during plugin install
FluentMigrator.Runner.Exceptions.MissingMigrationsException:
No migrations found in assemblies 'Nop.Plugin.Widgets.MyPlugin'
Fix -- ensure your migration class is properly decorated:
using FluentMigrator;
using Nop.Data.Migrations;
namespace Nop.Plugin.Widgets.MyPlugin.Data
{
[NopMigration("2024-01-15 12:00:00", "Widgets.MyPlugin base schema",
MigrationProcessType.Installation)]
public class SchemaMigration : AutoReversingMigration
{
public override void Up()
{
Create.TableFor<MyPluginRecord>();
}
}
}
Common migration pitfalls:
- Missing
[NopMigration]attribute (nopCommerce uses this instead of FluentMigrator's[Migration]) - Wrong
MigrationProcessType-- useInstallationfor plugin install,Updatefor upgrades - Timestamp format must be parseable as
DateTime
Schema Mismatch After Failed Migration
Error:
Microsoft.Data.SqlClient.SqlException: There is already an object named
'MyPlugin_Records' in the database.
This happens when a migration partially ran (created the table) but then failed on a subsequent step. The migration is not recorded as complete in MigrationVersionInfo, so it tries to run again.
Fix -- manual cleanup:
-- Check what migrations have been recorded
SELECT * FROM [MigrationVersionInfo]
WHERE [Description] LIKE '%MyPlugin%'
ORDER BY [AppliedOn] DESC;
-- If table exists but migration is not recorded, insert the record manually
INSERT INTO [MigrationVersionInfo] ([Version], [AppliedOn], [Description])
VALUES (637123456789, GETUTCDATE(), 'Widgets.MyPlugin base schema');
-- OR drop the partially-created table and re-run the migration
DROP TABLE IF EXISTS [MyPlugin_Records];
Rolling Back Failed Migrations
nopCommerce does not provide a built-in migration rollback UI. To roll back:
-- 1. Identify the failed migration
SELECT TOP 5 * FROM [MigrationVersionInfo]
ORDER BY [AppliedOn] DESC;
-- 2. Remove the migration record
DELETE FROM [MigrationVersionInfo]
WHERE [Description] = 'Widgets.MyPlugin base schema';
-- 3. Manually reverse the schema changes
DROP TABLE IF EXISTS [MyPlugin_Records];
DROP TABLE IF EXISTS [MyPlugin_Settings];
-- 4. Restart the application and retry installation
For code-based rollback, implement IReversibleMigration:
[NopMigration("2024-01-15 12:00:00", "Widgets.MyPlugin base schema",
MigrationProcessType.Installation)]
public class SchemaMigration : Migration
{
public override void Up()
{
Create.TableFor<MyPluginRecord>();
}
public override void Down()
{
Delete.Table(nameof(MyPluginRecord));
}
}
Entity Builder Pattern (4.50+)
In nopCommerce 4.50+, the preferred approach for table creation uses NopEntityBuilder:
using FluentMigrator.Builders.Create.Table;
using Nop.Data.Mapping.Builders;
namespace Nop.Plugin.Widgets.MyPlugin.Data
{
public class MyPluginRecordBuilder : NopEntityBuilder<MyPluginRecord>
{
public override void MapEntity(CreateTableExpressionBuilder table)
{
table
.WithColumn(nameof(MyPluginRecord.Name))
.AsString(400).NotNullable()
.WithColumn(nameof(MyPluginRecord.ConfigValue))
.AsString(int.MaxValue).Nullable()
.WithColumn(nameof(MyPluginRecord.StoreId))
.AsInt32().NotNullable()
.ForeignKey<Store>(onDelete: System.Data.Rule.Cascade);
}
}
}
Version Upgrade Breaking Changes
| Version Transition | Breaking Change | Migration Path |
|---|---|---|
| 4.30 to 4.40 | IPlugin.Install() / Uninstall() became async |
Change signatures to Task InstallAsync() / Task UninstallAsync() and add await to all calls |
| 4.30 to 4.40 | PluginDescriptor.SupportedVersions added |
Add SupportedVersions array to plugin.json |
| 4.40 to 4.50 | Autofac replaced with Microsoft DI | Rewrite DependencyRegistrar to use IServiceCollection instead of ContainerBuilder |
| 4.40 to 4.50 | ILocalizationService.AddOrUpdateLocaleResourceAsync removed |
Use ILocaleResourceService with manual insert/update pattern |
| 4.40 to 4.50 | NopConfig replaced with AppSettings |
Update all references in DI registrars and services |
| 4.40 to 4.50 | IRepository<T>.Table property removed |
Use IRepository<T>.GetAllAsync() or Table via IRepository<T>.GetAllAsync(query => query) |
| 4.50 to 4.60 | GetAllResourceValuesAsync signature change |
Remove loadPublicLocales parameter |
| 4.50 to 4.60 | IPermissionService.InstallPermissionsAsync signature change |
Update IPermissionProvider implementation to match new interface |
| 4.60 to 4.70 | Cache key structure reorganized | Update NopLocalizationDefaults references to use new .Prefix property |
| 4.60 to 4.70 | BaseEntity.Id setter visibility changed |
Use constructor or mapped property for setting entity IDs |
| 4.60 to 4.70 | IScheduleTaskService renamed methods |
Update GetTaskByTypeAsync to GetTaskByTypeAsync (parameter change from string to Type) |
| 4.70+ | IWorkContext.WorkingLanguageAsync property change |
Use await _workContext.GetWorkingLanguageAsync() method instead of property |
Common Exception Messages Reference
| Error Message | Cause | Fix |
|---|---|---|
'ILocalizationService' does not contain a definition for 'AddOrUpdateLocaleResourceAsync' |
API removed in nopCommerce 4.50 | Switch to ILocaleResourceService with manual insert/update logic |
Unable to resolve service for type 'ILocalizationService' |
Wrong service injected or not registered | Verify DI registration in DependencyRegistrar; check if you need ILocaleResourceService instead |
Plugin is not compatible with the current version of nopCommerce |
SupportedVersions in plugin.json does not include current version |
Update plugin.json to include target nopCommerce version |
System.InvalidOperationException: Unable to resolve service for type |
Service not registered in DI container | Add registration in DependencyRegistrar or INopStartup |
No migrations found in assemblies |
Missing [NopMigration] attribute on migration class |
Add [NopMigration] attribute with timestamp and MigrationProcessType |
There is already an object named 'X' in the database |
Partially-failed migration left orphan tables | Manually drop the table or insert migration record into MigrationVersionInfo |
ContainerBuilder does not exist in the current context |
Plugin still uses Autofac API from pre-4.50 | Rewrite DependencyRegistrar to use IServiceCollection |
The DELETE statement conflicted with the REFERENCE constraint |
Foreign key prevents plugin data deletion during uninstall | Delete dependent records before calling base.UninstallAsync() |
Could not load file or assembly 'Nop.Plugin.X' |
Plugin DLL not found or dependency missing | Verify build output copies to Plugins/ directory; check all NuGet dependencies |
NullReferenceException in PluginService.PreparePluginToInstall |
Malformed plugin.json or missing required fields |
Validate JSON structure and ensure all required fields are present |
System.TypeLoadException: Method 'InstallAsync' does not have an implementation |
Plugin interface changed between versions | Rebuild plugin against the target nopCommerce version NuGet packages |
MissingMethodException: Method not found 'Void Nop.Services.Localization.ILocalizationService.DeleteLocaleResources' |
Non-async version removed | Switch to async DeleteLocaleResourceAsync on ILocaleResourceService |
InvalidOperationException: Cannot resolve scoped service from root provider |
Singleton service depends on scoped service | Change service lifetime to Scoped or use IServiceScopeFactory |
Debugging Plugin Errors
Enable Detailed Errors
// appsettings.json
{
"Hosting": {
"UseDetailedErrors": true
},
"Logging": {
"LogLevel": {
"Default": "Debug",
"Nop": "Trace",
"FluentMigrator": "Debug"
}
}
}
Check Plugin Load Errors in Admin
Administration > System > Log
Filter by:
- Log level: Error
- Message: Search for your plugin system name
- Date range: Time of last install attempt
Verify Plugin Assembly Loading
// Temporary diagnostic code in your plugin's InstallAsync
public override async Task InstallAsync()
{
var assembly = typeof(MyPlugin).Assembly;
var logger = EngineContext.Current.Resolve<ILogger>();
await logger.InformationAsync(
$"Plugin loading: {assembly.FullName}, Location: {assembly.Location}");
await base.InstallAsync();
}
Related Resources
- nopCommerce Troubleshooting Overview - General nopCommerce troubleshooting
- Events Not Firing - Tracking event debugging
- CLS Issues - Layout shift fixes
- LCP Issues - Loading performance fixes
- GTM Setup - Tag Manager configuration