• Home
  • Services
    • Retail & eCommerce
      • .NET eCommerce
      • Multichannel eCommerce
      • Mobile Apps for eCommerce
      • eCommerce Chatbots
      • Dynamics365 Integration
    • SharePoint Portal
      • Document flow
      • Request approval services
      • Staff directory and orgchart
      • Helpdesk
      • HR-services
      • Reporting and documentation
  • Projects
  • Customers and partners
  • Blog
  • Contact us
ISDK - eCommerce development, omnichannel solutions, CRM-marketing
  • EN
    • EN
    • DE
    • NL
  • Home
  • Services
    • Retail & eCommerce
      • .NET eCommerce
      • Multichannel eCommerce
      • Mobile Apps for eCommerce
      • eCommerce Chatbots
      • Dynamics365 Integration
    • SharePoint Portal
      • Document flow
      • Request approval services
      • Staff directory and orgchart
      • Helpdesk
      • HR-services
      • Reporting and documentation
  • Projects
  • Customers and partners
  • Blog
  • Contact us
plugin developmentplugin development

nopCommerce plugin development manual

This guide covers various aspects, best practices and approaches for nopCommerce plugin development.

Plugin is a software component enabling nopCommerce basic features extension. There is an official nopCommerce plugin marketplace distributing both free and paid plugins.

Plugins enable developers to add new features and/or customize a UI theme without modifying nopCommerce source code. This is important for stable running of a web application and for upgrading nopCommerce platform to newer versions.

This manual introduces best practices in code organization and plugin development based on eCommerce expertise of ISDK.

Official nopCommerce plugin development documentation can be found here.

nopCommerce source code containing several free plugins included in the distribution package can be found here.

Contents hide
1 Requirements
2 Creating a project
3 Logging
3.1 Dependency injection
3.2 Plugin interface and base class
4 User-defined plugin settings
5 Localization
6 Custom entities and database tables
6.1 Defining new entities
6.2 Implementing database context
6.3 Mapping entities to database tables
6.4 Services for new entities
7 Scheduled tasks
7.1 Implement IScheduleTask
7.2 Managing tasks in the admin area
8 Events subscription
9 Widgets
10 Generic attributes
11 Method overriding

Requirements

Plugin development for nopCommerce would require prior experience with Entity Framework Core 2 and ASP.NET Core MVC 2, understanding of DI, IoC and AOP, as well as familiarity with features and design of the nopCommerce platform.

Creating a project

A Class Library project template is used for plugin development. It is recommended to store a new project in \Plugins solution folder.

Naming conventions for plugin projects are the following: Nop.Plugin.{Group}.{Name}. e.g. Nop.Plugin. Payments.PayPalStandard. It is also common to use the following naming template: {Solution namespace}.{Name}Plugin. e.g. Company.Solution.PaymentMethodNamePlugin

After creating a project, you should edit *.csproj file by adding PropertyGroup element to indicate the project’s output path and Target.

Example of *.csproj file: value if PLUGIN_OUTPUT_DIRECTORY should match plugin’s name, e.g. Payments.PayPalStandard.

LISTING 1. PLUGIN *.CSPROJ FILE

<Project Sdk="Microsoft.NET.Sdk"> 
 <PropertyGroup> 
   <TargetFramework>netcoreapp2.1</TargetFramework> 
 </PropertyGroup> 
  
 <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'"> 
   <OutputPath>..\..\Presentation\Nop.Web\Plugins\PLUGIN_OUTPUT_DIRECTORY</OutputPath> 
   <OutDir>$(OutputPath)</OutDir> 
 </PropertyGroup> 
 <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> 
   <OutputPath>..\..\Presentation\Nop.Web\Plugins\PLUGIN_OUTPUT_DIRECTORY</OutputPath> 
   <OutDir>$(OutputPath)</OutDir> 
 </PropertyGroup> 
 <!-- This target execute after "Build" target --> 
 <Target Name="NopTarget" AfterTargets="Build"> 
   <!-- Delete unnecessary libraries from plugins path --> 
   <MSBuild Projects="$(MSBuildProjectDirectory)\..\..\Build\ClearPluginAssemblies.proj" Properties="PluginPath=$(MSBuildProjectDirectory)\$(OutDir)" Targets="NopClear" /> 
 </Target>   
</Project> 

Next, create a plugin.json file in the root of your project to describe the plugin.

Note: I this manual we consider nopCommerce 4.10. nopCommerce 3.90 and below have description.txt file with a different format.

LISTING 2. PLUGIN.JSON EXAMPLE

{ 
 "Group": "Payment methods", 
 "FriendlyName": "PayPal Standard", 
 "SystemName": "Payments.PayPalStandard", 
 "Version": "1.49", 
 "SupportedVersions": [ "4.10" ], 
 "Author": "nopCommerce team", 
 "DisplayOrder": 1, 
 "FileName": "Nop.Plugin.Payments.PayPalStandard.dll", 
 "Description": "This plugin allows paying with PayPal Standard" 
}

In the file properties specify value Copy if newer for property Copy to Output Directory since it’s necessary to copy this file to /Plugins folder of the web-application (Nop.Web) after it is built.

SystemName should be unique. FileName should match a class library name.

Group property can have any value; however, it is recommended to use one of the following values reflecting the purpose of the plugin:

  • DiscountRules – rules for applying discounts.
  • ExchangeRates – currency exchange rate providers.
  • ExternalAuth – external authentication providers.
  • Payments – payment gateway integrations.
  • Pickup – pickup point providers.
  • Shipping – shipping cost calculation and tracking providers.
  • Tax – tax calculation providers.
  • Widgets – widgets.
  • Misc – any other plugins that can’t be associated with one of the above categories.

