Asp.Net WebAPI中Filter的使用以及執(zhí)行順序

轉(zhuǎn)發(fā)自:http://www.cnblogs.com/UliiAn/p/5402146.html

在WEB Api中饲窿,引入了面向切面編程(AOP)的思想,在某些特定的位置可以插入特定的Filter進(jìn)行過(guò)程攔截處理络断。引入了這一機(jī)制可以更好地踐行DRY(Don’t Repeat Yourself)思想劣像,通過(guò)Filter能統(tǒng)一地對(duì)一些通用邏輯進(jìn)行處理尖啡,如:權(quán)限校驗(yàn)橄仆、參數(shù)加解密、參數(shù)校驗(yàn)等方面我們都可以利用這一特性進(jìn)行統(tǒng)一處理可婶,今天我們來(lái)介紹Filter的開(kāi)發(fā)沿癞、使用以及討論他們的執(zhí)行順序。

Filter的開(kāi)發(fā)和調(diào)用

在默認(rèn)的WebApi中矛渴,框架提供了三種Filter椎扬,他們的功能和運(yùn)行條件如下表所示:

Filter 類(lèi)型 實(shí)現(xiàn)的接口 描述
Authorization IAuthorizationFilter 最先運(yùn)行的Filter惫搏,被用作請(qǐng)求權(quán)限校驗(yàn)
Action IActionFilter 在Action運(yùn)行的前、后運(yùn)行
Exception IExceptionFilter 當(dāng)異常發(fā)生的時(shí)候運(yùn)行

首先蚕涤,我們實(shí)現(xiàn)一個(gè)AuthorizatoinFilter可以用以簡(jiǎn)單的權(quán)限控制:

public class AuthFilterAttribute : AuthorizationFilterAttribute
    {
        public override void OnAuthorization(HttpActionContext actionContext)
        {
            //如果用戶方位的Action帶有AllowAnonymousAttribute筐赔,則不進(jìn)行授權(quán)驗(yàn)證
            if (actionContext.ActionDescriptor.GetCustomAttributes<AllowAnonymousAttribute>().Any())
            {
                return;
            }
            var verifyResult = actionContext.Request.Headers.Authorization!=null &&  //要求請(qǐng)求中需要帶有Authorization頭
                               actionContext.Request.Headers.Authorization.Parameter == "123456"; //并且Authorization參數(shù)為123456則驗(yàn)證通過(guò)

            if (!verifyResult)
            {
                //如果驗(yàn)證不通過(guò),則返回401錯(cuò)誤揖铜,并且Body中寫(xiě)入錯(cuò)誤原因
                actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.Unauthorized,new HttpError("Token 不正確"));
            }
        }
    }

一個(gè)簡(jiǎn)單的用于用戶驗(yàn)證的Filter就開(kāi)發(fā)完了茴丰,這個(gè)Filter要求用戶的請(qǐng)求中帶有Authorization頭并且參數(shù)為123456,如果通過(guò)則放行天吓,不通過(guò)則返回401錯(cuò)誤贿肩,并在Content中提示Token不正確。

下面龄寞,我們需要注冊(cè)這個(gè)Filter汰规,注冊(cè)Filter有三種方法:

第一種:在我們希望進(jìn)行權(quán)限控制的Action上打上AuthFilterAttribute這個(gè)Attribute:

public class PersonController : ApiController
{
    [AuthFilter]
    public CreateResult Post(CreateUser user)
    {
         return new CreateResult() {Id = "123"};
    }
    }

這種方式適合單個(gè)Action的權(quán)限控制。

第二種物邑,找到相應(yīng)的Controller溜哮,并打上這個(gè)Attribute:

[AuthFilter]
public class PersonController : ApiController
{
    public CreateResult Post(CreateUser user)
    {
        return new CreateResult() {Id = "123"};
    }
}

這種方式適合于控制整個(gè)Controller,打上這個(gè)Attribute以后色解,整個(gè)Controller里所有Action都獲得了權(quán)限控制茂嗓。

