nopcommerce中文网

nopcommerce是国外asp.net领域一个高质量的b2c开源项目,基于EntityFramework和MVC开发,QQ群1:75272942(2000人超级群,已满) QQ群2:640322459

导航 - 搜索

鱼头鱼尾视频教程 - 微信扫码登录插件视频教程三


相关说明:
-->基于OAuth2.0的授权模式讲解
-->实现AspNet文件夹里面需要用到的类DictionaryExtensions.cs、JsonHelper.cs、MessagingUtilities.cs、WeiXinGraphData.cs、WeiXinOpenIdData.cs及抽象类OpenAuthenticationClient.cs的代码
-->实现Core文件夹里面需要用到的类Provider.cs、OAuthAuthenticationParameters.cs的代码及创建接口IOAuthProviderWeiXinAuthorizer.cs及实现接口的类WeiXinProviderAuthorizer.cs(此类代码暂不实现)
-->实现依赖注入类DependencyRegistrar.cs的代码

该视频由鱼头鱼尾(QQ:875755898)制作
视频下载地址:http://pan.baidu.com/s/1boZmJO7
插件下载地址:Nop.Plugin.ExternalAuth.WeiXin.zip (69.8KB)

鱼头鱼尾视频教程 - 微信扫码登录插件视频教程二


相关说明:
-->实现路由配置
-->定义ConfiguratinModel和LoginModel
-->实现WeiXinExternalAuthSettings类的属性定义
-->实现GetConfigurationRoute和GetPublicInfoRoute方法,扩展安装插件、卸载插件方法
-->实现后台配置Configure方法和前端能显示的PublicInfo方法
-->实现Configure.cshtml和PublicInfo.cshtml视图
备注:
这里说明下:我在新建路由的时候写错了,写成:
            // Login
            routes.MapRoute("Plugin.ExternalAutn.WeiXin.Login",
                "Plugins/ExternalAuthWeiXin/Login",
                new { controller = "Nop.Plugin.ExternalAuth.WeiXin.Controllers", action = "Login" },
                new[] { "Nop.Plugin.ExternalAuth.WeiXin.Controllers" });
其中路由的名字应该是Plugin.ExternalAuth.WeiXin.Login,自己改下,另外在PublicInfo里面的视图的那里也改下。

该视频由鱼头鱼尾(QQ:875755898)制作
视频下载地址:http://pan.baidu.com/s/1dDpwA0T
插件下载地址:Nop.Plugin.ExternalAuth.WeiXin.zip (57.2KB)

鱼头鱼尾视频教程 - 微信扫码登录插件视频教程


相关说明:
-->建立类库
-->配置类库属性
-->复制description.txt文件和web.config文件
-->修改description.txt文件信息
-->引用DLL
-->配置DLL是否复制到本地
-->添加开发过程中需要用到的文件夹、显示图片及类文件
-->配置文件属性(description.txt文件、xx.cshtml视图文件需要设置:复制到输出目录为如果较新则复制,生成操作为内容),(图片文件,css文件,js文件需要设置:复制到输出目录为始终复制,生成操作为内容)
-->实现安装、卸载插件方法

该视频由鱼头鱼尾(QQ:875755898)制作
视频下载地址:http://pan.baidu.com/s/1i3hMWol
插件下载地址:Nop.Plugin.ExternalAuth.WeiXin.zip (47.9KB)

NopCommerce是如何使用Autofac实现依赖注入的?

IOC和DI

IOC中文名被称作控制反转(Inversion of Control),DI被称为依赖注入(Dependency Injection),可参考Martin Fowler的这篇文章来了解这两个概念:IoC容器和DependencyInjection模式。使用控制反转模式开发项目流程是先建立接口,然后再实现类,或许有人不习惯这样的开发方法,但在规模较大的软件架构中,这种方法却可以有效的降低类之间的互相依赖的情况,不但能增加架构的弹性,也能有效的降低软件的复杂度。

如果不考虑控制反转的情况,采用直接创建类,并直接在应用层调用该类,如此一来,应用层的对象就会与BLL(业务逻辑层)对象高度依赖,这样的依赖会导致这两个类无法拆开,从而增加了这个类的维护难度,同时导致了单元测试难以进行。为了解决耦合度问题,从而引入了控制反转的概念。

Autofac介绍

 Autofac是一款IOC框架,比较于其他的IOC框架,如Spring.NET、Unity、Castle等,它更显得轻量级,同时保证了高性能。它具有以下优点:

  1. 和C#语言联系紧密,可以使用C#语言的很多特性,譬如Lambda表达式等;
  2. 较低的学习曲线,只需了解IoC和DI的概念以及在何时需要使用它们即可;
  3. XML配置支持;
  4. 自动装配;
  5. 与ASP.NET MVC3集成;(Orchard也是使用Autofac实现IOC的)

