Azure Nopcommerce File Upload Fails After Processing
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 costless and paid plugins.
Plugins enable developers to add new features and/or customize a UI theme without modifying nopCommerce source code. This is of import for stable running of a spider 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 evolution documentation tin can be plant here.
nopCommerce source code containing several free plugins included in the distribution package can be found hither.
Requirements
Plugin evolution for nopCommerce would require prior feel with Entity Framework Core two 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 Grade Library project template is used for plugin development. It is recommended to shop a new project in \Plugins solution folder.
Naming conventions for plugin projects are the post-obit: Nop.Plugin.{Grouping}.{Name}. east.chiliad. Nop.Plugin. Payments.PayPalStandard. It is also common to use the post-obit naming template: {Solution namespace}.{Name}Plugin. east.chiliad. Company.Solution.PaymentMethodNamePlugin
After creating a project, you should edit *.csproj file by adding PropertyGroup chemical element to betoken the project'due south output path and Target.
Instance of *.csproj file: value if PLUGIN_OUTPUT_DIRECTORY should match plugin'southward name, e.g. Payments.PayPalStandard.
LISTING 1. PLUGIN *.CSPROJ FILE
<Project Sdk="Microsoft.Internet.Sdk"> <PropertyGroup> <TargetFramework>netcoreapp2.one</TargetFramework> </PropertyGroup> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'"> <OutputPath>..\..\Presentation\Nop.Spider 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 Proper noun="NopTarget" AfterTargets="Build"> <!-- Delete unnecessary libraries from plugins path --> <MSBuild Projects="$(MSBuildProjectDirectory)\..\..\Build\ClearPluginAssemblies.proj" Backdrop="PluginPath=$(MSBuildProjectDirectory)\$(OutDir)" Targets="NopClear" /> </Target> </Project>
Next, create a plugin.json file in the root of your projection to describe the plugin.
Annotation: I this manual we consider nopCommerce four.10. nopCommerce three.90 and beneath take description.txt file with a unlike format.
Listing 2. PLUGIN.JSON EXAMPLE
{ "Group": "Payment methods", "FriendlyName": "PayPal Standard", "SystemName": "Payments.PayPalStandard", "Version": "1.49", "SupportedVersions": [ "4.ten" ], "Writer": "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 holding Copy to Output Directory since information technology's necessary to copy this file to /Plugins folder of the web-awarding (Nop.Web) afterward it is built.
SystemName should exist unique. FileName should friction match a class library name.
Group property can accept any value; however, it is recommended to utilize ane of the following values reflecting the purpose of the plugin:
- DiscountRules – rules for applying discounts.
- ExchangeRates – currency commutation charge per unit providers.
- ExternalAuth – external authentication providers.
- Payments – payment gateway integrations.
- Pickup – pickup point providers.
- Shipping – shipping toll adding and tracking providers.
- Tax – tax calculation providers.
- Widgets – widgets.
- Misc – whatsoever other plugins that can't be associated with one of the above categories.
Plugins can exist viewed, installed, configured or uninstalled in the corresponding admin section:
FIG i. Listing OF PLUGINS
Standard settings of an installed plugin tin can exist edited by clicking Edit button.
FIG 2. STANDARD PLUGIN SETTINGS
FIG 3. USER-DEFINED PLUGIN SETTINGS
If a plugin has user-defined settings unique to this plugin (encounter department User-defined plugin settings), the Configure button is available for editing these settings.
Logging
While developing controllers, services, and tasks, it is recommended to employ an inbuilt logging service Nop.Services.Logging.ILogger to log information and error letters.
LISTING iii. LOGGING Data Message
//log data near the successful renew of the access token _logger.Information(_localizationService.GetResource("Plugins.Payments.Foursquare.RenewAccessToken.Success"));
During exception treatment, exception object tin can also be passed to the logging service:
LISTING 4. LOGGING EXCEPTION
catch (Exception exception) { //log fault on renewing of the access token _logger.Mistake(_localizationService.GetResource("Plugins.Payments.Square.RenewAccessToken .Error"), exception); }
Log is stored in the database and is displayed in a respective admin section:
FIG 4. LOG ADMIN SECTION
Dependency injection
nopCommerce uses Autofac as an inversion of a command container. When classes are being adult, all required service interfaces are defined as constructor parameters and their instances are automatically resolved and initialized during the respective grade example initializing.
To register classes by implementing respective service interfaces y'all should create an implementation of Nop.Core.Infrastructure.DependencyManagement.IDependencyRegistrar. nopCommerce scans through all assemblies to detect classes implementing a respective interface and registers them.
Plugin interface and base of operations form
Plugin should incorporate an implementation of Nop.Cadre.Plugins.IPlugin. Abstract class Nop.Cadre.Plugins.BasePlugin which implements this interface can be used equally a base course.
For some specific type of a plugin a respective derived interface that corresponds to the plugin'south purpose should exist implemented (run across FIG 5. Plugin interfaces below).
Install() and Uninstall() methods of a base interface are required to exist implemented to mark a plugin as installed and uninstalled respectively. (see Listing five 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.
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 await at the examples of plugins of unlike 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'due south 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-divers settings can be either global or per store (nopCommerce allows to run multiple eCommerce stores in one nopCommerce setup).
Recommended proper noun for the model is ConfigurationModel.Configuration and {PluginName}Controller is for view and controller respectively.
A base course for all nopCommerce models is Nop.Web.Framework.Models.BaseNopModel from Nop.Web.Framework projection. NopResourceDisplayName attribute is used for localizing properties. It contains a respective resource key as cord.
If settings are applied per store, int ActiveStoreScopeConfiguration property is added to the model. New special backdrop are added to the model for each setting holding with the following naming template – {PropertyName}_OverrideForStore of bool blazon.
LISTING half-dozen. USER-Defined SETTINGS MODEL
public class ConfigurationModel : BaseNopModel { public int ActiveStoreScopeConfiguration { become; set up; } [NopResourceDisplayName("Plugins.Payments.PayPalStandard.Fields.UseSandbox")] public bool UseSandbox { get; set; } public bool UseSandbox_OverrideForStore { get; fix; }
Configuration view uses special layout _ConfigurePlugin. If settings are practical per store, StoreScopeConfiguration method is chosen asynchronously.
LISTING seven. USER-Defined SETTINGS VIEW
@model Nop.Plugin.Payments.PayPalStandard.Models.ConfigurationModel @inject Nop.Core.IWebHelper webHelper @{ Layout = "_ConfigurePlugin"; } @expect Component.InvokeAsync("StoreScopeConfiguration")
HTML form is used to shop settings server-side. Fields for each setting belongings are implemented using Bootstrap markup and helpers (meet sp Nop.Spider web.Framework.TagHelpers).
List 8. Class MARKUP
<form asp-controller="PaymentPayPalStandard" asp-action="Configure" method="post"> <div class="console-group"> <div class="console console-default"> <div form="console-body"> @Html.Raw(T("Plugins.Payments.PayPalStandard.Instructions", $"{webHelper.GetStoreLocation()}Plugins/PaymentPayPalStandard/PDTHandler")) <div form="course-group"> <div form="col-doctor-3"> <nop-override-shop-checkbox asp-for="UseSandbox_OverrideForStore" asp-input="UseSandbox" asp-store-scope="@Model.ActiveStoreScopeConfiguration" /> <nop-label asp-for="UseSandbox" /> </div> <div class="col-md-ix"> <nop-editor asp-for="UseSandbox" /> <span asp-validation-for="UseSandbox"></span> </div> </div> ... <div class="course-group"> <div form="col-md-nine col-md-offset-three"> <input type="submit" proper name="salvage" form="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.Spider web.Framework.Controllers.BasePaymentController in case of a payment method. Both of them are inherited from Nop.Web.Framework.Controllers.BaseController.
FIG 6. PLUGIN CONTROLLERS
Controller should implement ii Configure actions:
- Reading user-divers settings – loading settings using nopCommerce service, passing data to model, returning view with model.
- Saving user-defined settings – passing information from model to settings and saving to database using nopCommerce service.
For both methods, Surface area attribute should be specified as "Admin" (see Listing 9 below).
Nop.Services.Configuration.ISettingService is used for working with settings. If you lot need to shop settings per shop, Nop.Core. IStoreContext is used. Nop.Services.Security.IPermissionService is used to validate permissions to modify settings.
List 9. LOADING USER-Defined SETTINGS
[AuthorizeAdmin] [Area(AreaNames.Admin)] public IActionResult Configure() { if (!_permissionService.Qualify(StandardPermissionProvider.ManagePaymentMethods)) return AccessDeniedView(); //load settings for a called 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 => ten.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); } render View("~/Plugins/Payments.PayPalStandard/Views/Configure.cshtml", model); }
It's important to clear cache earlier saving settings so they take upshot (see Listing 10 below).
List 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 called 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; /* Nosotros practice not clear enshroud after each setting update. * This behavior can increase performance considering cached settings will not exist cleared * and loaded from database after each update */ _settingService.SaveSettingOverridablePerStore(payPalStandardPaymentSettings, x => x.UseSandbox, model.UseSandbox_OverrideForStore, storeScope, imitation); _settingService.SaveSettingOverridablePerStore(payPalStandardPaymentSettings, ten => x.BusinessEmail, model.BusinessEmail_OverrideForStore, storeScope, false); _settingService.SaveSettingOverridablePerStore(payPalStandardPaymentSettings, x => x.PdtToken, model.PdtToken_OverrideForStore, storeScope, false); _settingService.SaveSettingOverridablePerStore(payPalStandardPaymentSettings, ten => 10.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, faux); //at present clear settings cache _settingService.ClearCache(); SuccessNotification(_localizationService.GetResource("Admin.Plugins.Saved")); return Configure(); }
Controller tin can implement not just configuration actions but other actions implementing plugin UI besides.
Localization
If a plugin uses letters displayed to user, tooltips and other UI texts, then respective texts should be implemented every bit localization resources stored for each used linguistic communication using Nop.Services.Localization.ILocalizationService.
User with admin permissions can then edit these resources in the admin area.
As described above (see department "Plugin interface and base of operations class"), plugin form 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-Divers SETTINGS AND LOCALIZATION
public override void Install() { //settings _settingService.SaveSetting(new PayPalStandardPaymentSettings { UseSandbox = true }); //locales _localizationService.AddOrUpdatePluginLocaleResource("Plugins.Payments.PayPalStandard. Fields.AdditionalFee", "Boosted fee"); ... base.Install(); }
Naming convention for a localization resource key is the following: {PluginName}.{ResourceType}.{ResourceName}.
AddOrUpdatePluginLocaleResource method has a third optional parameter (cord languageCulture = nothing) that indicates linguistic communication and civilisation.
List 12. DELETING USER-DEFINED SETTINGS AND LOCALIZATION Resource DURING UNINSTALLING PLUGIN
public override void Uninstall() { //settings _settingService.DeleteSetting<PayPalStandardPaymentSettings>(); //locales _localizationService.DeletePluginLocaleResource("Plugins.Payments.PayPalStandard.Fields. AdditionalFee"); ... base of operations.Uninstall(); }
Resource can exist loaded using GetResource method (see example in Listing eight above).
Localization resource are rendered in markup using tags and helpers (due east.chiliad. T, nop-characterization) from Nop.Spider web.Framework.TagHelpers (see example in List 6 in a higher place)
Custom entities and database tables
A plugin can extend and/or change nopCommerce database structure without modifying nopCommerce code base. (run across the source code of Nop.Plugin.Pickup.PickupInStore).
Primal steps to perform:
- Define new entities in a plugin projection
- 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 projection 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 binder. Database context is inherited from Microsoft.EntityFrameworkCore.DbContext and implements Nop.Information.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 thirteen. 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 exist implemented: Nop.Cadre.Infrastructure.INopStartup. Recommended naming convention for a new class – PluginDbStartup. Recommended projection folder for a new class is Infrastructure.
Database mappings are created for each entity. Mappings are inherited from Nop.Information.Mapping.NopEntityTypeConfiguration.
Listing xiv. MAPPING ENTITY TO ITS TABLE
public override void Configure(EntityTypeBuilder<StorePickupPoint> builder) { architect.ToTable(nameof(StorePickupPoint)); builder.HasKey(point => bespeak.Id); builder.Holding(indicate => betoken.PickupFee).HasColumnType("decimal(18, 4)"); }
Services for new entities
To implement business concern logic for new entities, interfaces and classes of corresponding services should exist created in Services projection binder.
To register database context and services classes, Nop.Cadre.Infrastructure.DependencyManagement.IDependencyRegistrar should exist implemented. Recommended proper name for implementation is DependencyRegistrar. Recommended binder for implementation is Infrastructure project binder. Each entity likewise has an implementation of Nop.Cadre.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 information exchange with third-political party systems. E.g. exporting itemize information to external marketplace or importing orders and customers from CRM. nopCommerce also has inbuilt scheduled tasks such as Clear cache, Transport emails or Delete guests.
Fundamental 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 projection folder. It tin can also be Services project folder. Nop.Services.Tasks.IScheduleTask defines sole method Execute. Instance of implementation can be found in Nop.Plugin.Payments.Foursquare 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. Appropriately, job should exist 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 exist formed using Nop.Core.Domain.Tasks.ScheduleTask class. Information technology is recommended to name a task after the organization name of its blazon. Earlier registering the task, information technology'south recommended to cheque whether it already exists by calling GetTaskByType method using the system name of its type.
Listing 16. DEFINING A Proper noun FOR A Chore
public static string GetTaskName<T>() { return $"{typeof(T).FullName}, {typeof(T).Associates.GetName().Name}"; }
During registering the task its display name, launch interval, error beliefs are defined.
LISTING 17. REGISTERING TASK
string taskName = GetTaskName<ExportCatalogTask>(); ScheduleTask task = _scheduleTaskService.GetTaskByType(taskName); if (job == null) { chore = new ScheduleTask() { Enabled = true, Proper name = "retailCRM: Consign itemize", Seconds = 60 * 60 * four, StopOnError = false, Blazon = taskName, }; _scheduleTaskService.InsertTask(task); }
When the plugin is existence uninstalled, the chore is as well being deleted by its arrangement name in the Uninstall method. If multiple tasks are deployed with the plugin, yous can utilize loop to deleted them in social club to keep the code cleaner.
LISTING xviii. DELETING TASKS DURING UNINSTALLING THE PLUGIN
foreach (string typeName in new string[] { GetTaskName<DownloadHistoryTask>(), GetTaskName<ExportCatalogTask>(), GetTaskName<UploadCustomerTask>(), GetTaskName<UploadOrderTask>() }) { ScheduleTask job = _scheduleTaskService.GetTaskByType(typeName); if (task != zippo) _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 exist executed at any time using Run Now push (meet FIG seven. List of scheduled tasks). Final start date, last finish date and last success date are likewise displayed.
If an error occurred during the task execution, information technology's being logged and can be viewed in the arrangement log. If an error occurred during manual launch using Run at present command, it's besides displayed in UI. As a best practice, it'south recommended to use additional logging (see Listing four. Logging exception).
FIG 7. Listing OF SCHEDULED TASKS
Events subscription
nopCommerce implements outcome-driven architecture and allows to subscribe to various system events. Information technology is useful for developing triggers that run specific business organization logic after specific events occur. Due east.1000. sending data to external systems such as sending new orders to CRM correct subsequently an order was placed. Unlike scheduled tasks, such triggers are beingness executed in real-time without delays. Below is the listing of key events.
Data modification:
- Nop.Core.Events.EntityInsertedEvent – generic event of inserting new information
- Nop.Cadre.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 effect would be generated. E.g., Nop.Core.Events.EntityUpdatedEvent for an order occurs multiple times during the society placement. So, if you only need to handle gild placement, in that location is a number of other high-level events.
Client events:
- Nop.Core.Domain.Customers.CustomerRegisteredEvent – event of registering a client
- Nop.Core.Domain.Customers.CustomerLoggedinEvent – event of signing in
- Nop.Core.Domain.Customers.CustomerLoggedOutEvent – issue of signing out
- Nop.Core.Domain.Customers.CustomerPasswordChangedEvent – event of changing a countersign
- Nop.Core.Domain.Customers.CustomerPermanentlyDeleted – event of deleting a client
Social club 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 – issue of an order payment
- Nop.Core.Domain.Orders.OrderCancelledEvent – event of canceling an order
- Nop.Core.Domain.Orders.OrderRefundedEvent – consequence of an social club refund
Other events:
- Nop.Core.Domain.Blogs.BlogCommentApprovedEvent – effect of blog comment approval
- Nop.Cadre.Domain.Catalog. ProductReviewApprovedEvent – outcome of production review approving
- Nop.Core.Domain.Letters.AdditionTokensAddedEvent – event of adding new tokens to non-campaign bulletin templates
- Nop.Core.Domain.Letters.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 result of adding new tokens to an entity of a specific type
- Nop.Core.Domain.Letters.EmailSubscribedEvent – event of an email subscription
- Nop.Cadre.Domain.Messages.EmailUnsubscribedEvent – issue of canceling an electronic mail 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 result handling logic.
List 19. Treatment EVENT OF DELETING DISCOUNT Dominion
public partial form 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 == zip) return; //delete saved restricted customer part 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 every bit 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 exist 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 too exist developed. To reference additional namespaces _ViewImports.cshtml file should be added to Views projection folder.
Listing twenty. 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 Arrangement.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 two.
User with admin permission can enable and disable widgets in admin section besides as specify their brandish order. Configure and Edit buttons allow editing user-divers and standard settings of a plugin respectively (see FIG ii. Standard plugin settings and FIG 3. User-defined plugin settings).
FIG eight. LIST OF WIDGETS
Generic attributes
nopCommerce has a flexible blueprint blueprint for extending standard entities without modifying database tables. It is a GenericAttribute table that has non-strange key relations with nopCommerce entities and stores their custom attributes.
Beneath is the list and descriptions of GenericAttribute columns:
Column | Type | Description |
ID | int | Chief fundamental |
EntityId | int | non-FK reference to the entity to which an aspect belongs to |
KeyGroup | nvarchar(400) | Entity table where entity attribute that belongs to it is stored. E.g. "Client" for a Customer entity or "Order" for an Social club entity |
Key | nvarchar(400) | Attribute cardinal |
Value | nvarchar(MAX) | Attribute value |
StoreId | int | Store Id if an attribute is defined per store or 0 if the attribute is defined globally |
Example:
Id: i
EntityId: 2
KeyGroup: Customer
Key: FirstName
Value: John
StoreId: 0
A client whose Id equals 2 has a get-go name "John" defined as an aspect globally.
Generic attributes enable to extend entities with custom attributes belongings additional information. For case, a standard Client 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 expanse and common practice is to implement additional editable fields on entities in the admin surface area every bit part of the plugin.
FIG nine. CUSTOM CUSTOMER ATTRIBUTES
Note: you lot should carefully design your plugin earlier choosing between Generic attributes arroyo and extending entity via custom entities and database tables approach (see "Custom entities and database tables department in a higher place"). Generic attributes don't concord foreign keys and all attribute values are strings. So, overuse of GenericAttributes may lead to issues with functioning and information integrity when the format of some attribute has been changed (older records may contain inconsistent values) or entity which is referenced in GenericAttribute tabular array may no longer exist. For this reason, in some cases, you may use GenericAttribute while in other cases you should stick to more complex notwithstanding more correct and scalable approach of modifying the database via a plugin.
Writing and reading generic attributes is washed by using IGenericAttribute service and extension methods on entities (come across 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(client, SystemCustomerAttributeNames.FirstName, model.FirstName); _genericAttributeService.SaveAttribute(client, SystemCustomerAttributeNames.LastName, model.LastName); //reading generic attributes string gender = customer.GetAttribute<cord>(SystemCustomerAttributeNames.Gender) }
Method overriding
One of the core approaches of extending basic nopCommerce business organisation logic in plugin is method overriding. This is one of the well-nigh powerful however simple approaches. Eastward.thou. if we demand to change the behaviour of adding products to cart, nosotros can implement child grade inherited from ShoppingCartService in our plugin and override AddToCart method.
Listing 22. OVERRIDING BASE Course METHOD
public class ExtendedShoppingCartService: ShoppingCartService, IShoppingCartService { public override IList AddToCart(Customer customer, Product production, ShoppingCartType shoppingCartType, int storeId, cord attributesXml = cypher, decimal customerEnteredPrice = 0, DateTime? rentalStartDate = goose egg, DateTime? rentalEndDate = goose egg, int quantity = 1, bool automaticallyAddRequiredProductsIfEnabled = truthful) { //overridden business logic goes here … } }
To avoid copy-pasting code from the base grade' method that should be reused, telephone call base of operations course via base.. It's also a adept practice to pattern your customization in a way that child class contains minimum of code and calls base class' method to reuse its lawmaking.
In club to tell nopCommerce to employ our child class instead of its parent when resolving dependencies, we need to register our class using plugin'south implementation of Nop.Cadre.Infrastructure.DependencyManagement.IDependencyRegistrar.
List 23. REGISTERING CHILD CLASS
public void Annals(ContainerBuilder builder, ITypeFinder typeFinder, NopConfig config) { … builder.RegisterType<ExtendedShoppingCartService>().As<Orders.IShoppingCartService>().InstancePerLifetimeScope(); … }
Later registering child course all IShoppingCartServices instances will be resolved as ExtendedShoppingCartService instead of a standard nopCommerce's ShoppingCartService. In guild to verify that plugin's kid class is registered after its parent, you should specify Nop.Core.Infrastructure.DependencyManagement.IDependencyRegistrar.Society belongings to be higher than in nopCommerce's IDependencyRegistrar implementation. For example, by setting information technology to int.MaxValue. And then child class will be registered after its parent and will accept college priority.
Note: fifty-fifty though method overriding is a powerful customization approach it involves risks that should exist addressed. One of the common risks is the situation of conflicting plugins when several plugins override the aforementioned method which leads to an unexpected behavior. You should minimize using tertiary-political party plugins with airtight source code (because they have unknown method overrides) and should advisedly design your own plugins by fugitive overriding the same method by different plugins or utilise Nop.Cadre.Infrastructure.DependencyManagement.IDependencyRegistrar.Gild property in your plugins to manage priority order when several plugins extending the same methods are deployed.
Source: https://isdk.pro/nopcommerce-plugin-development-manual/
0 Response to "Azure Nopcommerce File Upload Fails After Processing"
Post a Comment