第三種,找到App_Start\WebApiConfig.cs科阎,在Register方法下加入Filter實(shí)例:

public static void Register(HttpConfiguration config)
{
     config.MapHttpAttributeRoutes();
    //注冊(cè)全局Filter
     config.Filters.Add(new AuthFilterAttribute());

     config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );
}

用這種方式適合于控制所有的API述吸,任意Controller和任意Action都接受了這個(gè)權(quán)限控制。

在大多數(shù)場(chǎng)景中萧恕,每個(gè)API的權(quán)限驗(yàn)證邏輯都是一樣的刚梭,在這樣的前提下使用全局注冊(cè)Filter的方法最為簡(jiǎn)單便捷,可這樣存在一個(gè)顯而易見(jiàn)的問(wèn)題:如果某幾個(gè)API是不需要控制的(例如登錄)怎么辦票唆?我們可以在這樣的API上做這樣的處理:

[AllowAnonymous]
public CreateResult PostLogin(LoginEntity entity)
{
      //TODO:添加驗(yàn)證邏輯
      return new CreateResult() {Id = "123456"};
}

我為這個(gè)Action打上了AllowAnonymousAttribute,驗(yàn)證邏輯就放過(guò)了這個(gè)API而不進(jìn)行權(quán)限校驗(yàn)屹徘。

在實(shí)際的開(kāi)發(fā)中走趋,我們可以設(shè)計(jì)一套類(lèi)似Session的機(jī)制,通過(guò)用戶登錄來(lái)獲取Token噪伊,在之后的交互HTTP請(qǐng)求中加上Authorization頭并帶上這個(gè)Token簿煌,并在自定義的AuthFilterAttribute中對(duì)Token進(jìn)行驗(yàn)證,一套標(biāo)準(zhǔn)的Token驗(yàn)證流程就可以實(shí)現(xiàn)了鉴吹。

接下來(lái)我們介紹ActionFilter:

ActionFilterAttrubute提供了兩個(gè)方法進(jìn)行攔截:

  • OnActionExecuting和OnActionExecuted姨伟,他們都提供了同步和異步的方法。
  • OnActionExecuting方法在Action執(zhí)行之前執(zhí)行豆励,OnActionExecuted方法在Action執(zhí)行完成之后執(zhí)行夺荒。

我們來(lái)看一個(gè)應(yīng)用場(chǎng)景:使用過(guò)MVC的同學(xué)一定不陌生MVC的模型綁定和模型校驗(yàn)瞒渠,使用起來(lái)非常方便,定義好Entity之后技扼,在需要進(jìn)行校驗(yàn)的地方可以打上相應(yīng)的Attribute伍玖,在Action開(kāi)始時(shí)檢查ModelState的IsValid屬性,如果校驗(yàn)不通過(guò)直接返回View剿吻,前端可以解析并顯示未通過(guò)校驗(yàn)的原因窍箍。而Web API中也繼承了這一方便的特性,使用起來(lái)更加方便:

public class CustomActionFilterAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(HttpActionContext actionContext)
    {
        if (!actionContext.ModelState.IsValid)
        {
            actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.BadRequest,  actionContext.ModelState);
        }
    }
}

這個(gè)Filter就提供了模型校驗(yàn)的功能丽旅,如果未通過(guò)模型校驗(yàn)則返回400錯(cuò)誤椰棘,并把相關(guān)的錯(cuò)誤信息交給調(diào)用者。他的使用方法和AuthFilterAttribute一樣榄笙,可以針對(duì)Action邪狞、Controller、全局使用办斑。

我們可以用下面一個(gè)例子來(lái)驗(yàn)證:

代碼如下:

public class LoginEntity
{
    [Required(ErrorMessage = "缺少用戶名")]
    public string UserName { get; set; }

    [Required(ErrorMessage = "缺少密碼")]
    public string Password { get; set; }
}

