MVC Model Binder
Filter(篩選器)是基于AOP(面向方面編程)的設(shè)計(jì),它的作用是對(duì)MVC框架處理客戶端請(qǐng)求注入額外的邏輯,以非常簡(jiǎn)單優(yōu)美的方式實(shí)現(xiàn) 橫切關(guān)注點(diǎn)(Cross-cutting Concerns) 。橫切關(guān)注點(diǎn)是指橫越應(yīng)該程序的多個(gè)甚至所有模塊的功能,經(jīng)典的橫切關(guān)注點(diǎn)有日志記錄、緩存處理、異常處理和權(quán)限驗(yàn)證等。本文將分別介紹MVC框架所支持的不同種類的Filter的創(chuàng)建和使用,以及如何控制它們的執(zhí)行。
本文目錄
四種基本 Filter 概述
MVC框架支持的Filter可以歸為四類,每一類都可以對(duì)處理請(qǐng)求的不同時(shí)間點(diǎn)引入額外的邏輯處理。這四類Filter如下表:
在MVC框架調(diào)用acion之前,它會(huì)先判斷有沒有實(shí)現(xiàn)上表中的接口的特性,如果有,則在請(qǐng)求管道的適當(dāng)?shù)狞c(diǎn)調(diào)用特性中定義的方法。
MVC框架為這些種類的Filter接口實(shí)現(xiàn)了默認(rèn)的特性類。如上表,ActionFilterAttribute 類實(shí)現(xiàn)了 IActionFilter 和 IResultFilter 兩個(gè)接口,這個(gè)類是一個(gè)抽象類,必須對(duì)它提供實(shí)現(xiàn)。另外兩個(gè)特性類,AuthorizeAttribute 和 HandleErrorAttribute, 已經(jīng)提供了一些有用的方法,可以直接使用。
Filter 既能應(yīng)用在單個(gè)的ation方法上,也能應(yīng)用在整個(gè)controller上,并可以在acion和controller上應(yīng)用多個(gè)Filter。如下所示:
[Authorize(Roles=
"
trader
"
)]
//
對(duì)所有action有效
public
class
ExampleController : Controller {
[ShowMessage]
//
對(duì)當(dāng)前ation有效
[OutputCache(Duration=
60
)]
//
對(duì)當(dāng)前ation有效
public
ActionResult Index() {
//
...
}
}
注意,對(duì)于自定義的controller的基類,應(yīng)用于該基類的Filter也將對(duì)繼承自該基類的所有子類有效。
Authorization Filter
Authorization Filter是在action方法和其他種類的Filter之前運(yùn)行的。它的作用是強(qiáng)制實(shí)施權(quán)限策略,保證action方法只能被授權(quán)的用戶調(diào)用。Authorization Filter實(shí)現(xiàn)的接口如下:
namespace
System.Web.Mvc {
public
interface
IAuthorizationFilter {
void
OnAuthorization(AuthorizationContext filterContext);
}
}
自定義Authorization Filter
你可以自己實(shí)現(xiàn) IAuthorizationFilter 接口來(lái)創(chuàng)建自己的安全認(rèn)證邏輯,但一般沒有這個(gè)必要也不推薦這樣做。如果要自定義安全認(rèn)證策略,更安全的方式是繼承默認(rèn)的?AuthorizeAttribute 類。
我們下面通過(guò)繼承?AuthorizeAttribute 類來(lái)演示自定義Authorization Filter。新建一個(gè)空MVC應(yīng)用程序,和往常的示例一樣添加一個(gè) Infrastructure 文件夾,然后添加一個(gè)?CustomAuthAttribute.cs 類文件,代碼如下:
namespace
MvcApplication1.Infrastructure {
public
class
CustomAuthAttribute : AuthorizeAttribute {
private
bool
localAllowed;
public
CustomAuthAttribute(
bool
allowedParam) {
localAllowed
=
allowedParam;
}
protected
override
bool
AuthorizeCore(HttpContextBase httpContext) {
if
(httpContext.Request.IsLocal) {
return
localAllowed;
}
else
{
return
true
;
}
}
}
}
這個(gè)簡(jiǎn)單的Filter,通過(guò)重寫 AuthorizeCore 方法,允許我們阻止本地的請(qǐng)求,在應(yīng)用該Filter時(shí),可以通過(guò)構(gòu)造函數(shù)來(lái)指定是否允許本地請(qǐng)求。AuthorizeAttribte 類幫我們內(nèi)置地實(shí)現(xiàn)了很多東西,我們只需把重點(diǎn)放在?AuthorizeCore 方法上,在該方法中實(shí)現(xiàn)權(quán)限認(rèn)證的邏輯。
為了演示這個(gè)Filter的作用,我們新建一個(gè)名為?Home 的 controller,然后在 Index action方法上應(yīng)用這個(gè)Filter。參數(shù)設(shè)置為false以保護(hù)這個(gè) action 不被本地訪問,如下:
public
class
HomeController : Controller {
[CustomAuth(
false
)]
public
string
Index() {
return
"
This is the Index action on the Home controller
"
;
}
}
運(yùn)行程序,根據(jù)系統(tǒng)生成的默認(rèn)路由值,將請(qǐng)求 /Home/Index,結(jié)果如下:
我們通過(guò)把?AuthorizeAttribute 類作為基類自定義了一個(gè)簡(jiǎn)單的Filter,那么?AuthorizeAttribute 類本身作為Filter有哪些有用的功能呢?
使用內(nèi)置的Authorization Filter
當(dāng)我們直接使用?AuthorizeAttribute 類作為Filter時(shí),可以通過(guò)兩個(gè)屬性來(lái)指定我們的權(quán)限策略。這兩個(gè)屬性及說(shuō)明如下:
- Users屬性,string類型,指定允許訪問action方法的用戶名,多個(gè)用戶名用逗號(hào)隔開。
- Roles屬性,string類型,用逗號(hào)分隔的角色名,訪問action方法的用戶必須屬于這些角色之一。
使用如下:
public
class
HomeController : Controller {
[Authorize(Users
=
"
jim, steve, jack
"
, Roles =
"
admin
"
)]
public
string
Index() {
return
"
This is the Index action on the Home controller
"
;
}
}
這里我們?yōu)镮ndex方法應(yīng)用了Authorize特性,并同時(shí)指定了能訪問該方法的用戶和角色。要訪問Index action,必須兩者都滿足條件,即用戶名必須是?jim, steve, jack 中的一個(gè),而且必須屬性 admin 角色。
另外,如果不指定任何用戶名和角色名(即 [Authorize] ),那么只要是登錄用戶都能訪問該action方法。
你可以通過(guò)創(chuàng)建一個(gè)Internet模板的應(yīng)用程序來(lái)看一下效果,這里就不演示了。
對(duì)于大部分應(yīng)用程序,AuthorizeAttribute 特性類提供的權(quán)限策略是足夠用的。如果你有特殊的需求,則可以通過(guò)繼承AuthorizeAttribute 類來(lái)滿足。
Exception Filter
Exception Filter,在下面三種來(lái)源拋出未處理的異常時(shí)運(yùn)行:
- 另外一種Filter(如Authorization、Action或Result等Filter)。
- Action方法本身。
- Action方法執(zhí)行完成(即處理ActionResult的時(shí)候)。
Exception Filter必須實(shí)現(xiàn)?IExceptionFilter 接口,該接口的定義如下:
namespace
System.Web.Mvc {
public
interface
IExceptionFilter {
void
OnException(ExceptionContext filterContext);
}
}
ExceptionContext 常用屬性說(shuō)明
在 IExceptionFilter 的接口定義中,唯一的 OnException 方法在未處理的異常引發(fā)時(shí)執(zhí)行,其中參數(shù)的類型:ExceptionContext,它繼承自?ControllerContext 類,ControllerContext?包含如下常用的屬性:
- Controller,返回當(dāng)前請(qǐng)求的controller對(duì)象。
- HttpContext,提供請(qǐng)求和響應(yīng)的詳細(xì)信息。
- IsChildAction,如果是子action則返回true(稍后將簡(jiǎn)單介紹子action)。
- RequestContext,提供請(qǐng)求上下文信息。
- RouteData,當(dāng)前請(qǐng)求的路由實(shí)例信息。
作為繼承?ControllerContext?類的子類,ExceptionContext 類還提供了以下對(duì)處理異常的常用屬性:
- ActionDescriptor,提供action方法的詳細(xì)信息。
- Result,是一個(gè)?ActionResult 類型,通過(guò)把這個(gè)屬性值設(shè)為非空可以讓某個(gè)Filter的執(zhí)行取消。
- Exception,未處理異常信息。
- ExceptionHandled,如果另外一個(gè)Filter把這個(gè)異常標(biāo)記為已處理則返回true。
一個(gè)Exception Filter可以通過(guò)把?ExceptionHandled 屬性設(shè)置為true來(lái)標(biāo)注該異常已被處理過(guò),這個(gè)屬性一般在某個(gè)action方法上應(yīng)用了多個(gè)Exception Filter時(shí)會(huì)用到。ExceptionHandled 屬性設(shè)置為true后,就可以通過(guò)該屬性的值來(lái)判斷其它應(yīng)用在同一個(gè)action方法Exception Filter是否已經(jīng)處理了這個(gè)異常,以免同一個(gè)異常在不同的Filter中重復(fù)被處理。
示例演示
在?Infrastructure 文件夾下添加一個(gè)?RangeExceptionAttribute.cs 類文件,代碼如下:
public
class
RangeExceptionAttribute : FilterAttribute, IExceptionFilter {
public
void
OnException(ExceptionContext filterContext) {
if
(!filterContext.ExceptionHandled &&
filterContext.Exception
is
ArgumentOutOfRangeException) {
filterContext.Result
=
new
RedirectResult(
"
~/Content/RangeErrorPage.html
"
);
filterContext.ExceptionHandled
=
true
;
}
}
}
這個(gè)Exception Filter通過(guò)重定向到Content目錄下的一個(gè)靜態(tài)html文件來(lái)顯示友好的 ArgumentOutOfRangeException 異常信息。我們定義的?RangeExceptionAttribute 類繼承了FilterAttribute類,并且實(shí)現(xiàn)了IException接口。作為一個(gè)MVC Filter,它的類必須實(shí)現(xiàn)IMvcFilter接口,你可以直接實(shí)現(xiàn)這個(gè)接口,但更簡(jiǎn)單的方法是繼承?FilterAttribute 基類,該基類實(shí)現(xiàn)了一些必要的接口并提供了一些有用的基本特性,比如按照默認(rèn)的順序來(lái)處理Filter。
在Content文件夾下面添加一個(gè)名為RangeErrorPage.html的文件用來(lái)顯示友好的錯(cuò)誤信息。如下所示:
<!
DOCTYPE html
>
<
html
xmlns
="http://www.w3.org/1999/xhtml"
>
<
head
>
<
title
>
Range Error
</
title
>
</
head
>
<
body
>
<
h2
>
Sorry
</
h2
>
<
span
>
One of the arguments was out of the expected range.
</
span
>
</
body
>
</
html
>
在HomeController中添加一個(gè)值越限時(shí)拋出異常的action,如下所示:
public
class
HomeController : Controller {
[RangeException]
public
string
RangeTest(
int
id) {
if
(id >
100
) {
return
String.Format(
"
The id value is: {0}
"
, id);
}
else
{
throw
new
ArgumentOutOfRangeException(
"
id
"
, id,
""
);
}
}
}
當(dāng)對(duì)RangeTest應(yīng)用自定義的的Exception Filter時(shí),運(yùn)行程序URL請(qǐng)求為?/Home/RangeTest/50,程序拋出異常后將重定向到RangeErrorPage.html頁(yè)面:
由于靜態(tài)的html文件是和后臺(tái)脫離的,所以實(shí)際項(xiàng)目中更多的是用一個(gè)View來(lái)呈現(xiàn)友好的錯(cuò)誤信息,以便很好的對(duì)它進(jìn)行一些動(dòng)態(tài)的控制。如下面把示例改動(dòng)一下,RangeExceptionAttribute 類修改如下:
public
class
RangeExceptionAttribute : FilterAttribute, IExceptionFilter {
public
void
OnException(ExceptionContext filterContext) {
if
(!filterContext.ExceptionHandled && filterContext.Exception
is
ArgumentOutOfRangeException) {
int
val = (
int
)(((ArgumentOutOfRangeException)filterContext.Exception).ActualValue); filterContext.Result
=
new
ViewResult { ViewName
=
"
RangeError
"
, ViewData
=
new
ViewDataDictionary<
int
>
(val) };
filterContext.ExceptionHandled
=
true
;
}
}
}
我們創(chuàng)建一個(gè)ViewResult對(duì)象,指定了發(fā)生異常時(shí)要重定向的View名稱和傳遞的model對(duì)象。然后我們?cè)赩iews/Shared文件夾下添加一個(gè)RangeError.cshtml文件,代碼如下:
@model int
<!
DOCTYPE html
>
<
html
>
<
head
>
<
meta
name
="viewport"
content
="width=device-width"
/>
<
title
>
Range Error
</
title
>
</
head
>
<
body
>
<
h2
>
Sorry
</
h2
>
<
span
>
The value @Model was out of the expected range.
</
span
>
<
div
>
@Html.ActionLink("Change value and try again", "Index")
</
div
>
</
body
>
</
html
>
運(yùn)行結(jié)果如下:
禁用異常跟蹤
很多時(shí)候異常是不可預(yù)料的,在每個(gè)Action方法或Controller上應(yīng)用Exception Filter是不現(xiàn)實(shí)的。而且如果異常出現(xiàn)在View中也無(wú)法應(yīng)用Filter。如RangeError.cshtml這個(gè)View加入下面代碼:
@model int
@{
var count = 0;
var number = Model / count;
}
...
運(yùn)行程序后,將會(huì)顯示如下信息:
顯然程序發(fā)布后不應(yīng)該顯示這些信息給用戶看。我們可以通過(guò)配置Web.config讓應(yīng)用程序不管在何時(shí)何地引發(fā)了異常都可以顯示統(tǒng)一的友好錯(cuò)誤信息。在Web.config文件中的<system.web>節(jié)點(diǎn)下添加如下子節(jié)點(diǎn):
<
system.web
>
...
<
customErrors
mode
="On"
defaultRedirect
="/Content/RangeErrorPage.html"
/>
</
system.web
>
這個(gè)配置只對(duì)遠(yuǎn)程訪問有效,本地運(yùn)行站點(diǎn)依然會(huì)顯示跟蹤信息。
使用內(nèi)置的 Exceptin Filter
通過(guò)上面的演示,我們理解了Exceptin Filter在MVC背后是如何運(yùn)行的。但我們并不會(huì)經(jīng)常去創(chuàng)建自己的Exceptin Filter,因?yàn)槲④浽贛VC框架中內(nèi)置的 HandleErrorAttribute(實(shí)現(xiàn)了IExceptionFilter接口) 已經(jīng)足夠我們平時(shí)使用。它包含ExceptionType、View和Master三個(gè)屬性。當(dāng)ExceptionType屬性指定類型的異常被引發(fā)時(shí),這個(gè)Filter將用View屬性指定的View(使用默認(rèn)的Layout或Mast屬性指定的Layout)來(lái)呈現(xiàn)一個(gè)頁(yè)面。如下面代碼所示:
...
[HandleError(ExceptionType
=
typeof
(ArgumentOutOfRangeException), View =
"
RangeError
"
)]
public
string
RangeTest(
int
id) {
if
(id >
100
) {
return
String.Format(
"
The id value is: {0}
"
, id);
}
else
{
throw
new
ArgumentOutOfRangeException(
"
id
"
, id,
""
);
}
}
...
使用內(nèi)置的HandleErrorAttribute,將異常信息呈現(xiàn)到View時(shí),這個(gè)特性同時(shí)會(huì)傳遞一個(gè)HandleErrorInfo對(duì)象作為View的model。HandleErrorInfo類包含ActionName、ControllerName和Exception屬性,如下面的 RangeError.cshtml 使用這個(gè)model來(lái)呈現(xiàn)信息:
@model HandleErrorInfo
@{
ViewBag.Title = "Sorry, there was a problem!";
}
<!
DOCTYPE html
>
<
html
>
<
head
>
<
meta
name
="viewport"
content
="width=device-width"
/>
<
title
>
Range Error
</
title
>
</
head
>
<
body
>
<
h2
>
Sorry
</
h2
>
<
span
>
The value @(((ArgumentOutOfRangeException)Model.Exception).ActualValue)
was out of the expected range.
</
span
>
<
div
>
@Html.ActionLink("Change value and try again", "Index")
</
div
>
<
div
style
="display: none"
>
@Model.Exception.StackTrace
</
div
>
</
body
>
</
html
>
Action Filter
顧名思義,Action Filter是對(duì)action方法的執(zhí)行進(jìn)行“篩選”的,包括執(zhí)行前和執(zhí)行后。它需要實(shí)現(xiàn)?IActionFilter 接口,該接口定義如下:
namespace
System.Web.Mvc {
public
interface
IActionFilter {
void
OnActionExecuting(ActionExecutingContext filterContext);
void
OnActionExecuted(ActionExecutedContext filterContext);
}
}
其中,OnActionExecuting方法在action方法執(zhí)行之前被調(diào)用,OnActionExecuted方法在action方法執(zhí)行之后被調(diào)用。我們來(lái)看一個(gè)簡(jiǎn)單的例子。
在Infrastructure文件夾下添加一個(gè)ProfileActionAttribute類,代碼如下:
using
System.Diagnostics;
using
System.Web.Mvc;
namespace
MvcApplication1.Infrastructure {
public
class
ProfileActionAttribute : FilterAttribute, IActionFilter {
private
Stopwatch timer;
public
void
OnActionExecuting(ActionExecutingContext filterContext) {
timer
=
Stopwatch.StartNew();
}
public
void
OnActionExecuted(ActionExecutedContext filterContext) {
timer.Stop();
if
(filterContext.Exception ==
null
) {
filterContext.HttpContext.Response.Write(
string
.Format(
"
<div>Action method elapsed time: {0}</div>
"
, timer.Elapsed.TotalSeconds));
}
}
}
}
在HomeController中添加一個(gè)Action并應(yīng)用該Filter,如下:
...
[ProfileAction]
public
string
FilterTest() {
return
"
This is the ActionFilterTest action
"
;
}
...
運(yùn)行程序,URL定位到/Home/FilterTest,結(jié)果如下:
我們看到,ProfileAction的?OnActionExecuted 方法是在?FilterTest 方法返回結(jié)果之前執(zhí)行的。確切的說(shuō),OnActionExecuted 方法是在action方法執(zhí)行結(jié)束之后和處理action返回結(jié)果之前執(zhí)行的。
OnActionExecuting方法和OnActionExecuted方法分別接受ActionExecutingContext和ActionExecutedContext對(duì)象參數(shù),這兩個(gè)參數(shù)包含了ActionDescriptor、Canceled、Exception等常用屬性。
Result Filter
Result Filter用來(lái)處理action方法返回的結(jié)果。用法和Action Filter類似,它需要實(shí)現(xiàn)?IResultFilter?接口,定義如下:
namespace
System.Web.Mvc {
public
interface
IResultFilter {
void
OnResultExecuting(ResultExecutingContext filterContext);
void
OnResultExecuted(ResultExecutedContext filterContext);
}
}
IResultFilter?接口和之前的?IActionFilter 接口類似,要注意的是Result Filter是在Action Filter之后執(zhí)行的。兩者用法是一樣的,不再多講,直接給出示例代碼。
在Infrastructure文件夾下再添加一個(gè)?ProfileResultAttribute.cs 類文件,代碼如下:
public
class
ProfileResultAttribute : FilterAttribute, IResultFilter {
private
Stopwatch timer;
public
void
OnResultExecuting(ResultExecutingContext filterContext) {
timer
=
Stopwatch.StartNew();
}
public
void
OnResultExecuted(ResultExecutedContext filterContext) {
timer.Stop();
filterContext.HttpContext.Response.Write(
string
.Format(
"
<div>Result elapsed time: {0}</div>
"
, timer.Elapsed.TotalSeconds));
}
}
應(yīng)用該Filter:
...
[ProfileAction]
[ProfileResult]
public
string
FilterTest() {
return
"
This is the ActionFilterTest action
"
;
}
...
內(nèi)置的 Action 和 Result Filter
MVC框架內(nèi)置了一個(gè)?ActionFilterAttribute 類用來(lái)創(chuàng)建action 和 result 篩選器,即可以控制action方法的執(zhí)行也可以控制處理action方法返回結(jié)果。它是一個(gè)抽象類,定義如下:
public
abstract
class
ActionFilterAttribute : FilterAttribute, IActionFilter, IResultFilter{
public
virtual
void
OnActionExecuting(ActionExecutingContext filterContext) {
}
public
virtual
void
OnActionExecuted(ActionExecutedContext filterContext) {
}
public
virtual
void
OnResultExecuting(ResultExecutingContext filterContext) {
}
public
virtual
void
OnResultExecuted(ResultExecutedContext filterContext) {
}
}
}
使用這個(gè)抽象類方便之處是你只需要實(shí)現(xiàn)需要加以處理的方法。其他和使用?IActionFilter 和?IResultFilter?接口沒什么不同。下面是簡(jiǎn)單做個(gè)示例。
在Infrastructure文件夾下添加一個(gè)?ProfileAllAttribute.cs 類文件,代碼如下:
public
class
ProfileAllAttribute : ActionFilterAttribute {
private
Stopwatch timer;
public
override
void
OnActionExecuting(ActionExecutingContext filterContext) {
timer
=
Stopwatch.StartNew();
}
public
override
void
OnResultExecuted(ResultExecutedContext filterContext) {
timer.Stop();
filterContext.HttpContext.Response.Write(
string
.Format(
"
<div>Total elapsed time: {0}</div>
"
, timer.Elapsed.TotalSeconds));
}
}
在HomeController中的FilterTest方法上應(yīng)用該Filter:
...
[ProfileAction]
[ProfileResult]
[ProfileAll]
public
string
FilterTest() {
return
"
This is the FilterTest action
"
;
}
...
運(yùn)行程序,URL定位到/Home/FilterTest,可以看到一個(gè)Action從執(zhí)行之前到結(jié)果處理完畢總共花的時(shí)間:
我們也可以Controller中直接重寫?ActionFilterAttribute 抽象類中定義的四個(gè)方法,效果和使用Filter是一樣的,例如:
public
class
HomeController : Controller {
private
Stopwatch timer;
...
public
string
FilterTest() {
return
"
This is the FilterTest action
"
;
}
protected
override
void
OnActionExecuting(ActionExecutingContext filterContext) {
timer
=
Stopwatch.StartNew();
}
protected
override
void
OnResultExecuted(ResultExecutedContext filterContext) {
timer.Stop();
filterContext.HttpContext.Response.Write(
string
.Format(
"
<div>Total elapsed time: {0}</div>
"
,
timer.Elapsed.TotalSeconds));
}
}
注冊(cè)為全局 Filter
全局Filter對(duì)整個(gè)應(yīng)用程序的所有controller下的所有action方法有效。在App_Start/FilterConfig.cs文件中的RegisterGlobalFilters方法,可以把一個(gè)Filter類注冊(cè)為全局,如:
using
System.Web;
using
System.Web.Mvc;
using
MvcApplication1.Infrastructure;
namespace
MvcApplication1 {
public
class
FilterConfig {
public
static
void
RegisterGlobalFilters(GlobalFilterCollection filters) {
filters.Add(
new
HandleErrorAttribute());
filters.Add(
new
ProfileAllAttribute());
}
}
}
我們?cè)黾恿薴ilters.Add(new ProfileAllAttribute())這行代碼,其中的filters參數(shù)是一個(gè)GlobalFilterCollection類型的集合。為了驗(yàn)證?ProfileAllAttribute 應(yīng)用到了所有action,我們另外新建一個(gè)controller并添加一個(gè)簡(jiǎn)單的action,如下:
public
class
CustomerController : Controller {
public
string
Index() {
return
"
This is the Customer controller
"
;
}
}
運(yùn)行程序,將URL定位到?/Customer ,結(jié)果如下:
其它常用 Filter
MVC框架內(nèi)置了很多Filter,常見的有RequireHttps、OutputCache、AsyncTimeout等等。下面例舉幾個(gè)常用的。
- RequireHttps,強(qiáng)制使用HTTPS協(xié)議訪問。它將瀏覽器的請(qǐng)求重定向到相同的controller和action,并加上?https:// 前綴。
- OutputCache,將action方法的輸出內(nèi)容進(jìn)行緩存。
- AsyncTimeout/NoAsyncTimeout,用于異步Controller的超時(shí)設(shè)置。(異步Controller的內(nèi)容請(qǐng)?jiān)L問 xxxxxxxxxxxxxxxxxxxxxxxxxxx)
- ChildActionOnlyAttribute,使用action方法僅能被Html.Action和Html.RenderAction方法訪問。
這里我們選擇?OutputCache 這個(gè)Filter來(lái)做個(gè)示例。新建一個(gè)?SelectiveCache controller,代碼如下:
public
class
SelectiveCacheController : Controller {
public
ActionResult Index() {
Response.Write(
"
Action method is running:
"
+
DateTime.Now);
return
View();
}
[OutputCache(Duration
=
30
)]
public
ActionResult ChildAction() {
Response.Write(
"
Child action method is running:
"
+
DateTime.Now);
return
View();
}
}
這里的?ChildAction 應(yīng)用了?OutputCache filter,這個(gè)action將在view內(nèi)被調(diào)用,它的父action是Index。
現(xiàn)在我們分別創(chuàng)建兩個(gè)View,一個(gè)是ChildAction.cshtml,代碼如下:
@{
Layout = null;
}
<
h4
>
This is the child action view
</
h4
>
另一個(gè)是它的Index.cshtml,代碼如下:
@{
ViewBag.Title = "Index";
}
<
h2
>
This is the main action view
</
h2
>
@Html.Action("ChildAction")
運(yùn)行程序,將URL定位到??/SelectiveCache ,過(guò)幾秒刷新一下,可看到如下結(jié)果:
?
參考: 《Pro ASP.NET MVC 4 4th Edition》
這篇博客是借助一個(gè)自己寫的工程來(lái)理解model binder的過(guò)程.
MVC通過(guò)路由系統(tǒng),根據(jù)url找到對(duì)應(yīng)的Action,然后再執(zhí)行action,在執(zhí)行action的時(shí)候,根據(jù)action的參數(shù)和數(shù)據(jù)來(lái)源比對(duì),生成各個(gè)參數(shù)的值,這就是model binder.
IActionInvoker
MVC中這個(gè)核心處理邏輯都在ControllerActionInvoker里,用reflector看,能看能到這個(gè)類繼承了IActionInvoker接口
1
public
interface
IActionInvoker
2
{
3
bool
InvokeAction(ControllerContext controllerContext,
string
actionName);
4
}
所以咱們可以根據(jù)代碼模擬寫出自己的CustomActionInvoker
以下是我自己寫的ActionInvoker類
1
public
class
CustomActionInvoker : IActionInvoker
2
{
3
4
public
bool
InvokeAction(ControllerContext controllerContext,
string
actionName)
5
{
6
bool
flag =
false
;
7
try
8
{
9
//
get controller type
10
Type controllerType =
controllerContext.Controller.GetType();
11
//
get controller descriptor
12
ControllerDescriptor controllerDescriptor =
new
ReflectedControllerDescriptor(controllerType);
13
//
get action descriptor
14
ActionDescriptor actionDescriptor =
controllerDescriptor.FindAction(controllerContext, actionName);
15
Dictionary<
string
,
object
> parameters =
new
Dictionary<
string
,
object
>
(StringComparer.OrdinalIgnoreCase);
16
//
get parameter-value entity
17
foreach
(ParameterDescriptor parameterDescriptor
in
actionDescriptor.GetParameters())
18
{
19
Type parameterType =
parameterDescriptor.ParameterType;
20
//
get model binder
21
IModelBinder modelBinder =
new
CustomModelBinder();
22
IValueProvider valueProvider =
controllerContext.Controller.ValueProvider;
23
string
str = parameterDescriptor.BindingInfo.Prefix ??
parameterDescriptor.ParameterName;
24
ModelBindingContext bindingContext =
new
ModelBindingContext();
25
bindingContext.FallbackToEmptyPrefix = parameterDescriptor.BindingInfo.Prefix ==
null
;
26
bindingContext.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(
null
, parameterType);
27
bindingContext.ModelName =
str;
28
bindingContext.ModelState =
controllerContext.Controller.ViewData.ModelState;
29
bindingContext.ValueProvider =
valueProvider;
30
parameters.Add(parameterDescriptor.ParameterName,
modelBinder.BindModel(controllerContext, bindingContext));
31
}
32
ActionResult result =
(ActionResult)actionDescriptor.Execute(controllerContext, parameters);
33
result.ExecuteResult(controllerContext);
34
flag =
true
;
35
}
36
catch
(Exception ex)
37
{
38
//
log
39
}
40
return
flag;
41
}
42
}
以下詳細(xì)解釋下執(zhí)行過(guò)程
*Descriptor
執(zhí)行過(guò)程中涉及到三個(gè)Descriptor,ControllerDescriptor,ActionDescriptor,ParameterDescriptor
ControllerDescriptor主要作用是根據(jù)action name獲取到ActionDescriptor,代碼中使用的是MVC自帶的ReflectedControllerDescriptor,從名字就可以看出來(lái),主要是靠反射獲取到action.
ActionDescriptor,主要作用是獲取parameterDescriptor,然后execute action.
parameterDescriptor,描述的是action的參數(shù)信息,包括name、type等
ModelBinder
最核心的方法. 將傳遞的數(shù)據(jù)和參數(shù)一一對(duì)應(yīng),筆者是自己寫的CustomModelBinder,MVC默認(rèn)用的是DefaultModelBinder 都實(shí)現(xiàn)了接口IModelBinder
1
public
interface
IModelBinder
2
{
3
object
BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext);
4
}
其中CustomModelBinder的代碼如下
1
public
class
CustomModelBinder : IModelBinder
2
{
3
4
public
object
BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
5
{
6
return
this
.GetModel(controllerContext, bindingContext.ModelType, bindingContext.ValueProvider, bindingContext.ModelName);
7
}
8
9
public
object
GetModel(ControllerContext controllerContext, Type modelType, IValueProvider valueProvider,
string
key)
10
{
11
if
(!
valueProvider.ContainsPrefix(key))
12
{
13
return
null
;
14
}
15
return
valueProvider.GetValue(key).ConvertTo(modelType);
16
}
17
}
注:我只是實(shí)現(xiàn)了簡(jiǎn)單的基本類型
中間有最核心的方法
valueProvider.GetValue(key).ConvertTo(modelType)
ValueProvider
MVC默認(rèn)提供了幾種ValueProvider,每種都有對(duì)應(yīng)的ValueProviderFactory,每種ValueProvider都對(duì)應(yīng)著自己的數(shù)據(jù)源
1
ValueProviderFactoryCollection factorys =
new
ValueProviderFactoryCollection();
2
factorys.Add(
new
ChildActionValueProviderFactory());
3
factorys.Add(
new
FormValueProviderFactory());
4
factorys.Add(
new
JsonValueProviderFactory());
5
factorys.Add(
new
RouteDataValueProviderFactory());
6
factorys.Add(
new
QueryStringValueProviderFactory());
7
factorys.Add(
new
HttpFileCollectionValueProviderFactory());
注冊(cè)ActionInvoker
上述過(guò)程講完之后,還缺一個(gè)怎么應(yīng)用上自己寫的ActionInvoker,在Controller里提供了虛方法CreateActionInvoker
1
protected
override
IActionInvoker CreateActionInvoker()
2
{
3
return
new
CustomActionInvoker();
4
}
到此,整個(gè)過(guò)程已講完。
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061
微信掃一掃加我為好友
QQ號(hào)聯(lián)系: 360901061
您的支持是博主寫作最大的動(dòng)力,如果您喜歡我的文章,感覺我的文章對(duì)您有幫助,請(qǐng)用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長(zhǎng)非常感激您!手機(jī)微信長(zhǎng)按不能支付解決辦法:請(qǐng)將微信支付二維碼保存到相冊(cè),切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對(duì)您有幫助就好】元

