Asp.net MVC 示例项目"Suteki.Shop"分析之---NVelocity模版引擎

简介:
   在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发送到我指定的邮箱,以便能看到最终效果,但这并不影响大家对本文的了解,呵呵。
   
    
        今天就先到这里了。


本文转自 daizhenjun 51CTO博客,原文链接:http://blog.51cto.com/daizhj/162500,如需转载请自行联系原作者
相关文章
|
1月前
|
数据可视化 网络协议 C#
C#/.NET/.NET Core优秀项目和框架2024年3月简报
公众号每月定期推广和分享的C#/.NET/.NET Core优秀项目和框架(每周至少会推荐两个优秀的项目和框架当然节假日除外),公众号推文中有项目和框架的介绍、功能特点、使用方式以及部分功能截图等(打不开或者打开GitHub很慢的同学可以优先查看公众号推文,文末一定会附带项目和框架源码地址)。注意:排名不分先后,都是十分优秀的开源项目和框架,每周定期更新分享(欢迎关注公众号:追逐时光者,第一时间获取每周精选分享资讯🔔)。
|
2月前
|
安全 数据安全/隐私保护 开发者
三款.NET 代码混淆工具比较分析:ConfuserEx、Obfuscar 和 Ipa Guard
三款.NET 代码混淆工具比较分析:ConfuserEx、Obfuscar 和 Ipa Guard
|
3月前
|
存储 开发框架 NoSQL
ASP.NET WEB——项目中Cookie与Session的用法
ASP.NET WEB——项目中Cookie与Session的用法
41 0
|
3月前
|
开发框架 前端开发 .NET
ASP.NET WEB——项目创建与文件上传操作
ASP.NET WEB——项目创建与文件上传操作
48 0
|
11天前
|
人工智能 自然语言处理 算法
分享几个.NET开源的AI和LLM相关项目框架
分享几个.NET开源的AI和LLM相关项目框架
|
19天前
|
开发框架 前端开发 JavaScript
JavaScript云LIS系统源码ASP.NET CORE 3.1 MVC + SQLserver + Redis医院实验室信息系统源码 医院云LIS系统源码
实验室信息系统(Laboratory Information System,缩写LIS)是一类用来处理实验室过程信息的软件,云LIS系统围绕临床,云LIS系统将与云HIS系统建立起高度的业务整合,以体现“以病人为中心”的设计理念,优化就诊流程,方便患者就医。
22 0
|
27天前
|
存储 测试技术 计算机视觉
高维数据惩罚回归方法:主成分回归PCR、岭回归、lasso、弹性网络elastic net分析基因数据
高维数据惩罚回归方法:主成分回归PCR、岭回归、lasso、弹性网络elastic net分析基因数据
|
2月前
|
开发框架 前端开发 .NET
进入ASP .net mvc的世界
进入ASP .net mvc的世界
32 0
|
2月前
mvc.net分页查询案例——mvc-paper.css
mvc.net分页查询案例——mvc-paper.css
5 0
|
2月前
|
开发框架 前端开发 .NET
C# .NET面试系列六:ASP.NET MVC
<h2>ASP.NET MVC #### 1. MVC 中的 TempData\ViewBag\ViewData 区别? 在ASP.NET MVC中,TempData、ViewBag 和 ViewData 都是用于在控制器和视图之间传递数据的机制,但它们有一些区别。 <b>TempData:</b> 1、生命周期 ```c# TempData 的生命周期是短暂的,数据只在当前请求和下一次请求之间有效。一旦数据被读取,它就会被标记为已读,下一次请求时就会被清除。 ``` 2、用途 ```c# 主要用于在两个动作之间传递数据,例如在一个动作中设置 TempData,然后在重定向到另
121 5