Asp.net MVC 示例項(xiàng)目"Suteki.Shop"分析之---Model和Service
在Suteki.Shop中Model的原型是基于Linq to SQL創(chuàng)建的,其dbml文件位于Suteki.Shop\Shop.dbml。
而Suteki.Shop在此文件的基本上,以"partial class "的方式在Suteki.Shop\Model文件夾下創(chuàng)建了相應(yīng)
的類文件以擴(kuò)展Shop.dbml中Model類的一些方法和屬性聲明,如下圖:
為了便于大家理解,下面以Model中的Product.cs為例進(jìn)行說(shuō)明。
Product是對(duì)網(wǎng)站中所銷售商品的數(shù)據(jù)信息類。在其中定義了一些屬性(聲明在Shop.dbml中):
private int _CategoryId; //產(chǎn)品所屬分類ID
private string _Name; //產(chǎn)品名稱
private string _Description;//產(chǎn)品描述
private decimal _Price; //產(chǎn)品價(jià)格
private int _Position; //在列表中的位置
private int _Weight; //重量
private bool _IsActive; //當(dāng)前是否激活顯示
private string _UrlName; //產(chǎn)品的URL鏈接
而Product.cs這個(gè)文件其實(shí)是以partial方式對(duì)Shop.dbml中的Product類的"擴(kuò)展",下面是其實(shí)現(xiàn)代碼:
{
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
};
}
}
首先要說(shuō)明的是OnNameChanging方法,該方法的聲明如下(位于Shop.dbml中):
并且在dbml中相應(yīng)的產(chǎn)品"Name"屬性中對(duì)其進(jìn)行調(diào)用:
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();
}
}
}
這樣做的目的就是在產(chǎn)品的名稱發(fā)生變更時(shí)調(diào)用該方法以進(jìn)行處理,當(dāng)然該set中還有一些
其它方法的調(diào)用,這里要不多做說(shuō)明了。下面接著看一下partial類中OnNameChanging方法所做
的工作:
{
value.Label("Name").IsRequired();
}
看到這里,如果大家之前看過(guò)我寫(xiě)的這篇文章的話(里面的ValidationProperty類),就會(huì)
清楚這里是要對(duì)當(dāng)前傳入的產(chǎn)品名稱變量進(jìn)行“是否為空”的校驗(yàn)了。同理,還有對(duì)產(chǎn)品描述的
驗(yàn)證規(guī)則:
{
value.Label("Description").IsRequired();
}
這里要說(shuō)明的一點(diǎn)就是在“數(shù)據(jù)驗(yàn)證”一文中所介紹的“用戶信息驗(yàn)證”與上面所使用的信
息驗(yàn)證規(guī)則的綁定方式有所不同。即“用戶信息驗(yàn)證”是在相應(yīng)Action中調(diào)用Validate()方法實(shí)
現(xiàn)的,而上面的這種方式是dbml中以“partial method”方式實(shí)現(xiàn)并在相應(yīng)對(duì)象屬性發(fā)生變化是
觸發(fā)。
同樣,在商品的partial類聲明中還包括一些其它的屬性如:HasMainImage,DefaultSize,
HasSize等。
當(dāng)然,如果您在實(shí)際開(kāi)發(fā)中未使用LinqToSql的話,IDE就不會(huì)為您生成相應(yīng)的數(shù)據(jù)實(shí)體類代
碼,這時(shí)我們可以參考另一個(gè)MVC示例項(xiàng)目“MVCStore”的做法,直接在Model中創(chuàng)建并定義相應(yīng)
的實(shí)體類,其實(shí)我個(gè)人是比較喜歡MVCStore的那種實(shí)體類結(jié)構(gòu),它Model中數(shù)據(jù)都是只有屬性沒(méi)有
方法。巧的是MVCStore中也有一個(gè)叫“Product”的Model類,其位于:Commerce.Data\Model\
Product.cs,大家可以下載其代碼看一下即可。
除了使用“partial”方式對(duì)Shop.dbml所生成的實(shí)體類代碼進(jìn)行完善擴(kuò)展之外,Suteki.Shop
項(xiàng)目也采用了大多數(shù)MVC示范中所使用的“Repository”模式對(duì)Model中類的CRUD方法進(jìn)行封裝,
只不過(guò)它做的更“高明”一些,即使用接口(和泛型接口)方式統(tǒng)一定義了CRUD的名稱,其類圖:
注:Suteki.Shop的作者在這篇文章中提到過(guò),這種架構(gòu)方式是吸取了Ayende 這篇文章的思想。而Ayende就是
Rhino.Commons,Rhino Mocks等軟件作者。
下面是其相關(guān)的接口聲明:
{
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();
}
做為實(shí)現(xiàn)上面兩個(gè)接口的“Repository”類,其承擔(dān)了對(duì)CRUD的具體操作邏輯實(shí)現(xiàn)。
{
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);
}
}
在上面的類圖中,還有兩個(gè)類也很重要,其中IRepositoryResolver是一個(gè)分析器接口,其定義了
“傳入一個(gè)type類型并在Castle容器中獲取該type組件實(shí)例的方法聲明”,而作為其接口具體實(shí)現(xiàn),
“RepositoryResolver”的實(shí)現(xiàn)代碼如下:
{
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框架的核心功能,可以參見(jiàn)我寫(xiě)的這篇文章即可。大家只要
知道其實(shí)現(xiàn)的與我們以前“使用反射方式動(dòng)態(tài)生成相應(yīng)類實(shí)例”的目的相同就行了。
通過(guò)上面這幾個(gè)類,我們?yōu)镸odel中的類提供了“CRUD”方法,這要比之前我所看到的一些
MVC示例相應(yīng)中的Repository模式實(shí)現(xiàn)的要好一些。當(dāng)然我猜測(cè)這種實(shí)現(xiàn)也是有性能問(wèn)題的,比
如說(shuō)對(duì)反射的使用,希望借助castle框架能將這個(gè)問(wèn)題化解。
有了“Repository”類的幫助,讓“Repositories”下的文件夾中的文件少了許多,當(dāng)然上
面的接口方法未必就能把所有的CRUD操作全部覆蓋,比如Product就需要有這樣一個(gè)功能,即按
“商品所屬分類”來(lái)獲取同一類下的所有商品。而這個(gè)功能的實(shí)現(xiàn)最終還是要在Repositories
文件夾下創(chuàng)建一個(gè)類,名為“ProductRepositoryExtensions”:
{
public static IQueryable<Product> WhereCategoryIdIs(this IQueryable<Product> products, int categoryId)
{
return products.Where(p => p.CategoryId == categoryId);
}
}
但這已經(jīng)是比“實(shí)現(xiàn)所有的CRUD方法的代碼”那種情況下的行數(shù)少了許多了。
有了這種實(shí)現(xiàn)方式之后,就可以在Controller中定義相應(yīng)的Model Repository實(shí)例了,其
行如(Suteki.Shop\Controllers\ProductController.cs):
{
readonly IRepository<Product> productRepository;
readonly IRepository<Category> categoryRepository;

}
看到這里,大家應(yīng)該基本搞清楚該項(xiàng)目中Model和相關(guān)的CRUD方法的實(shí)現(xiàn)原理了。下面接著再介
紹一下項(xiàng)目中Services的實(shí)現(xiàn)。
首先,可以說(shuō)其所有Service都有相關(guān)的接口進(jìn)行定義。
以UserService類為例(Suteki.Shop\Services\UserService.cs),其實(shí)現(xiàn)了“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);
}
這樣做的原因相信大家都清楚, 就是將來(lái)如果業(yè)務(wù)規(guī)則變化時(shí)(對(duì)應(yīng)service接口實(shí)現(xiàn)類也要發(fā)
生變化),這時(shí)不需要真正修改已有的代碼,只需再開(kāi)發(fā)一個(gè)相應(yīng)的實(shí)現(xiàn)類即可滿足需求,這種擴(kuò)展
方式也是與設(shè)計(jì)模式中的思想相符合的。除此之外,我再談一下我對(duì)該項(xiàng)目中Service實(shí)現(xiàn)的一些個(gè)
人觀點(diǎn):
Suteki.Shop對(duì)于其Service的封裝我認(rèn)為并不好,原因就在于“Model中是否應(yīng)該包括業(yè)務(wù)邏輯”
這個(gè)問(wèn)題上,我本人感覺(jué)對(duì)于小項(xiàng)目而言(Suteki.Shop,MVCStore就是這樣的小項(xiàng)目),還真談不
上什么充血模型。能把一個(gè)貧血模型實(shí)現(xiàn)并用好了就完全可以了。而Suteki.Shop中的Model中過(guò)多的
注入了方法代碼,其中有些應(yīng)該是放在Service中。
這一點(diǎn)我是強(qiáng)烈建議參考MVCStore下的“Commerce.Services”這個(gè)項(xiàng)目的實(shí)現(xiàn),其實(shí)現(xiàn)的方式
我感覺(jué)非常符合當(dāng)下SOA倡導(dǎo)的架構(gòu)方式。有關(guān)這方面的內(nèi)容我在這篇文章中就已闡述,就不做說(shuō)明了。
好了,今天的內(nèi)容就先到這里了。
原文鏈接: http://www.rzrgm.cn/daizhj/archive/2009/05/31/1455867.html
作者: daizhj,代震軍,LaoD
Tags: mvc,Suteki.Shop
網(wǎng)址: http://daizhj.cnblogs.com/

浙公網(wǎng)安備 33010602011771號(hào)