我的NopCommerce之旅(9): 编写Plugin实例
一、基础介绍
——In computing, a plug-in (or plugin) is a set of software components that add specific abilities to a larger software application (Wikipedia).
Plugin,即插件,用来做NopCommerce的功能扩展。NopCommerce源码本身提供了一些插件供参考使用。本篇文章通过阅读官方文档进行实践总结,主要讲解如何编写一个数据持久化的NopCommerce插件。
二、效果展示
当打开前台产品详细时,系统会自动追踪并记录用户访问的产品信息、访问的用户信息及其IP地址信息等。
三、编写步骤
1.新建项目,项目名为Nop.Plugin.Other.ProductViewTracker,注意项目位置需统一放置在..\plugins\下,与输出位置保持统一。
2.添加Description.txt文件,该文件为必备文件,且格式需保持统一。该文件描述插件相关信息,并作为系统识别插件依据。
3.添加必要的文件夹,名称可根据个人习惯,这里为保持命名规则统一,命名如下Controllers、Data、Domain、Services。
4.添加dll引用,且设置属性"Copy Local"为False。本项目引用如图所示。
5.添加实体类及其数据库映射,添加数据库访问上下文。
5.1 实体类TrackingRecord,继承基类BaseEntity
1 using Nop.Core; 2 3 namespace Nop.Plugin.Other.ProductViewTracker.Domain 4 { 5 public class TrackingRecord : BaseEntity 6 { 7 public virtual int ProductId { get; set; } 8 public virtual string ProductName { get; set; } 9 public virtual int CustomerId { get; set; } 10 public virtual string IpAddress { get; set; } 11 public virtual bool IsRegistered { get; set; } 12 } 13 }
5.2 数据库表映射类TrackingRecordMap,继承EntityTypeConfiguration<T>
1 using Nop.Plugin.Other.ProductViewTracker.Domain; 2 using System.Data.Entity.ModelConfiguration; 3 4 namespace Nop.Plugin.Other.ProductViewTracker.Data 5 { 6 public class TrackingRecordMap : EntityTypeConfiguration<TrackingRecord> 7 { 8 public TrackingRecordMap() 9 { 10 ToTable("ProductViewTracking"); 11 12 HasKey(m => m.Id); 13 Property(m => m.ProductId); 14 Property(m => m.ProductName).HasMaxLength(400); 15 Property(m => m.IpAddress); 16 Property(m => m.CustomerId); 17 Property(m => m.IsRegistered); 18 } 19 } 20 }
5.3 数据库访问上下文,安装插件时生成并执行建表脚本,卸载插件时删除该表
1 using Nop.Data; 2 using System; 3 using System.Collections.Generic; 4 using System.Data.Entity; 5 using Nop.Core; 6 using System.Data.Entity.Infrastructure; 7 8 namespace Nop.Plugin.Other.ProductViewTracker.Data 9 { 10 public class TrackingRecordObjectContext : DbContext, IDbContext 11 { 12 public TrackingRecordObjectContext(string nameOrConnectionString) : base(nameOrConnectionString) { } 13 14 #region Implementation of IDbContext 15 16 public bool AutoDetectChangesEnabled 17 { 18 get 19 { 20 throw new NotImplementedException(); 21 } 22 23 set 24 { 25 throw new NotImplementedException(); 26 } 27 } 28 29 public bool ProxyCreationEnabled 30 { 31 get 32 { 33 throw new NotImplementedException(); 34 } 35 36 set 37 { 38 throw new NotImplementedException(); 39 } 40 } 41 42 public void Detach(object entity) 43 { 44 throw new NotImplementedException(); 45 } 46 47 public int ExecuteSqlCommand(string sql, bool doNotEnsureTransaction = false, int? timeout = default(int?), params object[] parameters) 48 { 49 throw new NotImplementedException(); 50 } 51 52 public IList<TEntity> ExecuteStoredProcedureList<TEntity>(string commandText, params object[] parameters) where TEntity : BaseEntity, new() 53 { 54 throw new NotImplementedException(); 55 } 56 57 public IEnumerable<TElement> SqlQuery<TElement>(string sql, params object[] parameters) 58 { 59 throw new NotImplementedException(); 60 } 61 62 public new IDbSet<TEntity> Set<TEntity>() where TEntity : BaseEntity 63 { 64 return base.Set<TEntity>(); 65 } 66 67 protected override void OnModelCreating(DbModelBuilder modelBuilder) 68 { 69 modelBuilder.Configurations.Add(new TrackingRecordMap()); 70 71 base.OnModelCreating(modelBuilder); 72 } 73 74 #endregion 75 76 public string CreateDatabaseInstallationScript() 77 { 78 return ((IObjectContextAdapter)this).ObjectContext.CreateDatabaseScript(); 79 } 80 81 public void Install() 82 { 83 Database.SetInitializer<TrackingRecordObjectContext>(null); 84 85 Database.ExecuteSqlCommand(CreateDatabaseInstallationScript()); 86 SaveChanges(); 87 } 88 89 public void Uninstall() 90 { 91 var dbScript = "DROP TABLE ProductViewTracking"; 92 Database.ExecuteSqlCommand(dbScript); 93 SaveChanges(); 94 } 95 } 96 }
6.添加业务服务类。
6.1 服务接口
1 using Nop.Plugin.Other.ProductViewTracker.Domain; 2 3 namespace Nop.Plugin.Other.ProductViewTracker.Services 4 { 5 public interface IViewTrackingService 6 { 7 /// <summary> 8 /// Logs the specified record. 9 /// </summary> 10 /// <param name="record">The record.</param> 11 void Log(TrackingRecord record); 12 } 13 }
6.2 业务服务类
1 using Nop.Core.Data; 2 using Nop.Plugin.Other.ProductViewTracker.Domain; 3 4 namespace Nop.Plugin.Other.ProductViewTracker.Services 5 { 6 public class ViewTrackingService : IViewTrackingService 7 { 8 private readonly IRepository<TrackingRecord> _trackingRecordRepository; 9 10 public ViewTrackingService(IRepository<TrackingRecord> trackingRecordRepository) 11 { 12 _trackingRecordRepository = trackingRecordRepository; 13 } 14 15 public void Log(TrackingRecord record) 16 { 17 _trackingRecordRepository.Insert(record); 18 } 19 } 20 }
7.实现依赖注入的注册接口。
1 using Nop.Core.Infrastructure.DependencyManagement; 2 using Autofac; 3 using Nop.Core.Configuration; 4 using Nop.Core.Infrastructure; 5 using Nop.Plugin.Other.ProductViewTracker.Services; 6 using Nop.Web.Framework.Mvc; 7 using Nop.Plugin.Other.ProductViewTracker.Data; 8 using Nop.Data; 9 using Nop.Plugin.Other.ProductViewTracker.Domain; 10 using Nop.Core.Data; 11 using Autofac.Core; 12 13 namespace Nop.Plugin.Other.ProductViewTracker 14 { 15 public class DependencyRegistrar : IDependencyRegistrar 16 { 17 private const string CONTEXT_NAME = "nop_object_context_product_view_tracker"; 18 19 public void Register(ContainerBuilder builder, ITypeFinder typeFinder, NopConfig config) 20 { 21 builder.RegisterType<ViewTrackingService>().As<IViewTrackingService>().InstancePerLifetimeScope(); 22 23 //data context 24 this.RegisterPluginDataContext<TrackingRecordObjectContext>(builder, CONTEXT_NAME); 25 26 //override required repository with our custom context 27 builder.RegisterType<EfRepository<TrackingRecord>>() 28 .As<IRepository<TrackingRecord>>() 29 .WithParameter(ResolvedParameter.ForNamed<IDbContext>(CONTEXT_NAME)) 30 .InstancePerLifetimeScope(); 31 } 32 33 public int Order 34 { 35 get { return 1; } 36 } 37 } 38 }
8.添加MVC Controller。
1 using Nop.Core; 2 using Nop.Core.Domain.Catalog; 3 using Nop.Core.Domain.Customers; 4 using Nop.Core.Plugins; 5 using Nop.Plugin.Other.ProductViewTracker.Domain; 6 using Nop.Plugin.Other.ProductViewTracker.Services; 7 using Nop.Services.Catalog; 8 using Nop.Web.Framework.Controllers; 9 using System.Web.Mvc; 10 11 namespace Nop.Plugin.Other.ProductViewTracker.Controllers 12 { 13 public class TrackingController : BasePluginController 14 { 15 private readonly IProductService _productService; 16 private readonly IViewTrackingService _viewTrackingService; 17 private readonly IWorkContext _workContext; 18 19 public TrackingController(IWorkContext workContext, 20 IViewTrackingService viewTrackingService, 21 IProductService productService, 22 IPluginFinder pluginFinder) 23 { 24 _workContext = workContext; 25 _viewTrackingService = viewTrackingService; 26 _productService = productService; 27 } 28 29 [ChildActionOnly] 30 public ActionResult Index(int productId) 31 { 32 //Read from the product service 33 Product productById = _productService.GetProductById(productId); 34 35 //If the product exists we will log it 36 if (productById != null) 37 { 38 //Setup the product to save 39 var record = new TrackingRecord(); 40 record.ProductId = productId; 41 record.ProductName = productById.Name; 42 record.CustomerId = _workContext.CurrentCustomer.Id; 43 record.IpAddress = _workContext.CurrentCustomer.LastIpAddress; 44 record.IsRegistered = _workContext.CurrentCustomer.IsRegistered(); 45 46 //Map the values we're interested in to our new entity 47 _viewTrackingService.Log(record); 48 } 49 50 //Return the view, it doesn't need a model 51 return Content(""); 52 } 53 } 54 }
9.实现路由接口。
1 using Nop.Web.Framework.Mvc.Routes; 2 using System.Web.Mvc; 3 using System.Web.Routing; 4 5 namespace Nop.Plugin.Other.ProductViewTracker 6 { 7 public class RouteProvider : IRouteProvider 8 { 9 public int Priority 10 { 11 get 12 { 13 return 0; 14 } 15 } 16 17 public void RegisterRoutes(RouteCollection routes) 18 { 19 routes.MapRoute("Nop.Plugin.Other.ProductViewTracker.Log", 20 "tracking/productviews/{productId}", 21 new { controller = "Tracking", action = "Index" }, 22 new[] { "Nop.Plugin.Other.ProductViewTracker.Controllers" } 23 ); 24 } 25 } 26 }
10.添加插件类,继承插件基类,重载安装和卸载方法。
1 using Nop.Core.Plugins; 2 using Nop.Plugin.Other.ProductViewTracker.Data; 3 4 namespace Nop.Plugin.Other.ProductViewTracker 5 { 6 public class ProductViewTrackerPlugin : BasePlugin 7 { 8 private readonly TrackingRecordObjectContext _context; 9 10 public ProductViewTrackerPlugin(TrackingRecordObjectContext context) 11 { 12 _context = context; 13 } 14 15 public override void Install() 16 { 17 _context.Install(); 18 base.Install(); 19 } 20 21 public override void Uninstall() 22 { 23 _context.Uninstall(); 24 base.Uninstall(); 25 } 26 } 27 }
11.在NopCommerce的前台,加入插件的应用代码。这里,我们在页面Nop.Web\Views\Product\ProductTemplate.Simple.cshtml中插入应用代码。
@Html.Action("Index", "Tracking", new { productId = Model.Id })
如图