Hello friends,
I'm going to share my experience in NopCommerce customization. I will write about Plugins, Scheduled tasks, Events, Services, and everything you need to know to extend your NopCommerce shop.
This first post will be about NopCommerce plugin for NopCommerce 4.1.
So, to start writing your own plugin for NopCommerce you need to start from the help page of the official documentation
Steps described there are required to start writing your plugin. After that you will just extend it to meet your requirements and here are some useful things:
- Main Plugin class declaration:
public class Feeder : BasePlugin, IMiscPlugin, IAdminMenuPlugin
- Declare all classes you need for work:
public Feeder(IActionContextAccessor actionContextAccessor,
IDiscountService discountService,
ILocalizationService localizationService,
ISettingService settingService,
IUrlHelperFactory urlHelperFactory,
IWebHelper webHelper,
IScheduleTaskService scheduleTaskService)
{
this._actionContextAccessor = actionContextAccessor;
this._discountService = discountService;
this._localizationService = localizationService;
this._settingService = settingService;
this._urlHelperFactory = urlHelperFactory;
this._webHelper = webHelper;
this._scheduleTaskService = scheduleTaskService;
}
- Declare base methods:
public override string GetConfigurationPageUrl()
{
return $"{_webHelper.GetStoreLocation()}Admin/ProductFeederMPlug/Configure";
}
public string GetConfigurationUrl(int discountId, int? discountRequirementId)
{
return $"{_webHelper.GetStoreLocation()}Admin/ProductFeederMPlug/Configure";
}
- Adding menu item to the admin menu:
public void ManageSiteMap(SiteMapNode rootNode)
{
var menuItem = new SiteMapNode()
{
SystemName = "Product Feeder MPlug",
Title = "Product Feeder MPlug",
ControllerName = "ProductFeederMPlug",
ActionName = "Setup",
Visible = true,
IconClass = "fa fa-dot-circle-o",
RouteValues = new RouteValueDictionary() { { "area", Web.Framework.AreaNames.Admin } },
};
var pluginNode = rootNode.ChildNodes.FirstOrDefault(x => x.SystemName == "Configuration");
if (pluginNode != null)
pluginNode.ChildNodes.Add(menuItem);
else
rootNode.ChildNodes.Add(menuItem);
}
-Installation method:
public override void Install()
{
var task = _scheduleTaskService.GetTaskByType(Services.UpdateStoreTask.TypeName);
if (task == null)
{
_scheduleTaskService.InsertTask(new Core.Domain.Tasks.ScheduleTask()
{
Type = Services.UpdateStoreTask.TypeName,
Enabled = true,
Name = "MPlug Product Synchronizer",
Seconds = 60 * 10,
StopOnError = false
});
}
else
{
task.Enabled = true;
task.Seconds = 60 * 10;
task.StopOnError = false;
_scheduleTaskService.UpdateTask(task);
}
task = _scheduleTaskService.GetTaskByType(Services.UpdateOrderStateTask.TypeName);
if (task == null)
{
_scheduleTaskService.InsertTask(new Core.Domain.Tasks.ScheduleTask()
{
Type = Services.UpdateOrderStateTask.TypeName,
Enabled = true,
Name = "MPlug Order State Tracker",
Seconds = 60 * 10,
StopOnError = false
});
}
else
{
task.Enabled = true;
task.Seconds = 60 * 15;
task.StopOnError = false;
_scheduleTaskService.UpdateTask(task);
}
base.Install();
}
- Uninstalling:
public override void Uninstall()
{
//do whatever you need to uninstall your plugin
base.Uninstall();
}
- Adding your own controller - Inherit your plugin controller from the BasePluginController class:
public class ProductFeederMPlugController: BasePluginController
- Declaring views:
[AuthorizeAdmin]
[Area(AreaNames.Admin)]
public IActionResult Configure()
{
if (!_permissionService.Authorize(StandardPermissionProvider.AccessAdminPanel))
return AccessDeniedView();
return View("~/Plugins/ProductFeeder.MPlug/Views/Configure.cshtml");
}
[AuthorizeAdmin]
[Area(AreaNames.Admin)]
public IActionResult Setup()
{
if (!_permissionService.Authorize(StandardPermissionProvider.AccessAdminPanel))
return AccessDeniedView();
return View("~/Plugins/ProductFeeder.MPlug/Views/Setup.cshtml");
}
- Store scope detecting:
var storeScope = _storeContext.ActiveStoreScopeConfiguration;
- Store temporary information during session:
_cache.Set<string>("Progress", progress, TimeSpan.FromMinutes(30));
- Declare all instances of classes you need as constructor parameters and store them into fields:
public ProductFeederController(ICustomerService customerService,
Nop.Services.Shipping.Date.IDateRangeService dateRangeService,
ILocalizationService localizationService,
IPermissionService permissionService,
ISettingService settingService,
IStoreContext storeContext,
Nop.Services.Catalog.ICategoryService categoryService,
Nop.Services.Catalog.IManufacturerService manufacturerService,
Nop.Services.Catalog.IProductService productService,
Nop.Services.Catalog.IProductAttributeService productAttributeService,
Nop.Services.Catalog.ISpecificationAttributeService specificationAttributeService,
IGenericAttributeService genericAttributeService,
Core.Infrastructure.INopFileProvider fileProvider,
Nop.Services.Media.IPictureService pictureService,
Nop.Services.Stores.IStoreMappingService storeMappingService,
Nop.Services.Catalog.IProductTagService productTagService,
Nop.Services.Seo.IUrlRecordService urlRecordService,
Microsoft.Extensions.Caching.Memory.IMemoryCache cache)
{
this._dateRangeService = dateRangeService;
this._customerService = customerService;
this._localizationService = localizationService;
this._permissionService = permissionService;
this._settingService = settingService;
this._storeContext = storeContext;
this._categoryService = categoryService;
this._manufacturerService = manufacturerService;
this._productService = productService;
this._productAttributeService = productAttributeService;
this._specificationAttributeService = specificationAttributeService;
this._genericAttributeService = genericAttributeService;
this._fileProvider = fileProvider;
this._pictureService = pictureService;
this._storeMappingService = storeMappingService;
this._productTagService = productTagService;
this._urlRecordService = urlRecordService;
this._cache = cache;
}
- Save needed data into settings (DB table):
_settingService.SetSetting<string>("Categories", Newtonsoft.Json.JsonConvert.SerializeObject(finalcategories, Newtonsoft.Json.Formatting.None,
new Newtonsoft.Json.JsonSerializerSettings()
{
ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore
}), storeScope, true);
- To avoid complexity of existing model extending - use generic attributes:
_genericAttributeService.SaveAttribute<int>(manuf, Feeder.gAttrID, manuid);
- Add store mapping for entities (in this case for Manufacturer):
_storeMappingService.InsertStoreMapping<Core.Domain.Catalog.Manufacturer>(manuf, storeScope);
- Use repositories to speed-up it work with DB and if you do not need all other logic and events to be occured (use with caution and be aware of it):
var productRepository = Core.Infrastructure.EngineContext.Current.Resolve<Core.Data.IRepository<Core.Domain.Catalog.Product>>();
- Adding a manufacturer:
manuf = new Core.Domain.Catalog.Manufacturer()
{
Name = product.brandName,
CreatedOnUtc = DateTime.UtcNow,
ManufacturerTemplateId = 1,
MetaDescription = product.brandName,
MetaKeywords = product.brandName,
MetaTitle = product.brandName,
PageSize = 20,
Published = true,
UpdatedOnUtc = DateTime.UtcNow
};
_manufacturerService.InsertManufacturer(manuf);
_storeMappingService.InsertStoreMapping<Core.Domain.Catalog.Manufacturer>(manuf, storeScope);
- Adding date ranges / delivery dates:
daterange = new Core.Domain.Shipping.DeliveryDate()
{
Name = deliveryText
};
_dateRangeService.InsertDeliveryDate(daterange);
- Adding product:
pro = new Core.Domain.Catalog.Product()
{
ProductType = Core.Domain.Catalog.ProductType.SimpleProduct,
VisibleIndividually = true,
Sku = product.article,
IsShipEnabled = true,
MetaDescription = shortd,
MetaKeywords = shortd,
MetaTitle = name,
Price = price,
ProductCost = cost,
AdditionalShippingCharge = Feeder.CalculateShipping(price),
CreatedOnUtc = DateTime.UtcNow,
UpdatedOnUtc = ((DateTime)product.modified).ToUniversalTime(),
Name = name,
ShortDescription = product.descriptionSnort,
FullDescription = product.description,
BackorderMode = Core.Domain.Catalog.BackorderMode.NoBackorders,
MarkAsNew = true,
AllowBackInStockSubscriptions = true,
AllowCustomerReviews = true,
Published = price != null,
DeliveryDateId = daterange == null ? 0 : daterange.Id,
LimitedToStores = true,
OrderMaximumQuantity=int.MaxValue,
OrderMinimumQuantity=1,
DisableBuyButton = pprice == null,
ProductTemplateId = 1,
ProductManufacturers =
{
new Core.Domain.Catalog.ProductManufacturer() { Manufacturer = manuf, Product = pro }
}
};
productRepository.Insert(pro);
- Add pictures/images as files:
string iufn = _fileProvider.GetFileName(iurl);
if (!pro.ProductPictures.Any(a => a.Picture.SeoFilename.ToLowerInvariant() == iufn.ToLowerInvariant()))
{
Core.Domain.Media.Picture propic;
_pictureService.StoreInDb = false;
propic = _pictureService.InsertPicture(data.Item2, "image/jpeg", iufn, pro.Name, pro.Name, true);
_productService.InsertProductPicture(new Core.Domain.Catalog.ProductPicture()
{
Product = pro,
Picture = propic
});
}
- Adding Product Attribute:
pattr = new Core.Domain.Catalog.ProductAttribute()
{
Name = name
};
productAttributeService.InsertProductAttribute(pattr);
pro.ProductAttributeMappings.Add(new Core.Domain.Catalog.ProductAttributeMapping()
{
AttributeControlType = Core.Domain.Catalog.AttributeControlType.ColorSquares,
IsRequired = true,
ProductAttribute = pattr,
ProductAttributeValues =
{
new Core.Domain.Catalog.ProductAttributeValue
{
AttributeValueType = Core.Domain.Catalog.AttributeValueType.Simple,
Name = val,
IsPreSelected = true,
ColorSquaresRgb=rgb
}
}
- Adding Product Specification Attribute:
spattr = new Core.Domain.Catalog.SpecificationAttribute
{
Name = name
};
spAttrRepository.Insert(spattr);
spao = new Core.Domain.Catalog.SpecificationAttributeOption()
{
Name = val,
SpecificationAttribute = spattr
};
spOAttrRepository.Insert(spao);
pro.ProductSpecificationAttributes.Add(new Core.Domain.Catalog.ProductSpecificationAttribute()
{
AllowFiltering = true,
ShowOnProductPage = true,
SpecificationAttributeOption = spao,
AttributeType = Core.Domain.Catalog.SpecificationAttributeType.Option
});
- do not forget to update Entity (Product in this case) after all changes:
productRepository.Update(pro);
- Adding Urls/Slugs to entities:
if (string.IsNullOrEmpty(_urlRecordService.GetActiveSlug(p.Id, typeof(Core.Domain.Catalog.Product).Name, 0)))
{
slug = _urlRecordService.ValidateSeName(_productService.GetProductById(p.Id), p.Name, p.Name, true);
_urlRecordService.InsertUrlRecord(new Core.Domain.Seo.UrlRecord
{
EntityId = p.Id,
EntityName = typeof(Core.Domain.Catalog.Product).Name,
LanguageId = 0,
IsActive = true,
Slug = slug
});
}
Next post about adding message tokens...

1vqHSTrq1GEoEF7QsL8dhmJfRMDVxhv2y