[AllowAnonymous]
[CustomActionFilter]
public CreateResult PostLogin(LoginEntity entity)
{
     //TODO:添加驗(yàn)證邏輯
     return new CreateResult() {Id = "123456"};
}

image

當(dāng)然外恕,你也可以根據(jù)自己的需要解析ModelState然后用自己的格式將錯(cuò)誤信息通過(guò)Request.CreateResponse()返回給用戶。

OnActionExecuted方法我在實(shí)際工作中使用得較少乡翅,目前僅在一次部分響應(yīng)數(shù)據(jù)加密的場(chǎng)景下進(jìn)行過(guò)使用鳞疲,使用方法一樣,讀取已有的響應(yīng)蠕蚜,并加密后再給出加密后的響應(yīng)賦值給actionContext.Response即可尚洽。

我給大家一個(gè)Demo:

public override async Task OnActionExecutedAsync(HttpActionExecutedContext actionExecutedContext, CancellationToken cancellationToken)
{
        var key = 10;

        var responseBody = await actionExecutedContext.Response.Content.ReadAsByteArrayAsync(); //以Byte數(shù)組方式讀取Content中的數(shù)據(jù)

        for (int i = 0; i < responseBody.Length; i++)
        {
            responseBody[i] = (byte)(responseBody[i] ^ key); //對(duì)每一個(gè)Byte做異或運(yùn)算
        }

        actionExecutedContext.Response.Content = new ByteArrayContent(responseBody); //將結(jié)果賦值給Response的Content

        actionExecutedContext.Response.Content.Headers.ContentType = MediaTypeHeaderValue.Parse("Encrypt/Bytes"); //并修改Content-Type
}

通過(guò)這個(gè)方法我們將響應(yīng)的Content每個(gè)Byte都做了一個(gè)異或運(yùn)算,對(duì)響應(yīng)內(nèi)容進(jìn)行了一次簡(jiǎn)單的加密靶累,大家可以根據(jù)自己的需要進(jìn)行更可靠的加密腺毫,如AES、DES或者RSA…通過(guò)這個(gè)方法可以靈活地對(duì)某個(gè)Action的處理后的結(jié)果進(jìn)行處理挣柬,通過(guò)Filter進(jìn)行響應(yīng)內(nèi)容加密有很強(qiáng)的靈活性和通用性潮酒,他能獲取當(dāng)前Action的很多信息,然后根據(jù)這些信息選擇加密的方式邪蛔、獲取加密所需的參數(shù)等等急黎。如果加密所使用參數(shù)對(duì)當(dāng)前執(zhí)行的Action沒(méi)有依賴(lài),也可以采取HttpMessageHandler來(lái)進(jìn)行處理侧到,在之后的教程中我會(huì)進(jìn)行介紹勃教。

最后一個(gè)Filter:ExceptionFilter

顧名思義,這個(gè)Filter是用來(lái)進(jìn)行異常處理的匠抗,當(dāng)業(yè)務(wù)發(fā)生未處理的異常故源,我們是不希望用戶接收到黃頁(yè)或者其他用戶無(wú)法解析的信息的,我們可以使用ExceptionFilter來(lái)進(jìn)行統(tǒng)一處理:

public class ExceptionFilter : ExceptionFilterAttribute
{
    public override void OnException(HttpActionExecutedContext actionExecutedContext)
    {
        //如果截獲異常為我們自定義汞贸,可以處理的異常則通過(guò)我們自己的規(guī)則處理
        if (actionExecutedContext.Exception is DemoException)
        {
            //TODO:記錄日志
            actionExecutedContext.Response = actionExecutedContext.Request.CreateResponse(
                    HttpStatusCode.BadRequest, new {Message = actionExecutedContext.Exception.Message});
        }
        else
        {
            //如果截獲異常是我沒(méi)無(wú)法預(yù)料的異常绳军,則將通用的返回信息返回給用戶印机,避免泄露過(guò)多信息,也便于用戶處理

            //TODO:記錄日志
            actionExecutedContext.Response =
                    actionExecutedContext.Request.CreateResponse(HttpStatusCode.InternalServerError,
                        new {Message = "服務(wù)器被外星人拐跑了删铃!"});
        }
    }
}

