NopCommerce架构分析

很多人都说通过阅读、学习大神们高质量的代码是提高自己技术能力最快的方式之一。我觉得通过阅读NopCommerce的源码,可以从中学习很多企业系统、软件开发的规范和一些新的技术、技巧,可以快速地提高我们的技术能力。所以我最近决定写一个“NopCommerce源码架构详解”的系列,来详细剖析NopCommerce的架构和原理。

Nopcommerce主要用到的技术及特点:

1、Entity Framework

2、ASP.NET mvc

3、IoC容器+依赖注入(Autofac)

4、使用EF中的EntityTypeConfiguration+Repository模式+领域驱动开发

5、插件技术

6、Themes主题技术

7、Ajax

8、Validator验证技术

9、面向接口编程

10、事件通知、日志机制

11、缓存(System.Runtime.Caching.MemoryCache)

12、网站计划任务

13、消息队列

14、多语言支持

15、Jquery UI+kendo UI

16、多网店支持、促销推广、在线支付

17、seo友好支持

18、其它asp.net MVC和c#最新核心技术

Nopcommerce是国外的一个高质量的开源b2c网站系统,最新版是基于Entity Framework6.0和MVC5.0,使用razor模板引擎,有很强的插件机制,包括支付配送功能都是通过插件来实现的,基于xml的多语言版本,非常灵活的语言切换功能,包括在后台都能同时编辑产品的中英文属性,非常适合做外贸,优秀超前的程序架构,性能也非常强大,自定义的产品名称和分类又有很好的seo优化。综合能力远远高于国内的一些程序架构糟糕的.net商城程序,是二次开发和大型b2c架构的首选。3.0开始支持多店。

前台页面效果:

后台管理页面:

NopCommerce最新版的在CodePlex的源码下载

nopcommerce主要从上往下Nop.Web、Nop.Admin、Nop.Web.Framework、Nop插件、Nop.Services、
Nop.Core、Nop.Data。引用的第三方模块EntityFramework,Autofac(控制反转,即依赖注入),telerik.extern.mvc(后台管理用的界面,2.0后开始使用)。

下图是nopcommerce版本3.4的源码结构:

1、Libraries

Libaries文件夹下项目主要是一些公共库代码。

Nop.Core:封装了项目要用的基础核心类,接口。比如领域对象类,缓存类、接口,扩展方法等等。

Nop.Data:EF相关的数据访问相关的类封装和扩展。里面最关键的就是Mapping,Nop采用代码API的形式来建立Model和数据库表之间的映射,命名都是以“表名+Map”的形式。比如:

  1. using System.Data.Entity.ModelConfiguration;
  2. using Nop.Core.Domain.Blogs;
  3. namespace Nop.Data.Mapping.Blogs
  4. {
  5. public partial class BlogCommentMap : EntityTypeConfiguration<BlogComment>
  6. {
  7. public BlogCommentMap()
  8. {
  9. this.ToTable("BlogComment");
  10. this.HasKey(pr => pr.Id);
  11. this.HasRequired(bc => bc.BlogPost)
  12. .WithMany(bp => bp.BlogComments)
  13. .HasForeignKey(bc => bc.BlogPostId);
  14. this.HasRequired(cc => cc.Customer)
  15. .WithMany()
  16. .HasForeignKey(cc => cc.CustomerId);
  17. }
  18. }
  19. }

以后我会详细的说明这个Mapping的怎么实现的以级这样做的好处。

Nop.Services:真正的处理数据的业务层,都是通过面向接口编程,减少对具体实现的依赖。

2、Plugins

Plugins文件夹下是放的插件项目,你也可以按照规则开发属于自己的插件。

3、Presentation

Presentation中文意思是呈现、表现的意思。也就是这文件夹下的项目都是解决方案的表示层。

Nop.Admin:后台管理

Nop.Web:前台Web项目

Nop.Web.Framework:Web及MVC相关扩展和公共类的封装,比如:BaseController,Seo相关,主题Themes,autofac依赖注入DependencyRegistrart等等。

4、Tests

Tests下面放的都是对应项目的单元测试。

 

 

asp.net MVC中Action参数不只是一些基本类型,也支持实体参数。那么从客户端传来的数据如何映射或转换成实体对象呢?就是通过实体绑定类ModelBinder。此系列类在请求转化为后台Controller的Action方法前,捕获传递过来的数据,并对其进行解析和转换,最终为实体类对象。

在系统启动前,Global.asax.cs中的方法Application_Start方法调用下面代码定义参数转换规则。

 