Plugins can be viewed, installed, configured or uninstalled in the respective admin section:

nopCommerce plugin development

FIG 1. LIST OF PLUGINS

Standard settings of an installed plugin can be edited by clicking Edit button.

nopCommerce plugin development

FIG 2. STANDARD PLUGIN SETTINGS

 

nopCommerce plugin development

FIG 3. USER-DEFINED PLUGIN SETTINGS

If a plugin has user-defined settings unique to this plugin (see section User-defined plugin settings), the Configure button is available for editing these settings.

Logging

While developing controllers, services, and tasks, it is recommended to use an inbuilt logging service Nop.Services.Logging.ILogger to log information and error messages.

LISTING 3. LOGGING INFORMATION MESSAGE

//log information about the successful renew of the access token 
_logger.Information(_localizationService.GetResource("Plugins.Payments.Square.RenewAccessToken.Success"));

During exception handling, exception object can also be passed to the logging service:

LISTING 4. LOGGING EXCEPTION

catch (Exception exception) 
{ 
   //log error on renewing of the access token 
   _logger.Error(_localizationService.GetResource("Plugins.Payments.Square.RenewAccessToken .Error"), exception); 
}

Log is stored in the database and is displayed in a respective admin section:

nopCommerce plugin development

FIG 4. LOG ADMIN SECTION

Dependency injection

nopCommerce uses Autofac as an inversion of a control container. When classes are being developed, all required service interfaces are defined as constructor parameters and their instances are automatically resolved and initialized during the respective class instance initializing.

To register classes by implementing respective service interfaces you should create an implementation of Nop.Core.Infrastructure.DependencyManagement.IDependencyRegistrar. nopCommerce scans through all assemblies to find classes implementing a respective interface and registers them.

Plugin interface and base class

Plugin should contain an implementation of Nop.Core.Plugins.IPlugin. Abstract class Nop.Core.Plugins.BasePlugin which implements this interface can be used as a base class.

For some specific type of a plugin a respective derived interface that corresponds to the plugin’s purpose should be implemented (see FIG 5. Plugin interfaces below).

Install() and Uninstall() methods of a base interface are required to be implemented to mark a plugin as installed and uninstalled respectively. (see Listing 5 below).

LISTING 5. MARKING PLUGIN AS INSTALLED OR UNINSTALLED.

public virtual void Install()  
{ 
   PluginManager.MarkPluginAsInstalled(PluginDescriptor.SystemName); 
} 
public virtual void Uninstall()  
{ 
   PluginManager.MarkPluginAsUninstalled(PluginDescriptor.SystemName); 
}

PluginDescriptor is initialized automatically from data in plugin.json.

nopCommerce plugin development

FIG 5. PLUGIN INTERFACES

Besides this, these methods may contain one of the following:

  • User-defined plugin settings
  • Localization
  • Creating custom tables
  • Configuring scheduled tasks

It is recommended to have a look at the examples of plugins of different types in nopCommerce source code.

User-defined plugin settings

If a plugin has user-defined settings, it is required to develop model, view and controller for implementing plugin’s configuration page. For this purpose, the following project folders are created: Controllers, Models and Views. Class holding properties for settings should implement Nop.Core.Configuration.ISettings. Naming convention is {PluginName}Settings.

User-defined settings can be either global or per store (nopCommerce allows to run multiple eCommerce stores in one nopCommerce setup).

Recommended name for the model is ConfigurationModel.Configuration and {PluginName}Controller is for view and controller respectively.

A base class for all nopCommerce models is Nop.Web.Framework.Models.BaseNopModel from Nop.Web.Framework project. NopResourceDisplayName attribute is used for localizing properties. It contains a respective resource key as string.

If settings are applied per store, int ActiveStoreScopeConfiguration property is added to the model. New special properties are added to the model for each setting property with the following naming template – {PropertyName}_OverrideForStore of bool type.

LISTING 6. USER-DEFINED SETTINGS MODEL

public class ConfigurationModel : BaseNopModel 
{ 
   public int ActiveStoreScopeConfiguration { get; set; } 
  
   [NopResourceDisplayName("Plugins.Payments.PayPalStandard.Fields.UseSandbox")] 
   public bool UseSandbox { get; set; } 
   public bool UseSandbox_OverrideForStore { get; set; }

Configuration view uses special layout _ConfigurePlugin. If settings are applied per store, StoreScopeConfiguration method is called asynchronously.

LISTING 7. USER-DEFINED SETTINGS VIEW

@model Nop.Plugin.Payments.PayPalStandard.Models.ConfigurationModel 
@inject Nop.Core.IWebHelper webHelper 
@{ 
   Layout = "_ConfigurePlugin"; 
} 
  
@await Component.InvokeAsync("StoreScopeConfiguration")

HTML form is used to store settings server-side. Fields for each setting property are implemented using Bootstrap markup and helpers (see sp Nop.Web.Framework.TagHelpers).

LISTING 8. FORM MARKUP

<form asp-controller="PaymentPayPalStandard" asp-action="Configure" method="post"> 
   <div class="panel-group"> 
       <div class="panel panel-default"> 
           <div class="panel-body"> 
               @Html.Raw(T("Plugins.Payments.PayPalStandard.Instructions", $"{webHelper.GetStoreLocation()}Plugins/PaymentPayPalStandard/PDTHandler")) 
               <div class="form-group"> 
                   <div class="col-md-3"> 
                       <nop-override-store-checkbox asp-for="UseSandbox_OverrideForStore" asp-input="UseSandbox" asp-store-scope="@Model.ActiveStoreScopeConfiguration" /> 
                       <nop-label asp-for="UseSandbox" /> 
                   </div> 
                   <div class="col-md-9"> 
                       <nop-editor asp-for="UseSandbox" /> 
                       <span asp-validation-for="UseSandbox"></span> 
                   </div> 
               </div> 
... 
               <div class="form-group"> 
                   <div class="col-md-9 col-md-offset-3"> 
                       <input type="submit" name="save" class="btn bg-primary" value="@T("Admin.Common.Save")" /> 
                   </div> 
               </div> 
           </div> 
       </div> 
   </div> 
</form> 

Controller can be inherited from Nop.Web.Framework.Controllers.BasePluginController or from Nop.Web.Framework.Controllers.BasePaymentController in case of a payment method. Both of them are inherited from Nop.Web.Framework.Controllers.BaseController.

nopCommerce plugin development

FIG 6. PLUGIN CONTROLLERS

Controller should implement two Configure actions:

  1. Reading user-defined settings – loading settings using nopCommerce service, passing data to model, returning view with model.
  2. Saving user-defined settings – passing data from model to settings and saving to database using nopCommerce service.

For both methods, Area attribute should be specified as “Admin” (see Listing 9 below).
Nop.Services.Configuration.ISettingService is used for working with settings. If you need to store settings per store, Nop.Core. IStoreContext is used. Nop.Services.Security.IPermissionService is used to validate permissions to change settings.

LISTING 9. LOADING USER-DEFINED SETTINGS

[AuthorizeAdmin] 
[Area(AreaNames.Admin)] 
public IActionResult Configure() 
{ 
   if (!_permissionService.Authorize(StandardPermissionProvider.ManagePaymentMethods)) 
       return AccessDeniedView(); 
  
   //load settings for a chosen store scope 
   var storeScope = _storeContext.ActiveStoreScopeConfiguration; 
   var payPalStandardPaymentSettings = _settingService.LoadSetting<PayPalStandardPaymentSettings>(storeScope); 
  
   var model = new ConfigurationModel 
   { 
       UseSandbox = payPalStandardPaymentSettings.UseSandbox, 
       BusinessEmail = payPalStandardPaymentSettings.BusinessEmail, 
       PdtToken = payPalStandardPaymentSettings.PdtToken, 
       PassProductNamesAndTotals = payPalStandardPaymentSettings.PassProductNamesAndTotals, 
       AdditionalFee = payPalStandardPaymentSettings.AdditionalFee, 
       AdditionalFeePercentage = payPalStandardPaymentSettings.AdditionalFeePercentage, 
       ActiveStoreScopeConfiguration = storeScope 
   }; 
   if (storeScope > 0) 
   { 
       model.UseSandbox_OverrideForStore = _settingService.SettingExists(payPalStandardPaymentSettings, x => x.UseSandbox, storeScope); 
       model.BusinessEmail_OverrideForStore = _settingService.SettingExists(payPalStandardPaymentSettings, x => x.BusinessEmail, storeScope); 
       model.PdtToken_OverrideForStore = _settingService.SettingExists(payPalStandardPaymentSettings, x => x.PdtToken, storeScope); 
       model.PassProductNamesAndTotals_OverrideForStore = _settingService.SettingExists(payPalStandardPaymentSettings, x => x.PassProductNamesAndTotals, storeScope); 
       model.AdditionalFee_OverrideForStore = _settingService.SettingExists(payPalStandardPaymentSettings, x => x.AdditionalFee, storeScope); 
       model.AdditionalFeePercentage_OverrideForStore = _settingService.SettingExists(payPalStandardPaymentSettings, x => x.AdditionalFeePercentage, storeScope); 
   } 
  
   return View("~/Plugins/Payments.PayPalStandard/Views/Configure.cshtml", model); 
} 

It’s important to clear cache before saving settings so they take effect (see Listing 10 below).

LISTING 10. SAVING USER-DEFINED SETTINGS

[HttpPost] 
[AuthorizeAdmin] 
[AdminAntiForgery] 
[Area(AreaNames.Admin)] 
public IActionResult Configure(ConfigurationModel model) 
{ 
   if (!_permissionService.Authorize(StandardPermissionProvider.ManagePaymentMethods)) 
       return AccessDeniedView(); 
  
   if (!ModelState.IsValid) 
       return Configure(); 
  
   //load settings for a chosen store scope 
   var storeScope = _storeContext.ActiveStoreScopeConfiguration; 
   var payPalStandardPaymentSettings = _settingService.LoadSetting<PayPalStandardPaymentSettings>(storeScope); 
  
   //save settings 
   payPalStandardPaymentSettings.UseSandbox = model.UseSandbox; 
   payPalStandardPaymentSettings.BusinessEmail = model.BusinessEmail; 
   payPalStandardPaymentSettings.PdtToken = model.PdtToken; 
   payPalStandardPaymentSettings.PassProductNamesAndTotals = model.PassProductNamesAndTotals; 
   payPalStandardPaymentSettings.AdditionalFee = model.AdditionalFee; 
   payPalStandardPaymentSettings.AdditionalFeePercentage = model.AdditionalFeePercentage; 
  
   /* We do not clear cache after each setting update. 
    * This behavior can increase performance because cached settings will not be cleared  
    * and loaded from database after each update */ 
   _settingService.SaveSettingOverridablePerStore(payPalStandardPaymentSettings, x => x.UseSandbox, model.UseSandbox_OverrideForStore, storeScope, false); 
   _settingService.SaveSettingOverridablePerStore(payPalStandardPaymentSettings, x => x.BusinessEmail, model.BusinessEmail_OverrideForStore, storeScope, false); 
   _settingService.SaveSettingOverridablePerStore(payPalStandardPaymentSettings, x => x.PdtToken, model.PdtToken_OverrideForStore, storeScope, false); 
   _settingService.SaveSettingOverridablePerStore(payPalStandardPaymentSettings, x => x.PassProductNamesAndTotals, model.PassProductNamesAndTotals_OverrideForStore, storeScope, false); 
   _settingService.SaveSettingOverridablePerStore(payPalStandardPaymentSettings, x => x.AdditionalFee, model.AdditionalFee_OverrideForStore, storeScope, false); 
   _settingService.SaveSettingOverridablePerStore(payPalStandardPaymentSettings, x => x.AdditionalFeePercentage, model.AdditionalFeePercentage_OverrideForStore, storeScope, false); 
  
   //now clear settings cache 
   _settingService.ClearCache(); 
  
   SuccessNotification(_localizationService.GetResource("Admin.Plugins.Saved")); 
  
   return Configure(); 
} 

Controller can implement not only configuration actions but other actions implementing plugin UI as well.

Localization

If a plugin uses messages displayed to user, tooltips and other UI texts, then respective texts should be implemented as localization resources stored for each used language using Nop.Services.Localization.ILocalizationService.

User with admin permissions can then edit these resources in the admin area.

As described above (see section “Plugin interface and base class”), plugin class has Install() and Uninstall() methods, which is used to implement localization using Nop.Services.Localization.ILocalizationService.AddOrUpdatePluginLocaleResource and Nop.Services.Localization.ILocalizationService.DeletePluginLocaleResource methods respectively.

LISTING 11. INSTALLING USER-DEFINED SETTINGS AND LOCALIZATION

public override void Install() 
{ 
   //settings 
   _settingService.SaveSetting(new PayPalStandardPaymentSettings 
   { 
       UseSandbox = true 
   }); 
  
   //locales 
   _localizationService.AddOrUpdatePluginLocaleResource("Plugins.Payments.PayPalStandard. Fields.AdditionalFee", "Additional fee"); 
... 
   base.Install(); 
}

Naming convention for a localization resource key is the following: {PluginName}.{ResourceType}.{ResourceName}.

AddOrUpdatePluginLocaleResource method has a third optional parameter (string languageCulture = null) that indicates language and culture.

LISTING 12. DELETING USER-DEFINED SETTINGS AND LOCALIZATION RESOURCES DURING UNINSTALLING PLUGIN

public override void Uninstall() 
{ 
   //settings 
   _settingService.DeleteSetting<PayPalStandardPaymentSettings>(); 
  
   //locales 
   _localizationService.DeletePluginLocaleResource("Plugins.Payments.PayPalStandard.Fields. AdditionalFee"); 
... 
   base.Uninstall(); 
} 

Resources can be loaded using GetResource method (see example in Listing 8 above).

Localization resources are rendered in markup using tags and helpers (e.g. T, nop-label) from Nop.Web.Framework.TagHelpers (see example in Listing 6 above)

Custom entities and database tables

A plugin can extend and/or change nopCommerce database structure without modifying nopCommerce code base. (see the source code of Nop.Plugin.Pickup.PickupInStore).

Key steps to perform:

  • Define new entities in a plugin project
  • Develop the database context for these entities
  • Implement database mapping for these entities
  • Implement services for these entities

Defining new entities

Entity classes are defined in Domain project folder and are inherited from Nop.Core. BaseEntity, which has Id property of int type.

Implementing database context

Database context and database mapping for entities are defined in Data project folder. Database context is inherited from Microsoft.EntityFrameworkCore.DbContext and implements Nop.Data.IDbContext. You should also implement Install/Uninstall methods for creating/deleting custom database tables which are called in Install() and Uninstall() plugin methods respectively.

LISTING 13. DATABASE CONTEXT METHODS FOR CREATING AND DELETING CUSTOM TABLES

public void Install() 
{ 
   this.ExecuteSqlScript(this.GenerateCreateScript()); 
} 
  
public void Uninstall() 
{ 
   this.DropPluginTable(nameof(StorePickupPoint)); 
} 

Mapping entities to database tables

To create database tables the following interface should be implemented: Nop.Core.Infrastructure.INopStartup. Recommended naming convention for a new class – PluginDbStartup. Recommended project folder for a new class is Infrastructure.

Database mappings are created for each entity. Mappings are inherited from Nop.Data.Mapping.NopEntityTypeConfiguration.

LISTING 14. MAPPING ENTITY TO ITS TABLE

public override void Configure(EntityTypeBuilder<StorePickupPoint> builder) 
{ 
   builder.ToTable(nameof(StorePickupPoint)); 
   builder.HasKey(point => point.Id); 
  
   builder.Property(point => point.PickupFee).HasColumnType("decimal(18, 4)"); 
} 

Services for new entities

To implement business logic for new entities, interfaces and classes of respective services should be created in Services project folder.

To register database context and services classes, Nop.Core.Infrastructure.DependencyManagement.IDependencyRegistrar should be implemented. Recommended name for implementation is DependencyRegistrar. Recommended folder for implementation is Infrastructure project folder. Each entity also has an implementation of Nop.Core.Data.IRepository inherited from Nop.Data.EfRepository.

LISTING 15. REGISTERING DATABASE CONTEXT, SERVICE AND REPOSITORY

public virtual void Register(ContainerBuilder builder, ITypeFinder typeFinder, NopConfig config) 
{ 
   builder.RegisterType<StorePickupPointService>().As<IStorePickupPointService>() .InstancePerLifetimeScope(); 
  
   //data context 
   builder.RegisterPluginDataContext<StorePickupPointObjectContext>( "nop_object_context_pickup_in_store-pickup"); 
  
   //override required repository with our custom context 
   builder.RegisterType<EfRepository<StorePickupPoint>>().As<IRepository<StorePickupPoint>>() 
       .WithParameter(ResolvedParameter.ForNamed<IDbContext>( "nop_object_context_pickup_in_store-pickup")) 
       .InstancePerLifetimeScope(); 
}

Scheduled tasks

Scheduled tasks are usually used for a regular data exchange with third-party systems. E.g. exporting catalog data to external marketplace or importing orders and customers from CRM. nopCommerce also has inbuilt scheduled tasks such as Clear cache, Send emails or Delete guests.

Key steps to perform:

  • Implement IScheduleTask
  • Insert/Remove task during Install/Uninstall of plugin
  • Manage task in the admin area

Implement IScheduleTask

Classes implementing IScheduleTask are recommended to keep in Tasks project folder. It can also be Services project folder. Nop.Services.Tasks.IScheduleTask defines sole method Execute. Example of implementation can be found in Nop.Plugin.Payments.Square source code (class Nop.Plugin.Payments.Square.Services.RenewAccessTokenTask).

All tasks’ business logic is encapsulated in the Execute method.

Scheduled Task is registered in plugin’s Install method using Nop.Services.Tasks.IScheduleTaskService.InsertTask method. Accordingly, task should be deleted in plugin’s Uninstall method using Nop.Services.Tasks.IScheduleTaskService.DeleteTask method.

If a plugin is updated and the task appears in a new version of the plugin, the older version of the plugin should be deleted and the plugin should be reinstalled again. Otherwise, task would not be registered.

When registering a task, its description should be formed using Nop.Core.Domain.Tasks.ScheduleTask class. It is recommended to name a task after the system name of its type. Before registering the task, it’s recommended to check whether it already exists by calling GetTaskByType method using the system name of its type.

LISTING 16. DEFINING A NAME FOR A TASK

public static string GetTaskName<T>() 
{ 
   return $"{typeof(T).FullName}, {typeof(T).Assembly.GetName().Name}"; 
} 

During registering the task its display name, launch interval, error behavior are defined.

LISTING 17. REGISTERING TASK

string taskName = GetTaskName<ExportCatalogTask>(); 
ScheduleTask task = _scheduleTaskService.GetTaskByType(taskName); 
if (task == null) 
{ 
   task = new ScheduleTask() 
   { 
       Enabled = true, 
       Name = "retailCRM: Export catalog", 
       Seconds = 60 * 60 * 4, 
       StopOnError = false, 
       Type = taskName, 
   }; 
_scheduleTaskService.InsertTask(task); 
} 

When the plugin is being uninstalled, the task is also being deleted by its system name in the Uninstall method. If multiple tasks are deployed with the plugin, you can use loop to deleted them in order to keep the code cleaner.

LISTING 18. DELETING TASKS DURING UNINSTALLING THE PLUGIN

foreach (string typeName in new string[] { 
   GetTaskName<DownloadHistoryTask>(), 
   GetTaskName<ExportCatalogTask>(), 
   GetTaskName<UploadCustomerTask>(), 
   GetTaskName<UploadOrderTask>() }) 
{ 
   ScheduleTask task = _scheduleTaskService.GetTaskByType(typeName); 
   if (task != null) 
       _scheduleTaskService.DeleteTask(task); 
}

Managing tasks in the admin area

A user with admin rights can view and edit tasks in the respective admin section. The task can also be executed at any time using Run Now button (see FIG 7. List of scheduled tasks). Last start date, last end date and last success date are also displayed.

If an error occurred during the task execution, it’s being logged and can be viewed in the system log. If an error occurred during manual launch using Run now command, it’s also displayed in UI. As a best practice, it’s recommended to use additional logging (see LISTING 4. Logging exception).

nopCommerce plugin development

FIG 7. LIST OF SCHEDULED TASKS

Events subscription

nopCommerce implements event-driven architecture and allows to subscribe to various system events. It is useful for developing triggers that run specific business logic after specific events occur. E.g. sending data to external systems such as sending new orders to CRM right after an order was placed. Unlike scheduled tasks, such triggers are being executed in real-time without delays. Below is the list of key events.

Data modification:

  • Nop.Core.Events.EntityInsertedEvent – generic event of inserting new data
  • Nop.Core.Events.EntityUpdatedEvent – generic event of updating data
  • Nop.Core.Events.EntityDeletedEvent – generic event of deleting data

Note: if data inserting/updating/deleting occurs multiple times, each time an event would be generated. E.g., Nop.Core.Events.EntityUpdatedEvent for an order occurs multiple times during the order placement. So, if you only need to handle order placement, there is a number of other high-level events.

Customer events:

  • Nop.Core.Domain.Customers.CustomerRegisteredEvent – event of registering a customer
  • Nop.Core.Domain.Customers.CustomerLoggedinEvent – event of signing in
  • Nop.Core.Domain.Customers.CustomerLoggedOutEvent – event of signing out
  • Nop.Core.Domain.Customers.CustomerPasswordChangedEvent – event of changing a password
  • Nop.Core.Domain.Customers.CustomerPermanentlyDeleted – event of deleting a customer

Order events:

  • Nop.Core.Domain.Orders.OrderPlacedEvent – event or order placement
  • Nop.Core.Domain.Orders.OrderVoidedEvent – event of placing an empty order
  • Nop.Core.Domain.Orders.OrderPaidEvent – event of an order payment
  • Nop.Core.Domain.Orders.OrderCancelledEvent – event of canceling an order
  • Nop.Core.Domain.Orders.OrderRefundedEvent – event of an order refund

Other events:

  • Nop.Core.Domain.Blogs.BlogCommentApprovedEvent – event of blog comment approval
  • Nop.Core.Domain.Catalog. ProductReviewApprovedEvent – event of product review approval
  • Nop.Core.Domain.Messages.AdditionTokensAddedEvent – event of adding new tokens to non-campaign message templates
  • Nop.Core.Domain.Messages.CampaignAdditionTokensAddedEvent – event of adding new tokens to message templates for campaigns
  • Nop.Core.Domain.Messages.MessageTokensAddedEvent – event of adding new tokens to all message templates
  • Nop.Core.Domain.Messages.EntityTokensAddedEvent – generic event of adding new tokens to an entity of a specific type
  • Nop.Core.Domain.Messages.EmailSubscribedEvent – event of an email subscription
  • Nop.Core.Domain.Messages.EmailUnsubscribedEvent – event of canceling an email subscription
  • Nop.Core.Domain.News.NewsCommentApprovedEvent – event of news comment approval

Implementation of Nop.Services.Events.IConsumer is used for subscribing to events. HandleEvent method should be implemented to encapsulate event handling logic.

LISTING 19. HANDLING EVENT OF DELETING DISCOUNT RULE

public partial class DiscountRequirementEventConsumer : 
    IConsumer<EntityDeletedEvent<DiscountRequirement>> 
{ 
   private readonly ISettingService _settingService; 
   public DiscountRequirementEventConsumer(ISettingService settingService) 
   { 
       this._settingService = settingService; 
   } 
   public void HandleEvent(EntityDeletedEvent<DiscountRequirement> eventMessage) 
   { 
       var discountRequirement = eventMessage?.Entity; 
       if (discountRequirement == null) 
           return; 
  
       //delete saved restricted customer role identifier if exists 
       var setting = _settingService.GetSetting( 
           string.Format(DiscountRequirementDefaults.SettingsKey, discountRequirement.Id)); 
       if (setting != null) 
           _settingService.DeleteSetting(setting); 
   } 
} 

nopCommerce checks all assemblies and registers all classes implementing IConsumer to send events to them. (see source code in Nop.Web.Framework.Infrastructure. DependencyRegistrar).

Widgets

Nop.Services.Cms.IWidgetPlugin is used for developing widgets that can be embedded as part of a view on different pages. Plugin implementation’s GetWidgetZones method returns a list of so called widget zones – placeholders – where a widget can be embedded..
Nop.Web.Framework.Infrastructure.PublicWidgetZones class contains all zones for nopCommerce UI where widgets can be embedded. Nop.Web.Framework.Infrastructure.AdminWidgetZones class contains widget zones in the admin area.

Plugins Nop.Plugin.Widgets.GoogleAnalytics and Nop.Plugin.Widgets.NivoSlider are examples of the widget plugins.

MVC pattern is used for developing widgets, therefore model, partial view and controller should be developed. View component based on Nop.Web.Framework.Components. NopViewComponent should also be developed. To reference additional namespaces _ViewImports.cshtml file should be added to Views project folder.

LISTING 20. EXAMPLE OF _VIEWIMPORTS.CSHTML

@inherits Nop.Web.Framework.Mvc.Razor.NopRazorPage<TModel> 
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 
@addTagHelper *, Nop.Web.Framework 
  
@using Microsoft.AspNetCore.Mvc.ViewFeatures 
@using Nop.Web.Framework.UI 
@using Nop.Web.Framework.Extensions 
@using System.Text.Encodings.Web

The recommended name for a partial view is PublicInfo.cshtml. Best practices for development are the same as for ASP.NET Core MVC 2.

User with admin permission can enable and disable widgets in admin section as well as specify their display order. Configure and Edit buttons allow editing user-defined and standard settings of a plugin respectively (see FIG 2. Standard plugin settings and FIG 3. User-defined plugin settings).

nopCommerce plugin development

FIG 8. LIST OF WIDGETS

Generic attributes

nopCommerce has a flexible design pattern for extending standard entities without modifying database tables. It is a GenericAttribute table that has non-foreign key relations with nopCommerce entities and stores their custom attributes.

Below is the list and descriptions of GenericAttribute columns:

ColumnTypeDescription
IDintPrimary key
EntityIdintnon-FK reference to the entity to which an attribute belongs to
KeyGroupnvarchar(400) Entity table where entity attribute that belongs to it is stored. E.g. “Customer” for a Customer entity or “Order” for an Order entity
Keynvarchar(400)Attribute key
Valuenvarchar(MAX)Attribute value
StoreIdintStore Id if an attribute is defined per store or 0 if the attribute is defined globally

Example:
Id: 1
EntityId: 2
KeyGroup: Customer
Key: FirstName
Value: John
StoreId: 0
A customer whose Id equals 2 has a first name “John” defined as an attribute globally.

Generic attributes enable to extend entities with custom attributes holding additional information. For example, a standard Customer entity implements most of the customer profile fields as generic attributes. Basic nopCommerce feature of extending registration fields also uses GenericAttribute under the hood. By default, generic attributes cannot be edited in the admin area and common practice is to implement additional editable fields on entities in the admin area as part of the plugin.

nopCommerce plugin development

FIG 9. CUSTOM CUSTOMER ATTRIBUTES

Note: you should carefully design your plugin before choosing between Generic attributes approach and extending entity via custom entities and database tables approach (see “Custom entities and database tables section above”). Generic attributes don’t hold foreign keys and all attribute values are strings. So, overuse of GenericAttributes may lead to problems with performance and data integrity when the format of some attribute has been changed (older records may contain inconsistent values) or entity which is referenced in GenericAttribute table may no longer exist. For this reason, in some cases, you may use GenericAttribute while in other cases you should stick to more complex yet more correct and scalable approach of modifying the database via a plugin.

Writing and reading generic attributes is done by using IGenericAttribute service and extension methods on entities (see LISTING 21 below)

LISTING 21. READING AND WRITING GENERIC ATTRIBUTES

public virtual ActionResult Create(CustomerModel model, bool continueEditing, FormCollection form) 
{ 
   //writing generic attributes 
   if (_customerSettings.GenderEnabled) 
       _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.Gender, model.Gender); 
   _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.FirstName, model.FirstName); 
   _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.LastName, model.LastName); 
   //reading generic attributes 
   string gender = customer.GetAttribute<string>(SystemCustomerAttributeNames.Gender) 
} 

