Asp.net MVC 示例项目"Suteki.Shop"分析之---Model和Service

     在Suteki.Shop中Model的原型是基于Linq to SQL创建的,其dbml文件位于Suteki.Shop/Shop.dbml。
而Suteki.Shop在此文件的基本上,以"partial class "的方式在Suteki.Shop/Model文件夹下创建了相应
的类文件以扩展Shop.dbml中Model类的一些方法和属性声明,如下图:

         
     为了便于大家理解,下面以Model中的Product.cs为例进行说明。
   
     Product是对网站中所销售商品的数据信息类。在其中定义了一些属性(声明在Shop.dbml中):

   private   int  _ProductId;   // 产品ID
   private   int  _CategoryId;     // 产品所属分类ID
   private   string  _Name;   // 产品名称
   private   string  _Description; // 产品描述  
   private   decimal  _Price;   // 产品价格
   private   int  _Position;   // 在列表中的位置
   private   int  _Weight;   // 重量
   private   bool  _IsActive;   // 当前是否激活显示
   private   string  _UrlName;     // 产品的URL链接

    

     而Product.cs这个文件其实是以partial方式对Shop.dbml中的Product类的"扩展",下面是其实现代码:

public   partial   class  Product : IOrderable, IActivatable, IUrlNamed
{
    
partial   void  OnNameChanging( string  value)
    {
        value.Label(
" Name " ).IsRequired();
    }

    
partial   void  OnNameChanged()
    {
        UrlName 
=  Name.ToUrlFriendly();
    }

    
partial   void  OnDescriptionChanging( string  value)
    {
        value.Label(
" Description " ).IsRequired();
    }

    
public   bool  HasMainImage
    {
        
get
        {
            
return   this .ProductImages.Count  >   0 ;
        }
    }

    
public  Image MainImage
    {
        
get
        {
            
if  (HasMainImage)  return   this .ProductImages.InOrder().First().Image;
            
return   null ;
        }
    }

    
public   bool  HasSize
    {
        
get
        {
            
return   this .Sizes.Active().Count()  >   0 ;
        }
    }

    
public  Size DefaultSize
    {
        
get
        {
            
if  ( this .Sizes.Count()  ==   0 throw   new  ApplicationException( " Product has no default size " );
            
return   this .Sizes[ 0 ];
        }
    }

    
public   string  IsActiveAsString
    {
        
get
        {
            
if  (IsActive)  return   string .Empty;
            
return   "  Not Active " ;
        }
    }

 
public   static  Product DefaultProduct( int  parentCategory,  int  position)
 {
  
return   new  Product 
  {
   ProductId 
=   0 ,
   CategoryId 
=  parentCategory,
   Position 
=  position
  };

 }
}

    
     首先要说明的是OnNameChanging方法,该方法的声明如下(位于Shop.dbml中):     
     

partial   void  OnNameChanging( string  value);

    
     并且在dbml中相应的产品"Name"属性中对其进行调用:
  

[Column(Storage = " _Name " , DbType = " NVarChar(250) NOT NULL " , CanBeNull = false )]
    
public   string  Name
    {
            
get
            {
                
return   this ._Name;
            }
            
set
            {
                    
if  (( this ._Name  !=  value))
                    {
                        
this .OnNameChanging(value);
                        
this .SendPropertyChanging();
                        
this ._Name  =  value;
                        
this .SendPropertyChanged( " Name " );
                        
this .OnNameChanged();
                    }
            }
    }

 
     这样做的目的就是在产品的名称发生变更时调用该方法以进行处理,当然该set中还有一些
其它方法的调用,这里要不多做说明了。下面接着看一下partial类中OnNameChanging方法所做
的工作:

     partial   void  OnNameChanging( string  value)
    {
        value.Label(
" Name " ).IsRequired();
    }


    
     看到这里,如果大家之前看过我写的这篇文章的话(里面的ValidationProperty类),就会   
清楚这里是要对当前传入的产品名称变量进行“是否为空”的校验了。同理,还有对产品描述的
验证规则:

     partial   void  OnDescriptionChanging( string  value)
    {
        value.Label(
" Description " ).IsRequired();
    }  

 
    
     这里要说明的一点就是在“数据验证”一文中所介绍的“用户信息验证”与上面所使用的信
息验证规则的绑定方式有所不同。即“用户信息验证”是在相应Action中调用Validate()方法实
现的,而上面的这种方式是dbml中以“partial method”方式实现并在相应对象属性发生变化是
触发。

