zoukankan      html  css  js  c++  java
  • 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/ 
     
       
       
       
     

  • 相关阅读:
    [leetcode]Largest Number
    [leetcode]Second Highest Salary
    [leetcode]Combine Two Tables
    [leetode]Binary Search Tree Iterator
    [leetcode]Excel Sheet Column Number
    [leetcode]Two Sum III
    [leetcode]Majority Element
    Mysql5.7.26解压版(免安装版)简单快速配置步骤,5分钟搞定(win10-64位系统)
    APP移动端自动化测试工具选型“兵器谱”一览(主流开源工具)
    Jmeter+jenkins如何快速搭建接口和性能测试持续集成解决方案-[基于windows篇]
  • 原文地址:https://www.cnblogs.com/daizhj/p/1455867.html
Copyright © 2011-2022 走看看