在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();
}
看到这里,如果大家之前看过我写的
注: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方法的实现原理了。下面接着再介绍一下项目中
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