     同样,在商品的partial类声明中还包括一些其它的属性如:HasMainImage,DefaultSize,
HasSize
等。
   
      当然,如果您在实际开发中未使用LinqToSql的话,IDE就不会为您生成相应的数据实体类代
码,这时我们可以参考另一个MVC示例项目“MVCStore”的做法,直接在Model中创建并定义相应
的实体类,其实我个人是比较喜欢MVCStore的那种实体类结构,它Model中数据都是只有属性没有
方法。巧的是MVCStore中也有一个叫“Product”的Model类,其位于:Commerce.Data/Model/
Product.cs,大家可以下载其代码看一下即可。


      除了使用“partial”方式对Shop.dbml所生成的实体类代码进行完善扩展之外,Suteki.Shop
项目也采用了大多数MVC示范中所使用的“Repository”模式对Model中类的CRUD方法进行封装,
只不过它做的更“高明”一些,即使用接口(和泛型接口)方式统一定义了CRUD的名称,其类图:

    

      

     注:Suteki.Shop的作者在这篇文章中提到过,这种架构方式是吸取了Ayende 这篇文章的思想。而Ayende就是

Rhino.Commons,Rhino Mocks等软件作者。

     下面是其相关的接口声明:    
   

  public   interface  IRepository < T >   where  T :  class
    {
        T GetById(
int  id);
        IQueryable
< T >  GetAll();
        
void  InsertOnSubmit(T entity);
        
void  DeleteOnSubmit(T entity);
        [Obsolete(
" Units of Work should be managed externally to the Repository. " )]
        
void  SubmitChanges();
    }

    
public   interface  IRepository
    {
        
object  GetById( int  id);
        IQueryable GetAll();
        
void  InsertOnSubmit( object  entity);
        
void  DeleteOnSubmit( object  entity);
        [Obsolete(
" Units of Work should be managed externally to the Repository. " )]
        
void  SubmitChanges();
    }

     
     做为实现上面两个接口的“Repository”类,其承担了对CRUD的具体操作逻辑实现。   
   

  public   class  Repository < T >  : IRepository < T > , IRepository  where  T :  class
    {
        
readonly  DataContext dataContext;

        
public  Repository(IDataContextProvider dataContextProvider)
        {
            dataContext 
=  dataContextProvider.DataContext;
        }

        
public   virtual  T GetById( int  id)
        {
            var itemParameter 
=  Expression.Parameter( typeof (T),  " item " );

            var whereExpression 
=  Expression.Lambda < Func < T,  bool >>
                (
                Expression.Equal(
                    Expression.Property(
                        itemParameter,
                        
typeof (T).GetPrimaryKey().Name
                        ),
                    Expression.Constant(id)
                    ),
                
new [] { itemParameter }
                );

            
return  GetAll().Where(whereExpression).Single();
        }

        
public   virtual  IQueryable < T >  GetAll()
        {
            
return  dataContext.GetTable < T > ();
        }

        
public   virtual   void  InsertOnSubmit(T entity)
        {
            GetTable().InsertOnSubmit(entity);
        }

        
public   virtual   void  DeleteOnSubmit(T entity)
        {
            GetTable().DeleteOnSubmit(entity);
        }

        
public   virtual   void  SubmitChanges()
        {
            dataContext.SubmitChanges();
        }

        
public   virtual  ITable GetTable()
        {
            
return  dataContext.GetTable < T > ();
        }

        IQueryable IRepository.GetAll()
        {
            
return  GetAll();
        }

        
void  IRepository.InsertOnSubmit( object  entity)
        {
            InsertOnSubmit((T)entity);
        }

        
void  IRepository.DeleteOnSubmit( object  entity)
        {
            DeleteOnSubmit((T)entity);
        }

        
object  IRepository.GetById( int  id)
        {
            
return  GetById(id);
        }
    }

    
     在上面的类图中,还有两个类也很重要,其中IRepositoryResolver是一个分析器接口,其定义了
“传入一个type类型并在Castle容器中获取该type组件实例的方法声明”,而作为其接口具体实现,
RepositoryResolver”的实现代码如下:
   

