Sometimes, it’s all about performance! Especially if your nopCommerce site is high-traffic and / or...
Top Nop Tips for nopCommerce Development
nopCommerce is a smartly-built and well-maintained open source e-commerce platform for ASP .NET Core. While offering plenty of capability out-of-the-box, nop is built to provide great resources to add or enhance functionality through the use of “plug-ins.” In this article I’ll cover my top 8 tips and tricks for efficient, re-usable, plug-in development.
1. Do not reinvent the wheel
To minimize development time, be sure to check out the nopCommerce marketplace and see what themes / extensions are already available. There’s a healthy variety, and many come with a trial period to allow you to verify your needs are met. By making use of existing plug-ins, we can make better use of our development time.
2. Do not alter / remove source code
To avoid complications with other extensions and future nopCommerce upgrades, we strongly recommend customization be at the theme / plugin level. For proprietary customizations, if necessary, be sure to use partial classes in separate, easy-to-identify directories. It’s a good idea to recreate the directory tree in a single subdirectory, this allows an easier approach when upgrading nopCommerce in the future.
In the rare case that you cannot accomplish your need without modifying source code, be sure to submit your change to be included in future versions of nopCommerce.
Augmented entities in Nop.Core project tree
3. Utilize Event Consumers
Where possible, instead of overriding something in nop.services, use an IConsumer as a less invasive method for your customization. Apart from EntityInserted/Updated/Deleted events, others like OrderPlacedEvent and CustomerLoggedInEvent I find common use for. To see what’s available, check out usages of IEventPublisher in nopCommerce’s source.
Example consuming OrderPlacedEvent
{
public OrderPlacedEventConsumer ( )
{
}
public void HandleEvent (OrderPlacedEvent eventMessage )
{
var order = eventMessage . Order ;
//your extension logic here
}
}
By utilizing IConsumers we are able to add our functionality without risk of breaking other installed plugins.
4. Utilize Custom Properties Dictionary
Every model that inherits BaseNopModel contains a Dictionary<string, object> property CustomProperties that we can use in situations where you’re unable to extend the model itself. Here’s a quick example of adding an additional property to each of a list of order models:
{
var result = base . PrepareOrderListModel (searchModel ) ;
foreach ( var item in result . Data )
{
var additionalData = GetAdditionalData (item . Id ) ;
item . CustomProperties [ "MyCustomProperty1" ] = additionalData ;
}
return result ;
}
Getting these values in a view is fairly verbose, so I like to use an extension method to simplify. Here’s a quick-and-dirty example:
{
if (model . CustomProperties . ContainsKey (key ) )
{
try
{
var retVal = model . CustomProperties [key ] ;
return (T )retVal ;
}
catch { ; }
}
return default (T ) ;
}
CustomProperties can also be used in a kendo grid:
field : "CustomProperties.MyCustomProperty",
title : "My Custom Property"
} ]
5. Utilize AttributesXml
Generic attributes are a quick way to associate non-searchable information to any BaseEntity, but require additional data calls. To remove this performance hit, store information in AttributesXml when possible. The following example can be used to create extension methods on Order / OrderItem / ShoppingCartItem entities for getting & setting custom data. This new xml is invisible to ProductAttributeParser and ProductAttributeFormatter and handles type conversion like a GenericAttribute:
{
try
{
var xmlDoc = new XmlDocument ( ) ;
if ( String . IsNullOrEmpty (attributesXml ) )
{
var element1 = xmlDoc . CreateElement ( "Attributes" ) ;
xmlDoc . AppendChild (element1 ) ;
}
else
{
xmlDoc . LoadXml (attributesXml ) ;
}
var rootElement = (XmlElement )xmlDoc . SelectSingleNode ( @"//Attributes" ) ;
//find existing
var node = xmlDoc . SelectSingleNode ( @"//Attributes/" + key ) ;
//create new one if not found
if (node == null )
{
node = xmlDoc . CreateElement (key ) ;
rootElement . AppendChild (node ) ;
}
node . InnerText = CommonHelper . To ( value ) ;
return xmlDoc . OuterXml ;
}
catch (Exception exc )
{
return null ;
}
}
public static T GetCustomAttribute ( string attributesXml, string key )
{
try
{
var xmlDoc = new XmlDocument ( ) ;
if ( String . IsNullOrEmpty (attributesXml ) )
{
var element1 = xmlDoc . CreateElement ( "Attributes" ) ;
xmlDoc . AppendChild (element1 ) ;
}
else
{
xmlDoc . LoadXml (attributesXml ) ;
}
//find existing
var node = xmlDoc . SelectSingleNode ( @"//Attributes/" + key ) ;
//create new one if not found
if (node != null )
{
var retVal = CommonHelper . To (node . InnerText ) ;
return retVal ;
}
}
catch { ; }
return default (T ) ;
}
6. Cache Management
With performance in mind, consider implementing Entity caching with ICacheManager (per request) and model caching with IStaticCacheManager where it makes sense. For implementation, it’s a good rule-of-thumb that your first resource for best-practices be nop source code. nopCommerce’s caching features make it easy for developers to ensure their plugin’s performance.
7. Adding an admin ajax tab (nop 3.8+)
To display custom Order / Customer / Product data in the admin interface, it usually makes sense to create a separate tab as part of its tab-strip. To accomplish this, you’d implement IConsumer<AdminTabStripCreated> and append script conditionally based on the tab-strip name. To benefit page load-times it’s a good idea to get its content asynchronously on tab click – for this I’ve written a handy extension method:
{
var htmlString = ""
+ "$(document).ready(function() {"
+ $ "$(" <li ><a data -tab -name = "{tabName}" data -toggle = "tab" href = "#{tabName}" id = "_tab{tabName}" > "
+ tabTitle
+ $" </a ></li > ").appendTo('#{eventMessage.TabStripName} .nav-tabs:first');"
+ $ "$(" <div class = "tab-pane" id = "{tabName}" ><div style = "padding:30px;height:200px" > "
+ $" <h4 >One moment ...</h4 ></div ></div > ").appendTo('#{eventMessage.TabStripName} .tab-content:first');"
+ $ "$('#_tab{tabName}').click(function(){{"
+ $ "if($('#{tabName}').data('tab-loaded')){{ return; }}"
+ $ "$.get('{contentUrl}', function(result) {{"
+ $ "$('#{tabName}').html(result).data('tab-loaded',true);"
+ "});"
+ "});"
+ "});"
+ "" ;
eventMessage . BlocksToRender . Add ( new HtmlString (htmlString ) ) ;
}
8. Automatically copy views to output directory
One of the bottlenecks of plugin development speed is the need to re-build your project after every view / css / script file update. If you’re impatient like me, you’ll want a way around this – so I wrote a console app that monitors specified plugin directories and automatically pushes updates to the output directory upon save, which I’ve created a git repository for – though at the time of writing this was informed that one of our interns had taken it a step further and created a VS extension which deserves a look: https://github.com/alexHayes08/NopyCopy
Hopefully you’ve found some of this helpful or insightful! As an open-source platform that stays on the cutting-edge of .NET, nopCommerce is rich in value, and we’re fond of watching it continue to grow and flourish.