Method overriding

One of the core approaches of extending basic nopCommerce business logic in plugin is method overriding. This is one of the most powerful yet simple approaches. E.g. if we need to change the behaviour of adding products to cart, we can implement child class inherited from ShoppingCartService in our plugin and override AddToCart method.

LISTING 22. OVERRIDING BASE CLASS METHOD

public class ExtendedShoppingCartService: ShoppingCartService, IShoppingCartService 
{ 
   public override IList AddToCart(Customer customer, Product product, ShoppingCartType shoppingCartType, int storeId, string attributesXml = null, decimal customerEnteredPrice = 0, DateTime? rentalStartDate = null, DateTime? rentalEndDate = null, int quantity = 1, bool automaticallyAddRequiredProductsIfEnabled = true) 
   { 
       //overridden business logic goes here 
… 
   } 
}

To avoid copy-pasting code from the base class’ method that should be reused, call base class via base.. It’s also a good practice to design your customization in a way that child class contains minimum of code and calls base class’ method to reuse its code.

In order to tell nopCommerce to use our child class instead of its parent when resolving dependencies, we need to register our class using plugin’s implementation of Nop.Core.Infrastructure.DependencyManagement.IDependencyRegistrar.

LISTING 23. REGISTERING CHILD CLASS

public void Register(ContainerBuilder builder, ITypeFinder typeFinder, NopConfig config) 
{ 
   … 
  builder.RegisterType<ExtendedShoppingCartService>().As<Orders.IShoppingCartService>().InstancePerLifetimeScope(); 
   … 
}

After registering child class all IShoppingCartServices instances will be resolved as ExtendedShoppingCartService instead of a standard nopCommerce’s ShoppingCartService. In order to verify that plugin’s child class is registered after its parent, you should specify Nop.Core.Infrastructure.DependencyManagement.IDependencyRegistrar.Order property to be higher than in nopCommerce’s IDependencyRegistrar implementation. For example, by setting it to int.MaxValue. Then child class will be registered after its parent and will have higher priority.

Note: even though method overriding is a powerful customization approach it involves risks that should be addressed. One of the common risks is the situation of conflicting plugins when several plugins override the same method which leads to an unexpected behavior. You should minimize using third-party plugins with closed source code (because they have unknown method overrides) and should carefully design your own plugins by avoiding overriding the same method by different plugins or use Nop.Core.Infrastructure.DependencyManagement.IDependencyRegistrar.Order property in your plugins to manage priority order when several plugins extending the same methods are deployed.

Request a quote

nopCommerceplugin

Categories

  • News
  • Tech stories
  • eCommerce

Popular topics

Adjust Azure Behavior analytics Belgium berlinexpo Business Intelligence Certification chatbots COVID-19 CRM DevExpress XAF DPD Dynamics365 ecommerce events integration Linux Marketing tools Microsoft mobile marketing mobile measurement MVP nonprofit nopCommerce nopCommerceDays omnichannel open source plugin POS Regulations security SharePoint Snowplow SQL Server Store setup subscriptions web-applications Web Analytics
  • Microsoft Power AppsMicrosoft Power Apps
    Microsoft Power Apps
    19 JAN 2021 · admin
  • RPA with Power Automate DesktopRPA with Power Automate Desktop
    RPA with Power Automate Desktop
    4 JAN 2021 · admin
  • Microsoft Power AutomateMicrosoft Power Automate
    Microsoft Power Automate
    18 DEC 2020 · admin
  • Microsoft Power BIMicrosoft Power BI
    Microsoft Power BI
    10 DEC 2020 · admin
  • Microsoft 365 subscription plans overviewMicrosoft 365 subscription plans overview
    Microsoft 365 subscription plans overview
    19 NOV 2020 · admin
  • Microsoft Office 365 apps overviewMicrosoft Office 365 apps overview
    Microsoft Office 365 apps overview
    12 NOV 2020 · admin
  • Which mobile app analytics platform is right for you?Which mobile app analytics platform is right for you?
    Which mobile app analytics platform is right for you?
    5 NOV 2020 · admin
  • Minimum Viable Product to test your business idea quickly and with a little budgetMinimum Viable Product to test your business idea quickly and with a little budget
    Minimum Viable Product to test your business idea quickly and with a little budget
    16 OCT 2020 · admin
  • How tracking and analytics can help you maximize your mobile app marketing effortsHow tracking and analytics can help you maximize your mobile app marketing efforts
    How tracking and analytics can help you maximize your mobile app marketing efforts
    30 SEP 2020 · admin
  • User analytics: comparison of Snowplow and Google Analytics 360User analytics: comparison of Snowplow and Google Analytics 360
    User analytics: comparison of Snowplow and Google Analytics 360
    16 SEP 2020 · admin

Projects and solutions implemented

