在Suteki.Shop中使用了NVeloctiy模版引擎,用于提供可订制的邮件模版。而邮件的功能就是当定单状
态发生变化时,系统会向买家发送邮件通知。其中的邮件信息内容就是采用NVeloctiy的模版(.vm扩展名)
进行订制的。
因为在Sutekie.Shop的最新源码包中只是部分实现了其功能,而全部的功能还在完善中,所以要运行本
文中所说的功能,需要在下面的链接地址中下载其最新程序文件(包括单元测试文件):
http://code.google.com/p/sutekishop/source/detail?r=282
要下载的文件包括:
/branches/JtG_Enhancements/Suteki.Shop/Suteki.Shop/Views/EmailTemplates/OrderConfirmation.vm
/branches/JtG_Enhancements/Suteki.Shop/Suteki.Shop/Views/EmailTemplates/OrderDispatch.vm
/branches/JtG_Enhancements/Suteki.Shop/Suteki.Shop/Views/EmailTemplates/_orderDetails.vm
/branches/JtG_Enhancements/Suteki.Shop/Suteki.Shop/Controllers/OrderStatusController.cs
/branches/JtG_Enhancements/Suteki.Shop/Suteki.Shop/Services/EmailService.cs
等等。
当下载并覆盖(或添加)到本地项目中后,我们还需要在Castle Windsor中注册相应的EmailBuilder
组件。我们只要打开ContainerBuilder类并找到其Build方法(Suteki.Shop/ContainerBuilder.cs),并
添加如下代码:
Component.For<IEmailService>().ImplementedBy<EmailService>().LifeStyle.Singleton
注:有关Castle Windsor 的 IOC的内容我已在这篇文章中做了介绍.
最终的代码如下所示:
container.Register(
Component.For
<
IUnitOfWorkManager
>
().ImplementedBy
<
LinqToSqlUnitOfWorkManager
>
().LifeStyle.Transient,
Component.For
<
IFormsAuthentication
>
().ImplementedBy
<
FormsAuthenticationWrapper
>
(),
Component.For
<
IServiceLocator
>
().Instance(
new
WindsorServiceLocator(container)),
Component.For
<
AuthenticateFilter
>
().LifeStyle.Transient,
Component.For
<
UnitOfWorkFilter
>
().LifeStyle.Transient,
Component.For
<
DataBinder
>
().LifeStyle.Transient,
Component.For
<
LoadUsingFilter
>
().LifeStyle.Transient,
Component.For
<
CurrentBasketBinder
>
().LifeStyle.Transient,
Component.For
<
ProductBinder
>
().LifeStyle.Transient,
Component.For
<
OrderBinder
>
().LifeStyle.Transient,
Component.For
<
IOrderSearchService
>
().ImplementedBy
<
OrderSearchService
>
().LifeStyle.Transient,
Component.For
<
IEmailBuilder
>
().ImplementedBy
<
EmailBuilder
>
().LifeStyle.Singleton,
Component.For
<
IEmailService
>
().ImplementedBy
<
EmailService
>
().LifeStyle.Singleton
//
新加的代码
);
完成了这些工作后,我们就可以编译运行该项目了。
下面我们来看一下今天的主角 EMailBuilder,其实现了使用NVelocityEngine加载模版信息并将
ViewData中的数据与模版中的指定变量进行绑定的工作。下面是其类图:
下面对其中相关类和接口做一下介绍:
首先是IEmailBuilder接口,该接口中只有一个方法GetEmailContent,用于将指定的NVelocity模
版与ViewData的数据进行绑定,其中参数templateName就是指定的NV模版名称,而 viewdata就是服务
(EmailService)所传递过来的定单数据。
///
<summary>
///
Provide the base method and property to build email
///
</summary>
public
interface
IEmailBuilder
{
///
<summary>
///
Get the email content
///
</summary>
///
<returns>
Return the email content.
</returns>
string
GetEmailContent(
string
templateName, IDictionary
<
string
,
object
>
viewdata);
}
而做为IEmailBuilder接口的实现类,EMailBuilder 中相应的GetEamilContent方法实现代码如下:
public
string
GetEmailContent(
string
templateName, IDictionary
<
string
,
object
>
viewdata)
{
return
BuildEmail(templateName, viewdata);
}
string
BuildEmail(
string
templateName, IDictionary
<
string
,
object
>
viewdata)
{
if
(viewdata
==
null
)
{
throw
new
ArgumentNullException(
"
viewData
"
);
}
if
(
string
.IsNullOrEmpty(templateName))
{
throw
new
ArgumentException(
"
TemplateName
"
);
}
var template
=
ResolveTemplate(templateName);
var context
=
new
VelocityContext();
foreach
(var key
in
viewdata.Keys)
{
context.Put(key, viewdata[key]);
}
using
(var writer
=
new
StringWriter())
{
template.Merge(context, writer);
return
writer.ToString();
}
}
可以看出,最终获取Email内容的工作交给了BuildEmail这个方法,其实现的逻辑还是很清晰的。
首要是判断传入参数是否为空(包括viewdata,templateName),然后调用ResolveTemplate
方法来获取指定NV模版的信息内容,其方法(ResolveTemplate)内容如下:
Template ResolveTemplate(
string
name)
{
name
=
Path.Combine(templatePath, name);
if
(
!
Path.HasExtension(name))
{
name
+=
"
.vm
"
;
}
if
(
!
velocityEngine.TemplateExists(name))
{
throw
new
InvalidOperationException(
string
.Format(
"
Could not find a template named '{0}'
"
, name));
}
return
velocityEngine.GetTemplate(name);
}
ResolveTemplate的工作流程即:先判断指定的模版路径中是否包括扩展名,如不包括则添加"vm"
结尾的扩展名(该扩展名是NV模版的指定扩展名)。然后继续判断指定路径下的模版文件是否存在“
TemplateExists”。并最终使用velocityEngine来完成获取模版文件内容信息的功能。
注:velocityEngine的初始化在构造方法:
EmailBuilder(IDictionary<string, object> nvelocityProperties) 中实现。
分析完ResolveTemplate方法,再回到上面的BuildEmail方法中看一下其余的代码。运行完获取模
版信息的方法之后, 接着就是使用viewdata中的数据构造一个 VelocityContext对象并使用它来完成
与指定模版信息的绑定了。即下面的这几行代码:
var context
=
new
VelocityContext();
foreach
(var key
in
viewdata.Keys)
{
context.Put(key, viewdata[key]);
}
using
(var writer
=
new
StringWriter())
{
template.Merge(context, writer);
return
writer.ToString();
}
到这里其本上就完成了对NV模版的数据绑定并返回其最终结果了。下面看一下Suteki.Shop是如何
使用它的。
首先我们要看一下整个EMail通知发送体系的类图:
注:图中右下角就是上面我们所说的那个EmailBuilder
我们要先清楚了解一下上图中的类关系:
图中右上角OrderStatusController这个Controller,顾名思义其用于定单状态发生变化时的控制器操作,
它定义了几个Action方法(图中的Method),其中Dispatch方法就包括对EmailService类中“发送Dispatch通
知(SendDispatchNotification)”的方法调用,而就是该方法会最终完成对EMailBuilder调用,下面就以调用
的先后顺序依次介绍一下其实代码:
首先是OrderStatusController中的Dispatch方法,其实现代码如下:
[AdministratorsOnly]
public
class
OrderStatusController : ControllerBase
{
readonly
IRepository
<
Order
>
orderRepository;
readonly
IUserService userService;
readonly
IEmailService emailService;
public
OrderStatusController(IRepository
<
Order
>
orderRepository, IUserService userService, IEmailService emailService)
{
this
.orderRepository
=
orderRepository;
this
.emailService
=
emailService;
this
.userService
=
userService;
}
[UnitOfWork]
public
ActionResult Dispatch(
int
id)
{
var order
=
orderRepository.GetById(id);
if
(order.IsCreated)
{
order.OrderStatusId
=
OrderStatus.DispatchedId;
order.DispatchedDate
=
DateTime.Now;
order.UserId
=
userService.CurrentUser.UserId;
emailService.SendDispatchNotification(order);
}
return
this
.RedirectToAction
<
OrderController
>
(c
=>
c.Item(order.OrderId));
}
}
首先就是把Http请求过来的定单ID作为参数,并调用orderRepository.GetById方法获取该定单ID的相关
信息,然后判断其是否已被创建(IsCreated为“true”), 如果已创建就可以将当前的定单信息中的状态设置
为“DispatchedId”,同时将 DispatchedDate时间设置为系统当前时间,然后是用户ID的绑定。当一切完
成后,就可以将该定单数据作为参数传递给IEmailService的SendDispatchNotification方法,以启动“发送
Email通知买家”的流程了。
下面是接口IEmailService的实现类“EmailService”(Suteki.Shop/Services/EmailService.cs)中相应
方法的实现代码:
public
void
SendDispatchNotification(Order order)
{
var viewdata
=
new
Dictionary
<
string
,
object
>
{
{
"
order
"
, order },
{
"
shopName
"
, baseService.ShopName }
};
var email
=
builder.GetEmailContent(OrderDispatchTemplate, viewdata);
var subject
=
"
{0}: Your Order has Shipped
"
.With(baseService.ShopName);
sender.Send(order.Email, subject, email,
true
);
}
在这里就完成了对EmailBuilder类中的GetEmailContent方法的调用,并最终使用EmailSender(发送
邮件的功能类)来发送邮件(“Send方法”)给指定的买家。
下面就完看一下其最终的运行效果,首先我们要先创建一个定单(注:创建定单的流程在本系列文章的第
一篇中已做过说明,这里就暂且略过了)。然后我们以管理员的身份登录系统,并单击顶部导航的“Online-
Shop”链接,然后点击左侧的“Orders”链接即可看到一个订单列表页面,如下图所示:
然后点击相应的订单“Number”,就会进入到相应订单明细页面,如下:
点击页面中的“Dispatch”链接,之后就会修改当前定单的状态同时发送相应的Email给买家了,这里为
了清楚起见,我在EmailBuilder中设置了一个断点,并截了一张图,来让大家看一下其最终返回的邮件信息内
容:
因为我本地的机器上未安装发送Email的插件,所以无法真正将该Email发送到我指定的邮箱,以便能看到
最终效果,但这并不影响大家对本文的了解,呵呵。
今天就先到这里了。
原文链接:http://www.cnblogs.com/daizhj/archive/2009/06/03/1457521.html
作者: daizhj,代震军,LaoD
Tags: mvc,Suteki.Shop,NVelocity
网址: http://daizhj.cnblogs.com/