Hello,

There are times where you have to extend NopCommerce core libraries, and that is quite annoying to maintain. I was thinking in how to reduce that pain and I think I came up with a good idea to reduce the pain:

In order to avoid touching the core you can have 3 libraries called:
Nop.Core.Custom
Nop.Data.Custom
Nop.Services.Custom

Whatever code you put in those libraries will end up reweaved in the associated main libraries using Mono.Cecil.

The other projects will use the reweaved libraries transparently and you'll have an easily maintained codebase even if you modified the core libraries.

Those 3 custom libraries can be part of the NopCommerce solution but they will be empty initially so no reweaving will be made.

In the build process there will be a reweaving step that checks for added clases, methods, enums, etc in the custom libraries and adds them to the core library. In case of similar classes, methods, enum values, etc. the custom ones overwrite the core ones. This provides maximum flexibility and maintainability

And if the Nop core team provides a little of support for this idea, some trivial source core modifications will make developers life a lot easier.

For example, imagine I want to add a new value to the AttributeControlType enumeration.

In Nop.Core.Custom I will add a file with the following enum:


namespace Nop.Core.Domain.Catalog
{
    /// <summary>
    /// Represents an attribute control type
    /// </summary>
    public enum AttributeControlType
    {
        /// <summary>
        /// Dropdown list
        /// </summary>
        DropdownList = 1,
        /// <summary>
        /// Radio list
        /// </summary>
        RadioList = 2,
        /// <summary>
        /// Checkboxes
        /// </summary>
        Checkboxes = 3,
        /// <summary>
        /// TextBox
        /// </summary>
        TextBox = 4,
        /// <summary>
        /// Multiline textbox
        /// </summary>
        MultilineTextbox = 10,
        /// <summary>
        /// Datepicker
        /// </summary>
        Datepicker = 20,
        /// <summary>
        /// File upload control
        /// </summary>
        FileUpload = 30,
        /// <summary>
        /// Color squares
        /// </summary>
        ColorSquares = 40,
        /// <summary>
        /// Image squares
        /// </summary>
        ImageSquares = 45,
        /// <summary>
        /// Read-only checkboxes
        /// </summary>
        ReadonlyCheckboxes = 50,
        /// <summary>
        /// Slider Control
        /// </summary>
        Slider = 100,
    }
}


Note that the enum has a new value that was not in the core library (Slider = 100).

The following code (that uses Mono.Cecil) merges both assemblies adding the new enumeration value to the AttributeControlType:


var path = @"D:\NopCommerce\src\nopCommerce\src";
var coreAssemblyDefinition = AssemblyDefinition.ReadAssembly($@"{path}\Libraries\Nop.Core\bin\Debug\net461\Nop.Core.dll");
var customAssemblyDefinition = AssemblyDefinition.ReadAssembly($@"{path}\CustomCode\Nop.Core.Custom\bin\Debug\Nop.Core.Custom.dll");

var coreEnumType = coreAssemblyDefinition.MainModule.Types.FirstOrDefault(t => t.FullName == "Nop.Core.Domain.Catalog.AttributeControlType");
var customEnumType = customAssemblyDefinition.MainModule.Types.FirstOrDefault(t => t.FullName == "Nop.Core.Domain.Catalog.AttributeControlType");


foreach (var enumValue in customEnumType.Fields.Where(f => !coreEnumType.Fields.Select(cf => cf.Name).Contains(f.Name)))
{
    var newEnumValue = new FieldDefinition(enumValue.Name, enumValue.Attributes, coreEnumType);
    newEnumValue.Constant = enumValue.Constant;
    coreEnumType.Fields.Add(newEnumValue);
}

coreAssemblyDefinition.Write($@"{path}\Libraries\Merged\Nop.Core.dll");


You can see this working in the patched assembly:



With a bit of support of the nop core team whenever the AttributeControlType is used, some extension methods can be called to handle additional entries. For example, in PrepareCustomerAttributeModel in the CustomController class, you have a switch (attribute.AttributeControlType) statement, that handles different AttributeControlType values. In the default part you can call a method called HandleCustomAttributeControlTypes that does nothing.

However, I can also reweave the HandleCustomAttributeControlTypes method so I provide my implementation in the Nop.Core.Custom project and that way with only a redefined enum and a redefined method, I have been able to change the core without pain.

Comments?