我們定義了一個(gè)ExceptoinFilter用于處理未捕獲的異常耳贬,我們將異常分為兩類(lèi):一類(lèi)是我們可以預(yù)料的異常:如業(yè)務(wù)參數(shù)錯(cuò)誤,越權(quán)等業(yè)務(wù)異常猎唁;還有一類(lèi)是我們無(wú)法預(yù)料的異常:如數(shù)據(jù)庫(kù)連接斷開(kāi)咒劲、內(nèi)存溢出等異常。我們通過(guò)HTTP Code告知調(diào)用者以及用相對(duì)固定诫隅、友好的數(shù)據(jù)結(jié)構(gòu)將異常信息告訴調(diào)用者腐魂,以便于調(diào)用者記錄并處理這樣的異常。

[CustomerExceptionFilter]
public class TestController : ApiController
{
    public int Get(int a, int b)
    {
        if (a < b)
        {
            throw new DemoException("A必須要比B大逐纬!");
        }
        if (a == b)
        {
            throw new NotImplementedException();
        }
        return a*b;
    }
}

我們定義了一個(gè)Action:在不同的情況下會(huì)拋出不同的異常蛔屹,其中一個(gè)異常是我們能夠預(yù)料并認(rèn)為是調(diào)用者傳參出錯(cuò)的,一個(gè)是不能夠處理的豁生,我們看一下結(jié)果:

image
image

在這樣的RestApi中兔毒,我們可以預(yù)先定義好異常的表現(xiàn)形式,讓調(diào)用者可以方便地判斷什么情況下是出現(xiàn)異常了甸箱,然后通過(guò)較為統(tǒng)一的異常信息返回方式讓調(diào)用者方便地解析異常信息育叁,形成統(tǒng)一方便的異常消息處理機(jī)制。

==但是芍殖,ExceptionFilter只能在成功完成了Controller的初始化以后才能起到捕獲豪嗽、處理異常的作用,而在Controller初始化完成之前(例如在Controller的構(gòu)造函數(shù)中出現(xiàn)了異常)則ExceptionFilter無(wú)能為力豌骏。對(duì)此WebApi引入了ExceptionLogger和ExceptionHandler處理機(jī)制龟梦,我們將在之后的文章中進(jìn)行講解。==

Filter的執(zhí)行順序

在使用MVC的時(shí)候窃躲,ActionFilter提供了一個(gè)Order屬性计贰,用戶可以根據(jù)這個(gè)屬性控制Filter的調(diào)用順序,而Web API卻不再支持該屬性蒂窒。Web API的Filter有自己的一套調(diào)用順序規(guī)則:

所有Filter根據(jù)注冊(cè)位置的不同擁有三種作用域:Global蹦玫、Controller、Action:

  • 通過(guò)HttpConfiguration類(lèi)實(shí)例下Filters.Add()方法注冊(cè)的Filter(一般在App_Start\WebApiConfig.cs文件中的Register方法中設(shè)置)就屬于Global作用域刘绣;

  • 通過(guò)Controller上打的Attribute進(jìn)行注冊(cè)的Filter就屬于Controller作用域;

  • 通過(guò)Action上打的Attribute進(jìn)行注冊(cè)的Filter就屬于Action作用域挣输;

他們遵循了以下規(guī)則:

  1. 在同一作用域下纬凤,AuthorizationFilter最先執(zhí)行,之后執(zhí)行ActionFilter

  2. 對(duì)于AuthorizationFilter和ActionFilter.OnActionExcuting來(lái)說(shuō)撩嚼,如果一個(gè)請(qǐng)求的生命周期中有多個(gè)Filter的話停士,執(zhí)行順序都是Global->Controller->Action挖帘;

  3. 對(duì)于ActionFilter,OnActionExecuting總是先于OnActionExecuted執(zhí)行恋技;

  4. 對(duì)于ExceptionFilter和ActionFilter.OnActionExcuted而言執(zhí)行順序?yàn)锳ction->Controller->Global拇舀;

  5. 對(duì)于所有Filter來(lái)說(shuō),如果阻止了請(qǐng)求:即對(duì)Response進(jìn)行了賦值蜻底,則后續(xù)的Filter不再執(zhí)行骄崩。

