在Suteki.Shop中对于Filter的使用上提供了两种方式,一种是从FilterAttribute
(抽象类属性)以及接口 IActionFilter和 IResultFilter中继承并实现。另一种是我们经
常提到的从ActionFilterAttribute 上继承方式来实现自己的ActionFilter。首先看一下
第一种,同时它也是该项目中被Action广泛使用的方式, 下面是类图:
当然图中最核心的当属FilterUsingAttribute,它同时继承了 FilterAttribute类和
IAuthorizationFilter, IActionFilter, IResultFilter这三个接口,所以其所实现的功能
与MVC中所定义的ActionFilterAttribute如出一辙。同时,下面是其核心代码:
public
class
FilterUsingAttribute : FilterAttribute, IAuthorizationFilter, IActionFilter, IResultFilter
{
private
readonly
Type filterType;
private
object
instantiatedFilter;
public
FilterUsingAttribute(Type filterType)
{
if
(
!
IsFilterType(filterType))
{
throw
new
InvalidOperationException(
"
Type '{0}' is not valid within the FilterUsing
attribute
as
it
is
not a filter type.
"
.With(filterType.Name));
}
this
.filterType
=
filterType;
}
private
bool
IsFilterType(Type type)
{
return
typeof
(IAuthorizationFilter).IsAssignableFrom(type)
||
typeof
(IActionFilter).IsAssignableFrom(type)
||
typeof
(IResultFilter).IsAssignableFrom(type);
}
public
Type FilterType
{
get
{
return
filterType; }
}
private
T GetFilter
<
T
>
()
where
T :
class
{
if
(instantiatedFilter
==
null
)
{
instantiatedFilter
=
ServiceLocator.Current.GetInstance(filterType);
}
return
instantiatedFilter
as
T;
}
private
void
ExecuteFilterWhenItIs
<
TFilter
>
(Action
<
TFilter
>
action)
where
TFilter :
class
{
var filter
=
GetFilter
<
TFilter
>
();
if
(filter
!=
null
)
{
action(filter);
}
}
public
void
OnAuthorization(AuthorizationContext filterContext)
{
ExecuteFilterWhenItIs
<
IAuthorizationFilter
>
(f
=>
f.OnAuthorization(filterContext));
}
public
void
OnActionExecuting(ActionExecutingContext filterContext)
{
ExecuteFilterWhenItIs
<
IActionFilter
>
(f
=>
f.OnActionExecuting(filterContext));
}
public
void
OnActionExecuted(ActionExecutedContext filterContext)
{
ExecuteFilterWhenItIs
<
IActionFilter
>
(f
=>
f.OnActionExecuted(filterContext));
}
public
void
OnResultExecuting(ResultExecutingContext filterContext)
{
ExecuteFilterWhenItIs
<
IResultFilter
>
(f
=>
f.OnResultExecuting(filterContext));
}
public
void
OnResultExecuted(ResultExecutedContext filterContext)
{
ExecuteFilterWhenItIs
<
IResultFilter
>
(f
=>
f.OnResultExecuted(filterContext));
}
}
在上面的OnAction..和OnResult..事件中,都调用了ExecuteFilterWhenItIs这个泛型方法,而该方法的作用是对泛型约束中使用到的相应IActionFilter进行操作,而获取相应的Filter实例的工作就交给了GetFilter<T>()方法,因为该方法使用IOC方式将filterType以服务组件的方式进行创建,所以我们会看到在ContainerBuilder(Suteki.Shop\ContainerBuilder.cs)中有如下代码,注意最后一行:
container.Register(
Component.For
<
IUnitOfWorkManager
>
().ImplementedBy
<
LinqToSqlUnitOfWorkManager
>
().LifeStyle.Transient,
Component.For
<
IFormsAuthentication
>
().ImplementedBy
<
FormsAuthenticationWrapper
>
(),
Component.For
<
IServiceLocator
>
().Instance(
new
WindsorServiceLocator(container)),
看来其最终会使用Castle框架所提供的IOC功能。
其实理解上面代码并不难,就是Suteki.Shop以自己实现的FilterUsingAttribute代替了MVC自己的 ActionFilterAttribute, (当然它做的并不彻底,大家会在接下来的内容中看到)。当然有了FilterUsingAttribute之后,Suteki.Shop并没有直接就去在Action中直接使用它,而是以它派生出了几个Filter属性:
UnitOfWorkAttribute:项目中大部分Action使用
AuthenticateAttribute:用户信息认证
LoadUsingAttribute
其中UnitOfWorkAttribute和AuthenticateAttribute被用的最多,下面就分别加以介绍说明。
首先是UnitOfWorkAttribute,其构造方法声明中将UnitOfWorkFilter作为其基类方法的构造类型,如下:
public
class
UnitOfWorkAttribute : FilterUsingAttribute
{
public
UnitOfWorkAttribute() :
base
(
typeof
(UnitOfWorkFilter))
{
}
}
public
class
UnitOfWorkFilter : IActionFilter
{
private
readonly
IDataContextProvider provider;
public
UnitOfWorkFilter(IDataContextProvider provider)
{
this
.provider
=
provider;
}
public
void
OnActionExecuting(ActionExecutingContext filterContext)
{
}
public
void
OnActionExecuted(ActionExecutedContext filterContext)
{
var context
=
provider.DataContext;
if
(filterContext.Controller.ViewData.ModelState.IsValid)
{
context.SubmitChanges();
}
}
}
其要实现的功能主要是对用户所做的数据操作进行判断,如果没有发生异常:
ModelState.IsValid为True时,则提交所做的修改到数据库中。
接下来再看一个AuthenticateAttribute,前面说过,其所实现的功能就是对当前
用户身份进行验证,其核心代码如下,因为内容比较简单,大家一看便知。
public
class
AuthenticateAttribute : FilterUsingAttribute
{
public
AuthenticateAttribute() :
base
(
typeof
(AuthenticateFilter))
{
Order
=
0
;
}
}
public
class
AuthenticateFilter : IAuthorizationFilter
{
private
IRepository
<
User
>
userRepository;
private
IFormsAuthentication formsAuth;
public
AuthenticateFilter(IRepository
<
User
>
userRepository, IFormsAuthentication formsAuth)
{
this
.userRepository
=
userRepository;
this
.formsAuth
=
formsAuth;
}
public
void
OnAuthorization(AuthorizationContext filterContext)
{
var context
=
filterContext.HttpContext;
if
(context.User
!=
null
&&
context.User.Identity.IsAuthenticated)
{
var email
=
context.User.Identity.Name;
var user
=
userRepository.GetAll().WhereEmailIs(email);
if
(user
==
null
)
{
formsAuth.SignOut();
}
else
{
AuthenticateAs(context, user);
return
;
}
}
AuthenticateAs(context, User.Guest);
}
private
void
AuthenticateAs(HttpContextBase context, User user)
{
Thread.CurrentPrincipal
=
context.User
=
user;
}
}
当然在本文开篇说过,Suteki.Shop也使用了我们经常用到的方式,即从ActionFilterAttribute继承实现自己的ActionFilter,比如说CopyMessageFromTempDataToViewData(Suteki.Shop\Filters\CopyMessageFromTempDataToViewData.cs),从字面可以看出,其要实现的功能就是把临时数据复制到ViewData中,以便于前台视图显示,下面是其类图:
其实现代码如下:
public
class
CopyMessageFromTempDataToViewData : ActionFilterAttribute
{
public
override
void
OnActionExecuted(ActionExecutedContext filterContext)
{
var result
=
filterContext.Result
as
ViewResult;
if
(result
!=
null
&&
filterContext.Controller.TempData.ContainsKey(
"
message
"
))
{
var model
=
result.ViewData.Model
as
ShopViewData;
if
(model
!=
null
&&
string
.IsNullOrEmpty(model.Message))
{
model.Message
=
filterContext.Controller.TempData[
"
message
"
]
as
string
;
}
}
}
}
其实看到这里,我感觉Suteki.Shop对于ActionFilter的使用还有待商榷,必定 MVC中的 Filter是一种耗时的操作,对于程序的运行速度和执行效率来说都是一个考验。这其实也能部分解释为什么我在本地运行Suteki.Shop时速度会比较慢。
这里不妨开句玩笑,Suteki.Shop开发者似乎得到ActionFilter强迫症,因为我感觉一个项目中一个Action绑定的Filter最好别超过2个,否则必然会影响程序运行效率,尽管Suteki.Shop基本上控制在了2个左右,但其还是运行速度偏慢。当然这里我并没有做到具体的测试,只是部分猜测,不过有兴趣的朋友不妨测试一下,看看结果如何,相信会见分晓。
好了,今天的内容就先到这里了。