https://isdk.pro/wp-content/uploads/Barcode-scanning-small-150x150.jpegMVP app to test an E-Commerce business idea in 3 weeks
https://isdk.pro/wp-content/uploads/GlobeIn-Shop-small-1-150x150.jpgiOS app with Apple in-app subscriptions and purchases
https://isdk.pro/wp-content/uploads/globein-box-small-150x150.jpgSubscription boxes iOS app set up for marketing campaigns
https://isdk.pro/wp-content/uploads/book-publisher-small-150x150.jpgnopCommerce multi-store for a book publisher
https://isdk.pro/wp-content/uploads/clinic-small-150x150.jpgRoutine automation with Office 365 and Teams chatbot
https://isdk.pro/wp-content/uploads/SharePoint-in-a-business-school-150x150.jpgSharePoint Online and Office 365 in a business school
https://isdk.pro/wp-content/uploads/SharePoint-for-a-DMS_ed-small-150x150.pngSharePoint 2019 Document Archive
https://isdk.pro/wp-content/uploads/nopCommerce-upgrade-case-featured-400-200-150x150.pngnopCommerce Upgrade to .NET CORE
https://isdk.pro/wp-content/uploads/Harman-JBL-ecommerce-case-featured-400-200-150x150.pngHigh-traffic eCommerce webstore Harman.club
https://isdk.pro/wp-content/uploads/Herbalife-banner-400-200-150x150.pngE-Commerce Omni-Channel
https://isdk.pro/wp-content/uploads/hr-sharepoint-trainings-home-400-200-150x150.pngEnterprise SharePoint Portal. HRM-Trainings
https://isdk.pro/wp-content/uploads/Automobile-company-sharepoint-hr-portal-400-200-150x150.pngSharePoint HR-Portal – Employee Request Approvals

Contact us

logo

“The only way to do great work is to love what you do” - Steve Jobs

Search by topic

.NET Core .NET Framework 1С Adjust Ajax Apache JMeter ARIS ASP.NET ASP.NET MVC Bootstrap Business Connectivity Services DevExpress DevExpress XAF DevExtreme ESB Google Geoservices Google Map InfoPath Java Message Service JavaScript JQuery Knockout.js Knockoutjs Microsoft Azure Microsoft Flow MS Active Directory MS SQL MS System Center Nintex Nintex Forms Nintex Workflow nopCommerce QlikView Reporting Services SharePoint Sharepoint Search Sharepoint workflow Snowplow Telerik UML Visual Studio Web Services Xamarin

Projects

https://isdk.pro/wp-content/uploads/Barcode-scanning-small-150x150.jpegMVP app to test an E-Commerce business idea in 3 weeks
https://isdk.pro/wp-content/uploads/GlobeIn-Shop-small-1-150x150.jpgiOS app with Apple in-app subscriptions and purchases
https://isdk.pro/wp-content/uploads/globein-box-small-150x150.jpgSubscription boxes iOS app set up for marketing campaigns
https://isdk.pro/wp-content/uploads/book-publisher-small-150x150.jpgnopCommerce multi-store for a book publisher
https://isdk.pro/wp-content/uploads/clinic-small-150x150.jpgRoutine automation with Office 365 and Teams chatbot
https://isdk.pro/wp-content/uploads/SharePoint-in-a-business-school-150x150.jpgSharePoint Online and Office 365 in a business school
https://isdk.pro/wp-content/uploads/SharePoint-for-a-DMS_ed-small-150x150.pngSharePoint 2019 Document Archive
https://isdk.pro/wp-content/uploads/nopCommerce-upgrade-case-featured-400-200-150x150.pngnopCommerce Upgrade to .NET CORE
https://isdk.pro/wp-content/uploads/Harman-JBL-ecommerce-case-featured-400-200-150x150.pngHigh-traffic eCommerce webstore Harman.club
https://isdk.pro/wp-content/uploads/Herbalife-banner-400-200-150x150.pngE-Commerce Omni-Channel
https://isdk.pro/wp-content/uploads/hr-sharepoint-trainings-home-400-200-150x150.pngEnterprise SharePoint Portal. HRM-Trainings
https://isdk.pro/wp-content/uploads/Automobile-company-sharepoint-hr-portal-400-200-150x150.pngSharePoint HR-Portal – Employee Request Approvals

Contact us

  • USA Austin, TX
    +1 737 2413109

    GERMANY Berlin
    +49 15233977274

    BENELUX (BELGIUM, Antwerp)
    +32 484 40 10 53

    RUSSIA Moscow
    +7 495 651 66 62

    LATVIA Riga
    +371 27869927

  • [email protected]
  • https://isdk.pro
  • https://www.linkedin.com/company/isdk/
ISDK ©2010-2020

All rights reserved.

X
Request trial hours!
Submit
ISDK website uses cookies for statistics and content. Learn more ACCEPT
Privacy & Cookies Policy

Privacy Overview

This website uses cookies to improve your experience while you navigate through the website. Out of these cookies, the cookies that are categorized as necessary are stored on your browser as they are essential for the working of basic functionalities of the website. We also use third-party cookies that help us analyze and understand how you use this website. These cookies will be stored in your browser only with your consent. You also have the option to opt-out of these cookies. But opting out of some of these cookies may have an effect on your browsing experience.
Necessary Always Enabled

Necessary cookies are absolutely essential for the website to function properly. This category only includes cookies that ensures basic functionalities and security features of the website. These cookies do not store any personal information.

Non-necessary

Any cookies that may not be particularly necessary for the website to function and is used specifically to collect user personal data via analytics, ads, other embedded contents are termed as non-necessary cookies. It is mandatory to procure user consent prior to running these cookies on your website.