• 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() == 0throw 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/ 
     
       
       
       
     

  • 相关阅读:
    python--模块与包
    内置函数 的总结
    迭代器 生成器 列表推导式 生成器表达式的一些总结
    函数的有用信息 带参数的装饰器 多个装饰器装饰一个函数
    函数名的应用(第一对象) 闭包 装饰器
    动态参数 名称空间 作用域 作用域链 加载顺序 函数的嵌套 global nonlocal 等的用法总结
    函数的初识 函数的返回值 参数
    文件操作 常用操作方法 文件的修改
    遍历字典的集中方法 集合的作用 以及增删查的方法
    计算机硬件的小知识
  • 原文地址:https://www.cnblogs.com/daizhj/p/1455867.html
Copyright © 2020-2023  润新知