[csharp] view plain copy
 
  1. //model binders  
  2.             ModelBinders.Binders.Add(typeof(BaseNopModel), new NopModelBinder());  

 

NopModelBinder继承DefaultModelBinder承担系统的实体绑定类,但好像只是留一个借口,并没有使用。主要是继承父类的方法,稍有改变的地方是:方法BindModel添加了对NopModel的绑定支持。

 

[csharp] view plain copy
 
  1. public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)  
  2.         {  
  3.             var model = base.BindModel(controllerContext, bindingContext);  
  4.             if (model is BaseNopModel) ((BaseNopModel) model).BindModel(controllerContext, bindingContext);  
  5.             return model;  
  6.         }  


方法GetModelProperties添加了一个过滤方法,只是此方法尚未启用。

 

类BaseNopModel是所有Model的基类,支持对自定义属性的存储。并且有一个绑定到解析器的方法BindModel,只是尚未发现有子类实现此方法。

 

 

系统中对Razor的支持包括两部分,其中之一就是自定义RazorViewEngine

一、自定义RazorViewEngine

 

在Global.asax.cs的Application_Start方法中,注册了自定义视图引擎:

 

[csharp] view plain copy
 
  1. //remove all view engines  
  2. ViewEngines.Engines.Clear();  
  3. //except the themeable razor view engine we use  
  4. ViewEngines.Engines.Add(new ThemeableRazorViewEngine());  


ThemeableRazorViewEngine继承ThemeableBuildManagerViewEngine,

 

ThemeableBuildManagerViewEngine继承ThemeableVirtualPathProviderViewEngine

ThemeableVirtualPathProviderViewEngine继承VirtualPathProviderViewEngine,达到对虚拟路径解析的目的。

 

二、自定义类WebViewPage<TModel>

此类表示呈现使用ASP.NET Razor语法的视图所需的属性和方法。

所以每一个视图都应该继承此类。但是我们在项目中是看不到此继承的,默认情况下Razor会让视图继承自System.Web.Mvc.WebViewPage<TModel>基类。也可以通过修改视图目录(每一个asp.net mvc项目下面都有一个~/Views/目录)下的web.config文件来更改默认基类,NopCommerce就是使用此方法实现自定义WebViewPage类的。除此之外也可以在视图文件中引入命名空间,但这种方法比较繁琐,除非一个项目中有个别视图需要自定义WebViewPage。

 

 

系统支持的语言是有类:Language表示;

多语言资源对应的类为:LocalizedProperty;

当先选择某种语言存储在类中:GenericAttribute;

多语言可以导出为XML文件,当然也支持导出。

IWorkContext及其实体类WebWorkContext为当前运行上下文;用户的登录信息以及一些上下文环境设置都保存在此类中。

具体包括:当前用户信息:CurrentCustomer;当前用户Cookie;货币;语言;税的类型;供应商等;

展现多语言资源的方式有几种:

一、在自定义类WebViewPage<TModel>中放置了方法:T(),通过此方法,网页在展现时获取对应语言的文字。

其实T只是一个代理,代理的定义为:

 

[csharp] view plain copy
 
  1. namespace Nop.Web.Framework.Localization  
  2. {  
  3.     public delegate LocalizedString Localizer(string text, params object[] args);  
  4. }  


此代理返回值类型为LocalizedString,此类继承接口IHtmlString,以保证能正确显示本地化的文字资源。

 

IHtmlString的定义为:

 

[csharp] view plain copy
 
  1. // 摘要:  
  2.     //     表示不应再次进行编码的 HTML 编码的字符串。  
  3.     public interface IHtmlString  
  4.     {  
  5.         // 摘要:  
  6.         //     返回 HTML 编码的字符串。  
  7.         //  
  8.         // 返回结果:  
  9.         //     HTML 编码的字符串。  
  10.         string ToHtmlString();  
  11.     }  


二、通过扩展HtmlHelper

 

类HtmlExtensions扩展了HtmlHelper类,

主要是对一些控件的封装,并支持多语言。

方法 LocalizedEditor<T, TLocalizedModelLocal>是对Telerik的TabStrip控件的封装(也就是多页签控件---Tab控件),的。系统同时支持有多种语言时,多为每种语言显示一个页签,当然仅当需要时才这么做。这里面用到了接口ILocalizedModel和接口ILocalizedModelLocal。接口ILocalizedModel用来标示某Model类支持这种多语言显示,其中里面包括多种语言数据列表Locales,实现接口ILocalizedModelLocal的类就是特定一种语言的数据。LocalizedEditor方法就是根据这些接口的配合实现了支持多种语言页签了。Admin项目使用此方法,Web项目没有使用。

 