在MVC3项目中使用Autofac

 在MVC3工程中使用Autofac的最好也是最简单的方法是使用NuGet来安装Autofac.Mvc3,安装完成以后,在Global.asax的Application_Start方法中添加如下代码:

protected void Application_Start()
{
    var builder = new ContainerBuilder();
    builder.RegisterControllers(typeof(MvcApplication).Assembly);
    var container = builder.Build();
    DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
    // Other MVC setup...

这样就开启了Controller的依赖注入功能。其中的DependencyResolver是一个全局静态类,MVC3提供了对依赖注入的支持,SetResolver函数用于设置使用哪个Resolver(解析器)来进行依赖注入,这里使用的是Autofac的依赖注入解析器。如果要使用自己的解析器,必须在这里使用SetResolver函数设置。

1. 注册Controller

可以使用下面的方法对特定的Controller进行注册:

var builder =  new ContainerBuilder();
builder.RegisterType<HomeController>().InstancePerRequest();
同时可以使用Autofac提供的RegisterControllers扩展方法来对程序集中所有的Controller一次性的完成注册:
var builder = new ContainerBuilder();
builder.RegisterControllers(Assembly.GetExecutingAssembly());

2. 注册Model Binder

与控制器的注册类似,模型绑定也可以再Global.asax.cs中注册。您可以通过如下操作完成整个程序集的注册:

var builder = newContainerBuilder();
builder.RegisterModelBinders(Assembly.GetExecutingAssembly());
builder.RegisterModelBinderProvider();

您也必须记住使用RegisterModelBinderProvider扩展方法来注册RegisterModelBinderProvider。这个方法用是Autofac对IModelBinderProvider接口的实现。

因为RegisterModelBinders扩展方法通过扫描程序集来添加模型绑定的,所以您需要指定IModelBuilder注册的目标类是什么类型。

[ModelBinderType(typeof(string))]
public class StringBinder : IModelBinder
{
    public override object BindModel(ControllerContext controllerContext,ModelBindingContext bindingContext)
    {
        //do implementation here
    }
}

多行的ModelBuilderTypeAttribute实例可以添加到需要对个类型注册的类中。

3. 注入HTTP抽象类

MVC集成的Autofac模块将会为HTTP抽象类添加HTTP 请求的生命收起范围内的注册。包括依稀抽象类:
  • HttpContextBase
  • HttpRequestBase
  • HttpResponseBase
  • HttpServerUtilityBase
  • HttpSessionStateBase
  • HttpApplicationStateBase
  • HttpBrowserCapabilitiesBase
  • HttpCachePolicyBase
  • VirtualPathProvider
需要使用上面的抽象应该使用容器的RegisterModule方法来添加AutofacWebTypesModule
builder.RegisterModule(newAutofacWebTypesModule());

4. 注入View page

您可以通过在容器创建之前添加ViewRegistrationSource 到容器中使属性注入来使MVC页面可用。
builder.RegisterSource(newViewRegistrationSource());
您的viewpage必须继承MVC类中用于创建,当使用Razor试图引擎时将需要继承WebViewPage类:
public abstract class CustomViewPage : WebViewPage
{
    public IDependencyDependency { getset; }
}
当使用的是webform的试图引擎时,ViewPage,ViewMasterPage和ViewUserControl类都得到相应的支持。
public abstract class CustomViewPage : ViewPage
{
    public IDependencyDependency { getset; }
}
必须确保您实际的试图页面继承了您自定义的基类。在Razor视图引擎.cshtml中可以使用@inherits指令来实现:
@inherits Example.Views.Shared.CustomViewPage
使用webform时可以做如下设置
<%@ PageLanguage="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="Example.Views.Shared.CustomViewPage" %>

5. 对Filter Attribute进行属性注入

为过滤器使用属性注入必须在容器创建之前调用RegisterFilterProvider方法,并将其传到AutofacDependencyResolver
ContainerBuilder builder = new ContainerBuilder();
builder.RegisterControllers(Assembly.GetExecutingAssembly());
builder.Register(c => new Logger()).As<ILogger>().InstancePerHttpRequest();
builder.RegisterFilterProvider();
IContainer container = builder.Build();
DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
然后您就可以为您的过滤器添加属性了,并且
public class CustomActionFilter : ActionFilterAttribute
{
    public ILogger Logger { getset; }
 
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        Logger.Log("OnActionExecuting");
    }
}
下面是类似用户验证过滤器的自定义特性
public class CustomAuthorizeAttribute : AuthorizeAttribute
{
    public ILogger Logger { getset; }
 
