• 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<stringobject> viewdata);
     }


     
         而做为IEmailBuilder接口的实现类,EMailBuilder 中相应的GetEamilContent方法实现代码如下:

    public string GetEmailContent(string templateName, IDictionary<stringobject> viewdata)
    {
        
    return BuildEmail(templateName, viewdata);
    }

    string BuildEmail(string templateName, IDictionary<stringobject> 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<stringobject>
        {
           { 
    "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/
      
       

  • 相关阅读:
    Windows环境配置HTTP服务(Windows + Apache + Mysql + PHP)
    浏览器兼容innerText nextElementSibling firstElementChild
    JavaScript倒计时
    JavaScript数组去重
    模拟javascript中的sort排序
    相对路径与绝对路径
    近阶段的总结
    随机改变颜色返回#+...样式的值
    pc端图片文件上传
    小程序开发的心得
  • 原文地址:https://www.cnblogs.com/daizhj/p/1457521.html
Copyright © 2020-2023  润新知