[csharp] view plain copy
 
  1. public static HelperResult LocalizedEditor<T, TLocalizedModelLocal>(this HtmlHelper<T> helper, string name,  
  2.             Func<int, HelperResult> localizedTemplate,  
  3.             Func<T, HelperResult> standardTemplate)  
  4.            where T : ILocalizedModel<TLocalizedModelLocal>  
  5.            where TLocalizedModelLocal : ILocalizedModelLocal  
  6.        {  
  7.            return new HelperResult(writer =>  
  8.            {  
  9.                if (helper.ViewData.Model.Locales.Count > 1)  
  10.                {  
  11.                    var tabStrip = helper.Telerik().TabStrip().Name(name).Items(x =>  
  12.                    {  
  13.                        x.Add().Text("Standard").Content(standardTemplate(helper.ViewData.Model).ToHtmlString()).Selected(true);  
  14.                        for (int i = 0; i < helper.ViewData.Model.Locales.Count; i++)  
  15.                        {  
  16.                            var locale = helper.ViewData.Model.Locales[i];  
  17.                            var language = EngineContext.Current.Resolve<ILanguageService>().GetLanguageById(locale.LanguageId);  
  18.                            x.Add().Text(language.Name)  
  19.                                .Content(localizedTemplate  
  20.                                    (i).  
  21.                                    ToHtmlString  
  22.                                    ())  
  23.                                .ImageUrl("~/Content/images/flags/" + language.FlagImageFileName);  
  24.                        }  
  25.                    }).ToHtmlString();  
  26.                    writer.Write(tabStrip);  
  27.                }  
  28.                else  
  29.                {  
  30.                    standardTemplate(helper.ViewData.Model).WriteTo(writer);  
  31.                }  
  32.            });  
  33.        }  


扩展方法NopLabelFor<TModel, TValue>是另外一种多语言实现方式。

 

此方法主要是根据特性DisplayNameAttribute的子类NopResourceDisplayName实现对属性名称的描述。此特性是对Model属性的修饰,以指定属性的名称。

例如类AddNewsCommentModel的属性用NopResourceDisplayName特性指定:

 

[csharp] view plain copy
 
  1. namespace Nop.Web.Models.News  
  2. {  
  3.     public partial class AddNewsCommentModel : BaseNopModel  
  4.     {  
  5.         [NopResourceDisplayName("News.Comments.CommentTitle")]  
  6.         [AllowHtml]  
  7.         public string CommentTitle { get; set; }  
  8.   
  9.         [NopResourceDisplayName("News.Comments.CommentText")]  
  10.         [AllowHtml]  
  11.         public string CommentText { get; set; }  
  12.   
  13.         public bool DisplayCaptcha { get; set; }  
  14.     }  
  15. }  


HtmlHelper的扩展方法NopLabelFor的实现如下:

 

 

[csharp] view plain copy
 
    1. public static MvcHtmlString NopLabelFor<TModel, TValue>(this HtmlHelper<TModel> helper, Expression<Func<TModel, TValue>> expression, bool displayHint = true)  
    2.         {  
    3.             var result = new StringBuilder();  
    4.             var metadata = ModelMetadata.FromLambdaExpression(expression, helper.ViewData);  
    5.             var hintResource = string.Empty;  
    6.             object value = null;  
    7.             if (metadata.AdditionalValues.TryGetValue("NopResourceDisplayName", out value))  
    8.             {  
    9.                 var resourceDisplayName = value as NopResourceDisplayName;  
    10.                 if (resourceDisplayName != null && displayHint)  
    11.                 {  
    12.                     var langId = EngineContext.Current.Resolve<IWorkContext>().WorkingLanguage.Id;  
    13.                     hintResource =  
    14.                         EngineContext.Current.Resolve<ILocalizationService>()  
    15.                         .GetResource(resourceDisplayName.ResourceKey + ".Hint", langId);  
    16.   
    17.                     result.Append(helper.Hint(hintResource).ToHtmlString());  
    18.                 }  
    19.             }  
    20.             result.Append(helper.LabelFor(expression, new { title = hintResource }));  
    21.             return MvcHtmlString.Create(result.ToString());  
    22.         }  

posted on   大西瓜3721  阅读(286)  评论(0编辑  收藏  举报

编辑推荐:
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(1)

导航

点击右上角即可分享
微信分享提示