    protected override bool AuthorizeCore(HttpContextBase httpContext)
    {
        Logger.Log("AuthorizeCore");
        return true;
    }
}
应用如下:
[CustomActionFilter]
[CustomAuthorizeAttribute]
public ActionResult Index()
{
    // ...
}
关于Autofac更多的信息,可以参考autofac在google code上的wiki文档:http://code.google.com/p/autofac/wiki/Mvc3Integration

NopCommerce是如何使用Autofac实现依赖注入的?

 NopCommerce将所有和Autofac注入相关的工作都放到了EngineContext中,在Global.asax的Application_Start函数的第一句代码即是:

//initialize engine context
EngineContext.Initialize(false);

从这里开始EngineContext的初始化工作,初始化时会创建一个新的NopEngine,参数false指定当NopEngine不为空时是否重新生成一个新的NopEngine。

[MethodImpl(MethodImplOptions.Synchronized)]
public static IEngine Initialize(bool forceRecreate)
{
    if (Singleton<IEngine>.Instance == null || forceRecreate)
    {
        var config = ConfigurationManager.GetSection("NopConfig"as NopConfig;
        Debug.WriteLine("Constructing engine " + DateTime.Now);
        Singleton<IEngine>.Instance = CreateEngineInstance(config);
        Debug.WriteLine("Initializing engine " + DateTime.Now);
        Singleton<IEngine>.Instance.Initialize(config);
    }
    return Singleton<IEngine>.Instance;
}

NopEngine使用单例模式,在整个程序运行期间存在一个实例,代码首先会判断NopEngine是否为空,为空的话则根据web.config中配置的NopConfig节点信息创建一个新的NopEngine实例,然后对该实例进行初始化操作。web.config中的配置信息如下:

  <configSections>
    <section name="NopConfig" type="Easy.Core.Configuration.NopConfig, Easy.Core" requirePermission="false" />
  </configSections>
  <NopConfig>
    <DynamicDiscovery Enabled="true" />
    <Engine Type="" />
    <Themes basePath="~/Themes/" />
  </NopConfig>

CreateEngineInstance函数中使用new NopEngine()创建了一个NopEngine实例,在NopEngine的构造函数处对Autofac的容器(Container)作了初始化,如下代码:

public NopEngine(EventBroker broker, ContainerConfigurer configurer)
{
    var config = ConfigurationManager.GetSection("NopConfig"as NopConfig;
    InitializeContainer(configurer, broker, config);
}
private void InitializeContainer(ContainerConfigurer configurer, EventBroker broker, NopConfig config)
{
    var builder = new ContainerBuilder();
 
    _containerManager = new ContainerManager(builder.Build());
    configurer.Configure(this, _containerManager, broker, config);
}

NopCommerce通过ContainerManager对容器做了一层封装,方便对其他类型的IOC框架的扩充和支持。Configure函数完成了所有依赖的注入,同时查找所有实现了IDependencyRegistrar接口的类,并调用其Register方法,注册内容包括Http context、web helper、controller、data layer、plugin、cache manager、work context、services、settings、event consumers等等。

关于ContainerManager/ContainerConfigurer和IDependencyRegistrar是实现IOC的关键,下面对这两个部分做详细的讨论。

// todo:仍需继续分析具体实现

ContainerManager/ContainerConfigurer

ContainerManagerContainerManager对依赖注入中使用的容器做了一层封装,提供了这些函数:
  • AddComponent/AddComponentInstance/AddComponentWithParameters
  • Resolve/ResolveAll/ResovleUnregistered
  • UpdateContainer

DependencyRegistrar

  • web helper
  • controller
  • data layer
  • plugin
  • cache manager
  • work context
  • services
  • settings
  • event consumers

nopcommerce之权限模块

这篇文章简单介绍一下nopcommerce的权限模块,nopcommerce里面的权限设计相对比较简单,主要针对后台的action和前台的是否显示(比如产品、品牌等),虽然简单但是应付一般的项目应该没问题。

你只需要关注三张表:

1、AclRecord(访问控制表,比如控制某个产品某种角色前台是否可见)

2、PermissionRecord(权限表)

3、PermissionRecord_Role_Mapping(权限角色关联表)

AclRecord:

后台添加编辑产品或者品牌的时候你一定会看到有个标签叫做访问控制,如下图所示:

如果启用了并且选了对应的角色,那么保存的时候就会往这张表里面添加记录,其中EntityName是几个固定的,比如Product等,当然你也可以扩展,读取的时候就会关联到这张表进行查询,如图所示:

PermissionRecord和PermissionRecord_Role_Mapping是针对当前用户所属角色的,这部分主要用于后台,如果你看了代码你会发现后台controller里面每个action里面几乎都有类似代码,没有权限的话会返回空值或者跳转到没有权限的提示页面,如图所示:

那么如何添加自己的权限呢,首先找到Nop.Services\Security\StandardPermissionProvider.cs,参考上面的添加一条记录,别忘了GetPermissions()也要添加,还有数据库里面的表PermissionRecord添加记录,最后一步,在后台访问控制页面给每个角色设置权限。

教你一招 - 如何给nopcommerce做一套自己的主题

nopcommerce拥有一套不错的模板机制,可以让你快速的做一套属于自己的主题。\Presentation\Nop.Web下面有个Themes文件夹,这里面就是放主题的地方,每个主题对应一个文件夹,每个主题文件夹下面都有一个theme.config文件,这个文件定义了该主题的一些基本信息。Content文件夹放的样式、图片、脚本等资源文件,Views文件夹放的是试图,nopcommerce会默认先读取这个里面的view,如果找不到就去根目录下面的Views里面找,我的作法是直接把根目录下面的Views文件夹复制过来,需要哪个就修改哪个,然后就是修改样式了,nopcommerce有一列、两列、三列显示,你可以参考一些国内的商城系统(注:你可以使用google浏览器或者火狐浏览器能很方便的看到每一块的具体样式,能很快的抄过来。。。),这里就不多说了。

这里分享了几个简单的主题:查看

为nopcommerce自定义用户积分功能(2)

积分功能第二部,不了解情况的可以先看看第一部

nopcommerce的总体架构我们就不多说了,从WEB到逻辑再到数据库访问都有地方要修改,本文主要演示逻辑和数据库访问的修改,Web界面上的后边会提到如何调用。

首 先我们有一个新的reward point provider,那么在web.config的sectionGroup里要加上:<section name=”RewardProvider” type=”NopSolutions.NopCommerce.DataAccess.DBProviderSection, Nop.DataAccess” requirePermission=”false”/>

在nopDataProviders的最后也要加上:

<RewardProvider defaultProvider=”SQLRewardProvider”>
<providers>
<add name=”SQLRewardProvider” type=”NopSolutions.NopCommerce.DataAccess.Promo.SQLRewardProvider, Nop.DataAccess.SqlServer” connectionStringName=”NopSqlConnection”/>
</providers>
</RewardProvider>

NopContext里也要加个字段用来保存当前session下的积分值:

public decimal RewardPoint
{
get {
if (HttpContext.Current.Session["RewardPoint"] == null)
return decimal.Zero;

decimal ret = decimal.Zero;
if (decimal.TryParse(HttpContext.Current.Session["RewardPoint"].ToString(), out ret))
return ret;
else
return decimal.Zero;
}
set
{
HttpContext.Current.Session["RewardPoint"] = value;
}
}

现在再来看数据库抽象接口的代码,各个方法什么意思想必大家一看就懂:

namespace NopSolutions.NopCommerce.DataAccess.Promo
{
[DBProviderSectionName("nopDataProviders/RewardProvider")]
public abstract partial class DBRewardProvider : BaseDBProvider
{
public abstract bool RewardCalculation(decimal point, int customerID, int orderid, string details);
public abstract DBCustomerCollection CustomerRewardList();
public abstract DBRewardAuditCollection GetAllAuditList(DateTime from, DateTime to);
public abstract DBRewardAuditCollection GetAuditByOrderID(DateTime from, DateTime to, int OrderID);
public abstract bool RewardPointSetTo(decimal point, int customerID, string details);
}
}

DBRewardAudit的定义:

namespace NopSolutions.NopCommerce.DataAccess.Promo
{
public partial class DBRewardAudit : BaseDBEntity
{
public int RewardID { get; set; }
public int OrderID { get; set; }
public DateTime RewardTime { get; set; }
public string Status { get; set; }
public decimal Amount { get; set; }
public string RewardDetails { get; set; }
}
}

按照标配来说还得要个DBRewardAuditCollection,我形式上加了这个文件,但没实质内容,如果管理员要求在登录网店后看到历史记录,可以用这个。

数据层的DBCustomer还要加上个RewardPoint属性:

namespace NopSolutions.NopCommerce.DataAccess.CustomerManagement
{
public partial class DBCustomer : BaseDBEntity
{
public decimal RewardPoint { get; set; }
}
}

数 据库层的UpdateCustomer方法的参数要修改,把reward point属性传进去,SQL里update的时候也能更新积分,在这儿就省略了。DBOrder也要加同样的public decimal RewardPoint { get; set; }属性。InsertOrder方法也要把RewardPoint包含进来。

具体的数据访问层,我这儿有个SQLRewardProvider.cs供参考。SQLRewardProvider

然后再看看业务逻辑层的RewardManager.cs,RewardManager,简单的代码,就不用再描述了。

OK到这儿大部分接口已经实现了,只等客户端代码调用了,所有的逻辑层,web层的代码都可以称为客户端代码,那么我们来试试下订单后消耗积分的情况。OrderManager.cs 中的PlaceOrder,

….

Order order = InsertOrder(OrderGuid, //这儿注意,用刚才修改过的方法,把积分传进去。

….

等有了OrderID = order.OrderID;这句,订单也生成了,那么就要扣掉相应的积分了,直接在这句后边加上:

//reward function
if (NopContext.Current.RewardPoint > customer.RewardPoint)//reward point cannot greater than customer’s point.
NopContext.Current.RewardPoint = customer.RewardPoint;

if (OrderID >= 0 && NopContext.Current.RewardPoint > decimal.Zero)//success, deduct reward points
RewardManager.RewardCalculation(decimal.Zero – NopContext.Current.RewardPoint, customer.CustomerID, OrderID,”Order placed with ” + RewardManager.RewardName + NopContext.Current.RewardPoint.ToString());

然后再接着往下走找到SendOrderPlacedStoreOwnerNotification和SendOrderPlacedCustomerNotification,里边的方法改改,让email可以支持reward point的消息:

首先要定义这些消息的token是什么,在GetListOfAllowedTokens里添加:

//added comments and reward points – by dingsea:
allowedTokens.Add(“%Order.RewardPointApplied%”);
allowedTokens.Add(“%Customer.CurrentRewardPoint%”);
allowedTokens.Add(“%Customer.WinRewardPoint%”);
allowedTokens.Add(“%Settings.RewardName%”);
//ends here.

ReplaceMessageTemplateTokens里加上如下字段以方便在正则替换的时候找到对应的文字:

//added: dingsea
tokens.Add(“Order.RewardPointApplied”, HttpUtility.HtmlEncode(order.RewardPoint.ToString(“N2″)));
tokens.Add(“Customer.WinRewardPoint”,HttpUtility.HtmlEncode((order.OrderTotal * RewardManager.RewardPercentage).ToString(“N2″)));
tokens.Add(“Customer.CurrentRewardPoint”,HttpUtility.HtmlEncode(NopContext.Current.User.RewardPoint.ToString(“N2″)));
tokens.Add(“Settings.RewardName”,HttpUtility.HtmlEncode(RewardManager.RewardName));

既然都到这里了,那也把nopcommerce的一个小BUG也补上:

ProductListToHtmlTable方法中没有打折信息(在order details页面也有同样问题,同学们自行加上吧),所以我把打折和积分信息一起加上了:

//fixed by dingsea – discount is missing
string CusDiscount = string.Empty;
string CusRewardPoint = string.Empty;

……

//fixed by dingsea – discount and reward point
CusDiscount = order.OrderDiscount == decimal.Zero ? string.Empty : PriceHelper.FormatPrice(order.OrderDiscount, true, order.CustomerCurrencyCode, language, false);
CusRewardPoint = order.RewardPoint.ToString(“N2″);

…….

//fixed by dingsea – discount and reward point
if(CusDiscount!=string.Empty)
sb.AppendLine(“<tr><td style=\”text-align:right;\” colspan=\”3\”><strong>” + LocalizationManager.GetLocaleResourceString(“ShoppingCart.Sub-TotalDiscount”, LanguageID) + “</strong></td> <td style=\”text-align:right;\”><strong>” + CusDiscount + “</strong></td></tr>”);
sb.AppendLine(“<tr><td style=\”text-align:right;\” colspan=\”3\”><strong>” + LocalizationManager.GetLocaleResourceString(“Promotion.RewardPointName”, LanguageID) + “</strong></td> <td style=\”text-align:right;\”><strong>” + CusRewardPoint + “</strong></td></tr>”);
//fix ends

于是呼,在你的template->message template里订单生成时向用户和店主发的email里就可以支持积分的token了。

至于在web如何调用,那就比较容易了,在你想要的页面加上一个textbox,确保用户输入的是decimal,那就够了,这个数字会存到session里直到用户下单。而且在管理界面,利用rewardmanager的方法可以直接操作积分,方便快捷。