關(guān)于默認(rèn)情況下的Filter相關(guān)知識(shí)我們就講這么一些,如果在文章中有任何不正確的地方或者疑問(wèn)薄辅,歡迎大家為我指出要拂。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市站楚,隨后出現(xiàn)的幾起案子脱惰,更是在濱河造成了極大的恐慌,老刑警劉巖窿春,帶你破解...
    沈念sama閱讀 211,042評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件拉一,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡旧乞,警方通過(guò)查閱死者的電腦和手機(jī)蔚润,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)良蛮,“玉大人抽碌,你說(shuō)我怎么就攤上這事【鐾” “怎么了货徙?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,674評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)皮胡。 經(jīng)常有香客問(wèn)我痴颊,道長(zhǎng),這世上最難降的妖魔是什么屡贺? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,340評(píng)論 1 283
  • 正文 為了忘掉前任蠢棱,我火速辦了婚禮,結(jié)果婚禮上甩栈,老公的妹妹穿的比我還像新娘泻仙。我一直安慰自己,他們只是感情好量没,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,404評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布玉转。 她就那樣靜靜地躺著,像睡著了一般殴蹄。 火紅的嫁衣襯著肌膚如雪究抓。 梳的紋絲不亂的頭發(fā)上猾担,一...
    開(kāi)封第一講書(shū)人閱讀 49,749評(píng)論 1 289
  • 那天,我揣著相機(jī)與錄音刺下,去河邊找鬼绑嘹。 笑死,一個(gè)胖子當(dāng)著我的面吹牛橘茉,可吹牛的內(nèi)容都是我干的工腋。 我是一名探鬼主播,決...
    沈念sama閱讀 38,902評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼捺癞,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼夷蚊!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起髓介,我...
    開(kāi)封第一講書(shū)人閱讀 37,662評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤惕鼓,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后唐础,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體箱歧,經(jīng)...
    沈念sama閱讀 44,110評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年一膨,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了呀邢。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,577評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡豹绪,死狀恐怖价淌,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情瞒津,我是刑警寧澤蝉衣,帶...
    沈念sama閱讀 34,258評(píng)論 4 328
  • 正文 年R本政府宣布,位于F島的核電站巷蚪,受9級(jí)特大地震影響病毡,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜屁柏,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,848評(píng)論 3 312
  • 文/蒙蒙 一啦膜、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧淌喻,春花似錦僧家、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,726評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春乘粒,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背伤塌。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,952評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工灯萍, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人每聪。 一個(gè)月前我還...
    沈念sama閱讀 46,271評(píng)論 2 360
  • 正文 我出身青樓旦棉,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親药薯。 傳聞我的和親對(duì)象是個(gè)殘疾皇子绑洛,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,452評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容

  • spring mvc 工作機(jī)制(原理): DispatcherServlet主要用作職責(zé)調(diào)度工作,本身主要用于控制...
    java大濕兄閱讀 1,891評(píng)論 5 24
  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理童本,服務(wù)發(fā)現(xiàn)真屯,斷路器,智...
    卡卡羅2017閱讀 134,628評(píng)論 18 139
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,735評(píng)論 25 707
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語(yǔ)法穷娱,類(lèi)相關(guān)的語(yǔ)法绑蔫,內(nèi)部類(lèi)的語(yǔ)法,繼承相關(guān)的語(yǔ)法泵额,異常的語(yǔ)法配深,線程的語(yǔ)...
    子非魚(yú)_t_閱讀 31,597評(píng)論 18 399
  • 清feng閱讀 607評(píng)論 0 0