nopcommerce 系统开发文档
nopcommerce中的主题模块
利用nopcommerce中的主题模块(或者叫“皮肤”,“
模板
个人简介word模板免费下载关于员工迟到处罚通告模板康奈尔office模板下载康奈尔 笔记本 模板 下载软件方案模板免费下载
”),我们可以很方便地从管理界面选择当前主题。
今天把主题这块的知识整理一下,同时也起个抛砖引玉的作用。
1.如何使用。
主题的使用很简单,只有3步:
a 上传你的新主题到themes文件夹下
b把head.cshtml中的CSS路径换成你自己的,这个在开发主题的时候应该就搞定了 c 在后台管理面板中选择你新的主题,保存,然后你就能在前台看到了。 2.它是如何加载的,
这是本文重点。
说到加载机制就要说到主题模块用到的三个类,它们都位于Nop.Web.Framework.Themes下:
继承自下边一个类 ThemeableRazorViewEngine -
ThemeableBuildManagerViewEngine - 继承自下边一个类
ThemeableVirtualPathProviderViewEngine
然后是起到触发作用的Global.asax.cs
那么nopcommerce是一个MVC项目,大家都清楚视图文件都是放在view文件夹下的,为嘛nopcommerce
能直接读取theme里的视图哪,
OK先来说这三个和主题相关的类是怎么回事。
首先看ThemeableVirtualPathProviderViewEngine,它是从VirtualPathProviderViewEngine类继承的,使用此类可以改变所谓的视图的对应文件夹的实际位置,比如:
public ThemeableRazorViewEngine()
{
AreaViewLocationFormats = new[]
{
//themes
"~/Areas/{2}/Themes/{3}/Views/{1}/{0}.cshtml",
"~/Areas/{2}/Themes/{3}/Views/{1}/{0}.vbhtml",
"~/Areas/{2}/Themes/{3}/Views/Shared/{0}.cshtml",
"~/Areas/{2}/Themes/{3}/Views/Shared/{0}.vbhtml",
//default
"~/Areas/{2}/Views/{1}/{0}.cshtml",
"~/Areas/{2}/Views/{1}/{0}.vbhtml",
"~/Areas/{2}/Views/Shared/{0}.cshtml",
"~/Areas/{2}/Views/Shared/{0}.vbhtml"
};
AreaMasterLocationFormats = new[]
{
//themes
"~/Areas/{2}/Themes/{3}/Views/{1}/{0}.cshtml",
"~/Areas/{2}/Themes/{3}/Views/{1}/{0}.vbhtml",
"~/Areas/{2}/Themes/{3}/Views/Shared/{0}.cshtml",
"~/Areas/{2}/Themes/{3}/Views/Shared/{0}.vbhtml",
//default
"~/Areas/{2}/Views/{1}/{0}.cshtml",
"~/Areas/{2}/Views/{1}/{0}.vbhtml",
"~/Areas/{2}/Views/Shared/{0}.cshtml",
"~/Areas/{2}/Views/Shared/{0}.vbhtml"
};
AreaPartialViewLocationFormats = new[]
{
//themes
"~/Areas/{2}/Themes/{3}/Views/{1}/{0}.cshtml",
"~/Areas/{2}/Themes/{3}/Views/{1}/{0}.vbhtml",
"~/Areas/{2}/Themes/{3}/Views/Shared/{0}.cshtml",
"~/Areas/{2}/Themes/{3}/Views/Shared/{0}.vbhtml",
//default
"~/Areas/{2}/Views/{1}/{0}.cshtml",
"~/Areas/{2}/Views/{1}/{0}.vbhtml",
"~/Areas/{2}/Views/Shared/{0}.cshtml",
"~/Areas/{2}/Views/Shared/{0}.vbhtml"
};
ViewLocationFormats = new[]
{
//themes
"~/Themes/{2}/Views/{1}/{0}.cshtml",
"~/Themes/{2}/Views/{1}/{0}.vbhtml",
"~/Themes/{2}/Views/Shared/{0}.cshtml",
"~/Themes/{2}/Views/Shared/{0}.vbhtml",
//default
"~/Views/{1}/{0}.cshtml",
"~/Views/{1}/{0}.vbhtml",
"~/Views/Shared/{0}.cshtml",
"~/Views/Shared/{0}.vbhtml",
//Admin
"~/Administration/Views/{1}/{0}.cshtml",
"~/Administration/Views/{1}/{0}.vbhtml",
"~/Administration/Views/Shared/{0}.cshtml",
"~/Administration/Views/Shared/{0}.vbhtml",
};
MasterLocationFormats = new[]
{
//themes
"~/Themes/{2}/Views/{1}/{0}.cshtml",
"~/Themes/{2}/Views/{1}/{0}.vbhtml",
"~/Themes/{2}/Views/Shared/{0}.cshtml",
"~/Themes/{2}/Views/Shared/{0}.vbhtml",
//default
"~/Views/{1}/{0}.cshtml",
"~/Views/{1}/{0}.vbhtml",
"~/Views/Shared/{0}.cshtml",
"~/Views/Shared/{0}.vbhtml"
};
PartialViewLocationFormats = new[]
{
//themes
"~/Themes/{2}/Views/{1}/{0}.cshtml",
"~/Themes/{2}/Views/{1}/{0}.vbhtml",
"~/Themes/{2}/Views/Shared/{0}.cshtml",
"~/Themes/{2}/Views/Shared/{0}.vbhtml",
//default
"~/Views/{1}/{0}.cshtml",
"~/Views/{1}/{0}.vbhtml",
"~/Views/Shared/{0}.cshtml",
"~/Views/Shared/{0}.vbhtml",
//Admin
"~/Administration/Views/{1}/{0}.cshtml",
"~/Administration/Views/{1}/{0}.vbhtml",
"~/Administration/Views/Shared/{0}.cshtml",
"~/Administration/Views/Shared/{0}.vbhtml",
};
FileExtensions = new[] { "cshtml", "vbhtml" };
}
以上代码不难看出,除了nopcommerce的主题,还有管理界面啊之类的都定好了View是在哪个物理路
径上。
很自然地,我们在global文件里,应用启动的时候调用这么一段话就可以让nopcommerce把主题定向
theme文件夹:
if (databaseInstalled)
{
//remove all view engines
ViewEngines.Engines.Clear();
//except the themeable razor view engine we use
ViewEngines.Engines.Add(new ThemeableRazorViewEngine());
}
VirtualPathProviderViewEngine其实不是一个新东西,很早就有了,而且不但是MVC,就连webform也可以用它。更多信息,*丝们可以猛击此处
3.扩展主题机制。
既然知道原理,我们就可以着手扩展,这个就是个大题目了,如何扩展在具体个人,我们来举2个例子: 1默认管理路径为administration,我们可以改成自己想要的路径
2主题不定要放到Themes里,我们修改代码可以让它从其它地方读取视图文件。进一步来讲还可以把视图文件存放到数据库中。
NopCommerce里的
计划
项目进度计划表范例计划下载计划下载计划下载课程教学计划下载
任务机制
Nop.Web.Global.asax.cs
//start scheduled tasks
if (databaseInstalled)
{
TaskManager.Instance.Initialize();
TaskManager.Instance.Start();
}
做一个你自己的计划任务
1、依照下述(三、)中所列,为YourNewTask类做好路径,它应该有ITask接口;
2、YourNewTask只有一个无参数的方法:Execute。在任务要执行的时候,这个方法会被调用。
3、数据表ScheduleTask中添加一条记录。你可以手动添加,或使用IScheduleTaskService来添加记录。
现有的任务:(nop 2.50)
1 Send emails 60 Nop.Services.Messages.QueuedMessagesSendTask, Nop.Services
2 Keep alive 300 Nop.Services.Common.KeepAliveTask, Nop.Services
3 Delete guests 600 Nop.Services.Customers.DeleteGuestsTask, Nop.Services
4 Clear cache 600 Nop.Services.Caching.ClearCacheTask, Nop.Services
5 Update currency exchange rates 900 Nop.Services.Directory.UpdateExchangeRateTask,
Nop.Services
6 MailChimp sync 3600 Nop.Plugin.Misc.MailChimp.MailChimpSynchronizationTask,
Nop.Plugin.Misc.MailChimp
7 Froogle static file generation 3600 Nop.Plugin.Feed.Froogle.StaticFileGenerationTask,
Nop.Plugin.Feed.Froogle
nopcommerce计划任务分析
对比了一下nopcommerce和orchard的计划任务,orchard的复杂的不是一点点,如果想拆下来自己用难度很大,搜索拆了 orchard的lucene处理模块,邮件队列拆的discuznt和nopcommerce的结合,计划任务就拆nopcommerce的 了,discuznt计划任务设计的没nopcommerce的好。 1.nopcommerce的tasks结构如下:
IScheduleTaskService.cs 接口,这个主要是获取数据库里的任务信息,ScheduleTaskService.cs去实现它就可以了,当然需要在容器里注入一下。
ITask 这个接口比较特别但是很重要,所有的任务处理类都要实现里面唯一的Execute方法。执行计划任务时就需要通过反射来执行这个实现。
namespace Nop.Services.Tasks
{
///
/// Interface that should be implemented by each task
///
public partial interface ITask
{
///
/// Execute task
///
void Execute();
}
}
核心类之一:Task.cs,这个主要是处理任务的执行过程及执行过程类的结果处理。
private ITask CreateTask()
{
ITask task = null;
if (this.Enabled)
{
var type2 = System.Type.GetType(this.Type);
if (type2 != null)
{
object instance;
if (!EngineContext.Current.ContainerManager.TryResolve(type2, out instance))
{
//not resolved
instance = EngineContext.Current.ContainerManager.ResolveUnregistered(type2);
}
task = instance as ITask;
}
}
return task;
}
通过反射来找到编写的计划任务类。例如下面的发送邮件的任务。 using System;
using Nop.Services.Logging;
using Nop.Services.Tasks;
namespace Nop.Services.Messages
{
///
/// Represents a task for sending queued message
///
public partial class QueuedMessagesSendTask : ITask
{
private readonly IQueuedEmailService _queuedEmailService;
private readonly IEmailSender _emailSender;
private readonly ILogger _logger;
public QueuedMessagesSendTask(IQueuedEmailService queuedEmailService,
IEmailSender emailSender, ILogger logger)
{
this._queuedEmailService = queuedEmailService;
this._emailSender = emailSender;
this._logger = logger;
}
///
/// Executes a task
///
public void Execute()
{
var maxTries = 3;
var queuedEmails = _queuedEmailService.SearchEmails(null, null, null, null,
true, maxTries, false, 0, 500);
foreach (var queuedEmail in queuedEmails)
{
var bcc = String.IsNullOrWhiteSpace(queuedEmail.Bcc)
? null
: queuedEmail.Bcc.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
var cc = String.IsNullOrWhiteSpace(queuedEmail.CC)
? null
: queuedEmail.CC.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
try
{
_emailSender.SendEmail(queuedEmail.EmailAccount, queuedEmail.Subject, queuedEmail.Body,
queuedEmail.From, queuedEmail.FromName, queuedEmail.To, queuedEmail.ToName, bcc, cc);
queuedEmail.SentOnUtc = DateTime.UtcNow;
}
catch (Exception exc)
{
_logger.Error(string.Format("Error sending e-mail. {0}", exc.Message), exc);
}
finally
{
queuedEmail.SentTries = queuedEmail.SentTries + 1;
_queuedEmailService.UpdateQueuedEmail(queuedEmail);
}
}
}
}
}
执行完任务后需要将数据库里的任务记录状态更改,主要是时间状态变更。
核心执行方法:
///
/// Executes the task
///
public void Execute()
{
this.IsRunning = true;
var scheduleTaskService = EngineContext.Current.Resolve
();
var scheduleTask = scheduleTaskService.GetTaskByType(this.Type);
try
{
var task = this.CreateTask();
if (task != null)
{
this.LastStartUtc = DateTime.UtcNow;
if (scheduleTask != null)
{
//update appropriate datetime properties
scheduleTask.LastStartUtc = this.LastStartUtc;
scheduleTaskService.UpdateTask(scheduleTask);
}
//execute task
task.Execute();
this.LastEndUtc = this.LastSuccessUtc = DateTime.UtcNow;
}
}
catch (Exception exc)
{
this.Enabled = !this.StopOnError;
this.LastEndUtc = DateTime.UtcNow;
//log error
var logger = EngineContext.Current.Resolve();
logger.Error(string.Format("Error while running the '{0}' schedule task. {1}", this.Name,
exc.Message), exc);
}
if (scheduleTask != null)
{
//update appropriate datetime properties
scheduleTask.LastEndUtc = this.LastEndUtc;
scheduleTask.LastSuccessUtc = this.LastSuccessUtc;
scheduleTaskService.UpdateTask(scheduleTask);
}
this.IsRunning = false;
}
任务管理类:TaskManager.cs,主要负责任务的初始化,添加到线程列表,任务的开始和停止。需要
在Global里初始化和开始任务,它会根据线程里的定时器自动读取任务列表执行任务。
//start scheduled tasks
if (databaseInstalled)
{
TaskManager.Instance.Initialize();
TaskManager.Instance.Start();
}
任务线程管理类:TaskThread.cs,任务线程类,TaskManager将任务都添加到此线程管理类里,此线程
管理主要负责判断任务的执行状态,线程执行间隔时间及调用任务执行的主方法Execute,通过Timer
定时器实现定时自动运行。
主方法为:
private void Run()
{
if (Seconds <= 0)
return;
this.StartedUtc = DateTime.UtcNow;
this.IsRunning = true;
foreach (Task task in this._tasks.Values)
{
task.Execute();
}
this.IsRunning = false;
}
从任务列表中读取任务并执行。
在NopCommerce中新增一个Domain Model的步骤 1. 在NopCommerce中新增一个Domain Model,需要以下几个步骤:
2. 新建一个Entity Class (Nop/Core/Domain/Entity.cs)
3. 新建一个Mapping Class (Nop/Data/Mapping/EntityMap.cs) 4. 新建一个View Model (Nop/Admin/Models/EntityModel.cs 或 Nop/Web/Models/EntityModel.cs) 5. 新建Model Validator (Nop/Admin/Validators/EntityValidator.cs 或 Nop/Web/Validators/EntityValidator.cs) 6. 为AutoMapper新建映射配置,用来完成Model和Entity之间的转换
(Nop/Admin/Infrastructure/AutoMapperStartupTask.cs 或 Nop/Web/Infrastructure/AutoMapperStartupTask.cs)
7. 编写ToModel和ToEntity (Nop/Admin/MappingExtensions.cs 或 Nop/Web/MappingExtensions.cs) 8. 创建Service和Service Interface (Nop/Services/EntityService.cs 和 Nop/Services/IEntityService.cs) 9. 最后新建的Model创建Controller和View
PS: NopCommerce不支持database migration,需要手动更新数据库。
参考资料:
NopCommerce MVC 插件机制分析
基本原理
插件化的应用程序一般都是先定义插件接口,然后把插件编译的dll放到固定的目录中,应用程序主程序通过加载那些实现了插件接口的dll来实现插件的使用。NopCommerce也是这样,但作为MVC Web应用程序会有一些不一样,首先是不同信任级别(Full Trust,Medium Trust)的时候加载dll的策略会有不一样,另外就是怎样显示插件中的View的问题。
放插件的文件夹
NopCommerce的插件放在网站主目录的Plugins目录下,Plugins下面有很多文件夹,一个插件类库就是一个文件夹。在插件类库中修改编译输出的地址为网站主目录的Plugins文件夹,这样插件生成的dll就能自动在目标文件夹下面。如下图:
另一个文件夹是ShadowCopy文件夹,就在Plugins/bin文件夹下。关于为什么要用ShadowCopy,住这篇文章中有所叙述,NopCommerce就是参考它的实现。里面也详细叙述了信任级别的问题。
网站启动
我们先来看PluginManager.Initialize方法。通过在PluginManager类上定义如下属性保证PluginManager的Initialize方法在网站开始的时候运行,早于Application_Start运行。 [assembly: PreApplicationStartMethod(typeof(PluginManager), "Initialize")]
在Application_Start之前运行初始化代码主要是为了让网站应用程序可以引用到加载的dll。
核心类
IPluginFinder.cs接口:获取插件的信息接口,在ioc里的Nop.Web.Framework.DependencyRegistrar注册此接口。系统启动的时候会加载到内存里。
IPlugin.cs:插件的操作接口,主要有设置插件的属性信息,安装插件接口,卸载插件接口。 BasePlugins.cs 实现IPlugin.cs的方法。
PluginDescriptor.cs 插件的实体类,包含了插件的版本、描述,类型,文件名称,作者,等等一系列状态。
PluginFileParser.cs 包含对插件的实体操作方法,主要是写入插件的描述信息。
PluginFinder.cs 加载所有的插件,并获取它们的信息.
PluginManager.cs 插件管理的主类,看里面的注释,它的插件机制应该是参考的Umbraco这个cms的。
加载插件
首先加载插件的描述。每一个插件都必须定义一个插件描述文件,用文本文件Description.txt来定义,名字也是约定的,不能是其他名字。 在插件的类库中添加Description.txt,对插件进行描述。Description中的文本字段的格式是固定的,PluginManager会 把Description.txt文件转化成PluginDescriptor类,然后存储在内存中。Description中的 DisplayOrder字段表示了这个插件的顺序,以便在界面上显示。获取所有的插件描述文件后,它就会去InstalledPlugins.txt里面看,在
InstalledPlugins.txt里面有的就是已经安装的查件,没有的话就是没有安装的。 PluginDescriptor.Installed属性描述了这个信息。
把需要加载的dll复制到Plugins/bin文件夹下,当然都是要继承自IPlugIn接口的,通过Assembly.Load在家这个 dll,再用BuildManager.AddReferencedAssembly把这个dll加载到网站这个应用程序中。要注意的是 BuildManager.AddReferencedAssembly必须早网站程序的Application_PreStartInit过程中加入, 也就是在Application_Start前。这是要引用的插件都被放在了Plugins/bin下,并被CLR引用了。被引用的插件的 Assembly引用将被保存在PluginManager的静态列表中ReferencedPlugins。PluginManager的 Initialize方法到此结束。
在界面上显示插件
举例来说,我们在定义插件的时候会定义一种类型的插件,比如送货方式。那么我们在定义插件的时候会继承2个接口,一个是IPlugin接口,一个是 IShippingMethod接口。在需要显示送货方式插件的时候通过PluginFinder.GetPlugins ().ToList() 方法去获取。PluginFinder会去上一步的PluginManager.ReferencedPlugins列表里面去寻找。返回的是 IShippingMethod的实例。NopCommerce有个txt文件:InstalledPlugs.txt。只有在这个里面的插件最终会加载 到界面上去。可以通过NopCommerce的查件管理页面把最终需要作用于网站的插件加入到这个文件中。
配置插件
NopCommerce的admin网站可以对插件进行配置。如下图:可以配置Display Order 和 IsActive等。主要的逻辑是更新该插件的Description.txt文件和内存中的IPlugin.Descriptor里面的属性。
定义插件中的Controller,Action和View
稍微复杂的插件基本都包含自己要处理的界面和逻辑。所以在插件的类库中可以定义插件的界面View和相关Controller和Action。在建 立Controller和View的时候,不一定要按照
规范
编程规范下载gsp规范下载钢格栅规范下载警徽规范下载建设厅规范下载
的Controllers文件夹和Views文件夹来定义,可以定义自己的风格。
在Action中返回View的时候,要输入View的名称,这个名称要包含名称空间,例如:
return View("Nop.Plugin.DiscountRules.HasAllProducts.Views.DiscountRulesHasAllProducts.Configure", model); 因为,在编译过后的插件dll中,作为嵌入资源的View会被编译成名叫Nop.Plugin.Payments.CashOnDelivery.Views.PaymentCashOnDelivery.Configure.cshtml的资源文件。 NopCommerce通过一些插件的Configure界面,把一些插件的配置信息保存到数据库中。然后在前台页面显示的时候再从数据库获取。
读取嵌入的资源View
插件作为一个类库被加载到应用程序域中。而在定义插件的View的时候,需要把cshtml文件的属性修改成Embedded Resource。它是作为嵌入式资源放到AppDomain中去的。我们可以通过VirtualPathProvider,使 Web 应用程序可以从虚拟文件系统中检索资源,您可以在这篇文 章中找到相关知识,NopCommerce的实现和这篇文章是一样的。在NopCommerce中的Nop.Web.Framework类库中有个 EmbeddedViews文件夹,里面包含了如果处理嵌入的View的一些类。最后需要在Global.asax进行注册。NopCommerce的代 码如下:
//register virtual path provider for embedded views
var embeddedViewResolver = EngineContext.Current.Resolve();
var embeddedProvider = new EmbeddedViewVirtualPathProvider(embeddedViewResolver.GetEmbeddedViews());
HostingEnvironment.RegisterVirtualPathProvider(embeddedProvider);
编写NopCommerce插件
可以参考官方文档。其中有一条建议非常好,就是Copy原来的插件,在上面修改。
NopCommerce是如何使用Autofac实现依赖注入的 IOC和DI
IOC中文名被称作控制反转(Inversion of Control),DI被称为依赖注入(Dependency Injection),可参考Martin Fowler的这篇文章来了解这两个概念:IoC容器和DependencyInjection模式。使用控制反转模式开发项目流程是先建立接口,然后再实现类,或许有人不习惯这样的开发方法,但在规模较大的软件架构中,这种方法却可以有效的降低类之间的互相依赖的情况,不但能增加架构的弹性,也能有效的降低软件的复杂度。
如果不考虑控制反转的情况,采用直接创建类,并直接在应用层调用该类,如此一来,应用层的对象就会与BLL(业务逻辑层)对象高度依赖,这样的依赖 会导致这两个类无法拆开,从而增加了这个类的维护难度,同时导致了单元测试难以进行。为了解决耦合度问题,从而引入了控制反转的概念。 Autofac介绍
Autofac是一款IOC框架,比较于其他的IOC框架,如Spring.NET、Unity、Castle等,它更显得轻量级,同时保证了高性能。它具有以下优点:
, 和C#语言联系紧密,可以使用C#语言的很多特性,譬如Lambda表达式等;
, 较低的学习曲线,只需了解IoC和DI的概念以及在何时需要使用它们即可;
, XML配置支持;
, 自动装配;
, 与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().InstancePerRequest(); 同时可以使用Autofac提供的RegisterControllers扩展方法来对程序集中所有的Controller一次性的完成注册:
var builder = new ContainerBuilder();
builder.RegisterControllers(typeof(MvcApplication).Assembly);
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 { get; set; }
}
当使用的是webform的试图引擎时,ViewPage,ViewMasterPage和ViewUserControl类都得到相应的支持。 public abstract class CustomViewPage : ViewPage
{
public IDependencyDependency { get; set; }
}
必须确保您实际的试图页面继承了您自定义的基类。在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().InstancePerHttpRequest();
builder.RegisterFilterProvider();
IContainer container = builder.Build();
DependencyResolver.SetResolver(new AutofacDependencyResolver(container)); 然后您就可以为您的过滤器添加属性了,并且
public class CustomActionFilter : ActionFilterAttribute
{
public ILogger Logger { get; set; }
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
Logger.Log("OnActionExecuting");
}
}
下面是类似用户验证过滤器的自定义特性
public class CustomAuthorizeAttribute : AuthorizeAttribute {
public ILogger Logger { get; set; }
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
Logger.Log("AuthorizeCore");
return true;
}
}
应用如下:
[CustomActionFilter]
[CustomAuthorizeAttribute]
public ActionResult Index()
{
// ...
}
关于Autofac更多的信息,可以参考autofac在google code上的wiki文档:
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.Instance == null || forceRecreate)
{
var config = ConfigurationManager.GetSection("NopConfig") as NopConfig;
Debug.WriteLine("Constructing engine " + DateTime.Now);
Singleton.Instance = CreateEngineInstance(config);
Debug.WriteLine("Initializing engine " + DateTime.Now);
Singleton.Instance.Initialize(config);
}
return Singleton.Instance;
}
NopEngine 使用单例模式,在整个程序运行期间存在一个实例,代码首先会判断NopEngine是否为空,为空的话则根据web.config中配置的 NopConfig节点信息创建一个新的NopEngine实例,然后对该实例进行初始化操作。web.config中的配置信息如下:
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中cache分析
入口:
///
/// Gets a category
///
/// Category identifier
/// Category
public virtual Category GetCategoryById(int categoryId)
{
if (categoryId == 0)
return null;
string key = string.Format(CATEGORIES_BY_ID_KEY, categoryId);
return _cacheManager.Get(key, () => { return _categoryRepository.GetById(categoryId); });
}
//"CurrentCategoryId" property of "CategoryNavigationModel" object depends on the current category or
product.
//We need to clone the cached model (the updated one should not be cached)
var model = (CategoryNavigationModel)cachedModel.Clone();
model.CurrentCategoryId = activeCategoryId;
return PartialView(model);
}
功能描述:
1,生成cacheKey
2, 调用_cacheManager获取数据
接下来,看看_cacheManager Get方法是如何定义的。
_cacheManager.Get定义
///
/// Extensions
///
public static class CacheExtensions
{
public static T Get(this ICacheManager cacheManager, string key, Func acquire)
{
return Get(cacheManager, key, 60, acquire);
}
public static T Get(this ICacheManager cacheManager, string key, int cacheTime, Func acquire)
{
if (cacheManager.IsSet(key))
{
return cacheManager.Get(key);
}
else
{
var result = acquire();
//if (result != null)
cacheManager.Set(key, result, cacheTime);
return result;
}
}
}
由定义可知,当从缓存存中取不到数据时,则执行匿名函数acquire获取数据。 获取数据成功后,设置缓存,这样下次就可以直接从缓存中获取数据了。 Get方法中调用的是ICacheManager接口的方法,因此需要看看是哪个类实现了该接口。方法是通过查找IoC注册,找
到实现ICacheManager接口的类。
接口注册
//cache manager
builder.RegisterType().As().Named("nop_cache_static").SingleIns
tance();
builder.RegisterType().As().Named("nop_cache_per_request").InstancePerHttpRequest();
功能描述:在IoC容器中把MemoryCacheManager注册为ICacheManager,并且命名为nop_cache_static。
public virtual void Register(ContainerBuilder builder, ITypeFinder typeFinder)
{
//we cache presentation models between requests
builder.RegisterType()
.WithParameter(ResolvedParameter.ForNamed("nop_cache_static"));
builder.RegisterType()
.WithParameter(ResolvedParameter.ForNamed("nop_cache_static")); ……
功能描述:在IoC容器中注册CatalogController,并把它的缓存策略设置为nop_cache_static。 完成这两步注册后,在“入口”代码块中,当需要_cacheManager实例时,IoC容器将返回MemoryCacheManager的实例。 最后看看MemoryCacheManager具体是如何缓存的。
MemoryCacheManager实现
public partial class MemoryCacheManager : ICacheManager
{
protected ObjectCache Cache
{
get {return MemoryCache.Default;}
}
public void Set(string key, object data, int cacheTime)
{
if (data == null)
return;
var policy = new CacheItemPolicy();
policy.AbsoluteExpiration = DateTime.Now + TimeSpan.FromMinutes(cacheTime);
Cache.Add(new CacheItem(key, data), policy);
}
……
由上可知,它采用的是MemoryCache来做缓存的。
Nopcommerce 的数据校验
数据校验是一个用以保证程序操作干净,正确和有用数据的流程。很多.NET程序猿使用Data Annotation Validators,不过nopCommerce用的是Fluent Validation, 一个有着文艺青年般的接口和lambda表达式构成的.NET的小型验证库,用以生成符合你业务需求的校验规则 。在nopCommerce中你必须要通过2步来添加一个校验到一些模型中: 1.创建一个继承自AbstractValidator的类并把所有必须的验 证逻辑都放入其中,看下边这些应该有所启发:
public class AddressValidator : AbstractValidator
{
public AddressValidator(ILocalizationService localizationService, AddressSettings addressSettings)
{
RuleFor(x => x.FirstName)
.NotEmpty()
.WithMessage(localizationService.GetResource("Address.Fields.FirstName.Required"));
RuleFor(x => x.LastName)
.NotEmpty()
.WithMessage(localizationService.GetResource("Address.Fields.LastName.Required"));
RuleFor(x => x.Email)
.NotEmpty()
.WithMessage(localizationService.GetResource("Address.Fields.Email.Required")); 2.给你的模型类加上ValidatorAttribute属性,比如下边代码:
[Validator(typeof(AddressValidator))]
public partial class AddressModel : BaseNopEntityModel
{
当一个视图模型被提交到控制器,ASP.NET会执行相应的校验。
Nopcommerce 事件暴露和处理
事件是把消息广播给感兴趣的部分。事件是由数据驱动的如添加,更新和删除数据。NopCommerce允许程序员“监听”他们感兴趣的事件。程序员要想玩转事件基本上有如下两条路走,一个程序员要么发布某个事件让其它人来用,要么用别的程序员编好并发布的事件。
1. 程序员为了发布一个事件,必须先取得一个IEventPublisher实例再使用相应的数据一起调用Publish方法。 2. 程序员要监听一个事件,他必须实现一个新的IConsumer泛型接口,一旦有人使用这个事件,nopCommerce会用
反射来寻找并注册这个事件的实现。