  public   class  RepositoryResolver : IRepositoryResolver
    {
        
private   readonly  IKernel kernel;

        
public  RepositoryResolver(IKernel kernel)
        {
            
this .kernel  =  kernel;
        }

        
public  IRepository < T >  GetRepository < T > ()  where  T :  class  
        {
            Type repositoryType 
=   typeof (IRepository <> ).MakeGenericType( new [] {  typeof (T) });

            var repository 
=  kernel.Resolve(repositoryType)  as  IRepository < T > ;
            
if  (repository  ==   null )
            {
                
throw   new  ApplicationException(StringExtensions.With( " Could not find IRepository<{0}> in kernel " typeof (T).Name));
            }
            
return  repository;
        }

        
public  IRepository GetRepository(Type type)
        {
            Type repositoryType 
=   typeof (IRepository <> ).MakeGenericType( new [] { type });

            var repository 
=  kernel.Resolve(repositoryType);
            
            
if  (repository  ==   null )
            {
                
throw   new  ApplicationException( " Could not find IRepository<{0}> in kernel " .With(type.Name));
            }

            
if  ((repository  as  IRepository)  ==   null )
            {
                
throw   new  ApplicationException( " The repository that implements IRepository<{0}> does not implement IRepository " .With(type.Name));
            }

            
return  (IRepository)repository;
        }
    }

   

     上面的部分代码涉及到了castle框架的核心功能,可以参见我写的这篇文章即可。大家只要
知道其实现的与我们以前“使用反射方式动态生成相应类实例”的目的相同就行了。

     通过上面这几个类,我们为Model中的类提供了“CRUD”方法,这要比之前我所看到的一些
MVC示例相应中的Repository模式实现的要好一些。当然我猜测这种实现也是有性能问题的,比
如说对反射的使用,希望借助castle框架能将这个问题化解。

     有了“Repository”类的帮助,让“Repositories”下的文件夹中的文件少了许多,当然上
面的接口方法未必就能把所有的CRUD操作全部覆盖,比如Product就需要有这样一个功能,即按
“商品所属分类”来获取同一类下的所有商品。而这个功能的实现最终还是要在Repositories
文件夹下创建一个类,名为“ProductRepositoryExtensions”:

public   static   class  ProductRepositoryExtensions
{
    
public   static  IQueryable < Product >  WhereCategoryIdIs( this  IQueryable < Product >  products,  int  categoryId)
    {
        
return  products.Where(p  =>  p.CategoryId  ==  categoryId);
    }
}


     但这已经是比“实现所有的CRUD方法的代码”那种情况下的行数少了许多了。
   
   
     有了这种实现方式之后,就可以在Controller中定义相应的Model Repository实例了,其
行如(Suteki.Shop/Controllers/ProductController.cs):

public   class  ProductController : ControllerBase
{
     
readonly  IRepository < Product >  productRepository;
     
readonly  IRepository < Category >  categoryRepository;
               
}


        
     看到这里,大家应该基本搞清楚该项目中Model和相关的CRUD方法的实现原理了。下面接着再介
绍一下项目中Services的实现。

     首先,可以说其所有Service都有相关的接口进行定义。

     以UserService类为例(Suteki.Shop/Services/UserService.cs),其实现了“IUserService”
接口,如下: 

public   interface  IUserService
{
        User CreateNewCustomer();
        User CurrentUser { 
get ; }
        
void  SetAuthenticationCookie( string  email);
        
void  SetContextUserTo(User user);
        
void  RemoveAuthenticationCookie();
        
string  HashPassword( string  password);
        
bool  Authenticate( string  email,  string  password);
}


    
     这样做的原因相信大家都清楚, 就是将来如果业务规则变化时(对应service接口实现类也要发
生变化),这时不需要真正修改已有的代码,只需再开发一个相应的实现类即可满足需求,这种扩展
方式也是与设计模式中的思想相符合的。除此之外,我再谈一下我对该项目中Service实现的一些个
人观点:    
     Suteki.Shop对于其Service的封装我认为并不好,原因就在于“Model中是否应该包括业务逻辑”
这个问题上,我本人感觉对于小项目而言(Suteki.Shop,MVCStore就是这样的小项目),还真谈不
上什么充血模型。能把一个贫血模型实现并用好了就完全可以了。而Suteki.Shop中的Model中过多的
注入了方法代码,其中有些应该是放在Service中。

     这一点我是强烈建议参考MVCStore下的“Commerce.Services”这个项目的实现,其实现的方式
我感觉非常符合当下SOA倡导的架构方式。有关这方面的内容我在这篇文章中就已阐述,就不
做说明了。

     
     好了,今天的内容就先到这里了。    
    
     原文链接: http://www.cnblogs.com/daizhj/archive/2009/05/31/1455867.html

     作者: daizhj,代震军,LaoD

     Tags: mvc,Suteki.Shop

     网址: http://daizhj.cnblogs.com/ 
 
   
   
   
 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值