ASP.Net MVC探索之路 – 不想在多個Action上寫同樣的FitlerAttribute(上)

來源:互聯網
上載者:User
(寫完本文後,我去下載了ASP.NET MVC 3 RC,發現它對Filter的可控性方面進行了某些增強——不僅僅是針對全域Filter的GlobalFilterCollection——所以在此特別說明一下本文目前主要針對的是ASP.NET MVC 2.0 RTM,當然大部分都適用於3.0)

以AuthorizeAttribute這個Filter舉例,一個Controller有若干個Action,包括登入的Action(如Login)。這時我們有兩種方式來實現:
1、重新實現一個IAuthorizationFilter,在裡面判斷如果是Login這個Action,就不進行驗證。然後將這個Filter作為FilterAttribute置於Controller定義上。或者Controller自身實現IAuthorizationFilter。
2、除了Login這個Action之外的所有Action加上個Authorize。
這兩種方式雖然能夠達到目的,但總覺得不夠優雅。

如果我想給所有Action注入一段html到頁面底部,這種注入可能是臨時的,我必須去修改Controller嗎?(ASP.NET MVC 3 可以將Filter加入GlobalFilters集合中)
如果我想動態控制某個Action允許由哪些角色訪問,我通過修改Controller能實現嗎?
如果我想這時候控制的由哪些角色來訪問,需求改變時我想要控制由哪些使用者來訪問呢?我還得去修改Controller嗎?或者增加或修改Filter嗎?

在ASP.NET MVC 2 中,以上需求好像都需要重新編譯Controller。

這裡整理一下我們的需求:能不能增刪改Filter時不去修改Controller?能讓所有Controller和Action定義處都乾乾淨淨的那就最好了。

那就把Filter放在Controller外部來管理,在Action或ActionResult等執行Filter之前保證將需要的Filter準備好就行了。

在解決問題之前,先簡單回顧一下Action執行前後發生的事。

我們知道,在ASP.NET MVC中,每一次請求通常都定位到一個具體的Controller的Action中。
在預設情況下,由Action執行器(ControllerActionInvoker去控制Action的執行(或不執行),實際做事的是InvokeAction方法。

InvokeAction方法首先去尋找Action(由FindAction方法),如果Action被找到了,會通過反射的方式去檢索該Action所屬Controller的擁有的Filter以及Action擁有的Filter,如果Controller本身也實現了某些Filter介面,也會被檢索到(由GetFilters方法)。將找到的所有Filter放入一個FilterInfo變數中(當然放入FilterInfo變數的Filter是經過排序和重複清理的),FilterInfo中儲存的Filter不完全是Action自己的Attribute上定義的

然後先執行找到的所有IAuthorizationFilter(由InvokeAuthorizationFilters方法)。在InvokeAuthorizationFilters方法中,只要有一個IAuthorizationFilter的ActionResult不為null就會立即返回到調用處,不會執行其他的IAuthorizationFilter了。InvokeAuthorizationFilters執行完成後,InvokeAction方法檢測其執行結果,如果ActionResult不為null,則執行該Result,其他的IActionFilter,IActionResult就不管啦,否則InvokeAction方法繼續。

接著擷取要傳給Action的參數集(交給GetParameterValue方法),就執行InvokeActionMethodWithFilters方法,方法名已經足夠說明它是幹什麼的了。

如果一切正常,根據InvokeActionMethodWithFilters方法返回的結果去接著就執行InvokeActionResultWithFilters方法。

在執行InvokeAuthorizationFilters一直到執行InvokeActionResultWithFilters的這一整個過程中如果發生異常,則根據捕獲的異常執行InvokeExceptionFilters進行異常處理。

代碼public virtual bool InvokeAction(ControllerContext controllerContext, string actionName) {
    if (controllerContext == null) {
        throw new ArgumentNullException("controllerContext");
    }
    if (String.IsNullOrEmpty(actionName)) {
        throw new ArgumentException(MvcResources.Common_NullOrEmpty, "actionName");
    }

    ControllerDescriptor controllerDescriptor = GetControllerDescriptor(controllerContext);
    ActionDescriptor actionDescriptor = FindAction(controllerContext, controllerDescriptor, actionName);
    if (actionDescriptor != null) {
        FilterInfo filterInfo = GetFilters(controllerContext, actionDescriptor);

        try {
            AuthorizationContext authContext = InvokeAuthorizationFilters(controllerContext, filterInfo.AuthorizationFilters, actionDescriptor);
            if (authContext.Result != null) {
                // the auth filter signaled that we should let it short-circuit the request
                InvokeActionResult(controllerContext, authContext.Result);
            }
            else {
                if (controllerContext.Controller.ValidateRequest) {
                    ValidateRequest(controllerContext);
                }

                IDictionary<string, object> parameters = GetParameterValues(controllerContext, actionDescriptor);
                ActionExecutedContext postActionContext = InvokeActionMethodWithFilters(controllerContext, filterInfo.ActionFilters, actionDescriptor, parameters);
                InvokeActionResultWithFilters(controllerContext, filterInfo.ResultFilters, postActionContext.Result);
            }
        }
        catch (ThreadAbortException) {
            // This type of exception occurs as a result of Response.Redirect(), but we special-case so that
            // the filters don't see this as an error.
            throw;
        }
        catch (Exception ex) {
            // something blew up, so execute the exception filters
            ExceptionContext exceptionContext = InvokeExceptionFilters(controllerContext, filterInfo.ExceptionFilters, ex);
            if (!exceptionContext.ExceptionHandled) {
                throw;
            }
            InvokeActionResult(controllerContext, exceptionContext.Result);
        }

        return true;
    }

    // notify controller that no method matched
    return false;
}

這裡需要注意一點:InvokeAction、GetFilters、InvokeAuthorizationFilters、GetParameterValue、InvokeActionMethodWithFilters、InvokeActionResultWithFilters、InvokeExceptionFilters等全是虛方法,除非有足夠的原因去繼承IActionInvoker重寫一個Action執行器,否則我覺得重寫某些方法足夠滿足我們的擴充需求了。

甚至ControllerActionInvoker類本身,在ASP.NET MVC基礎架構中也是可以替換的,怎麼替換呢?在繼承Controller類實現我們自己的Controller時重寫CreateActionInvoker方法就可以。

另外還可以在構造Controller對象給它的ActionInvoker屬性賦值,這又怎麼賦值?重寫DefaultControllerFactory建立Controller執行個體的GetControllerInstance方法。 然後在Applicaion_Start中設定新的ControllerFactor:
ControllerBuilder.Current.SetControllerFactory(new YourControllerFactory());

回到主題。 我們將Filter和Action的對應關係或Filter和Controller的對應關係存於一個集合中並緩衝起來。在合適的位置“注入”進去就行了。

從找到Action到執行InvokeAuthorizationFilters之前,必須將IAuthorizationFilter準備好;從找到Action到執行InvokeActionMethodWithFilters內部執行Action之前,必須將IActionFilter準備好;從找到Action到執行InvokeActionResultWithFilters內部執行ActionResult之前,必須將IResultFilter準備好。異常發生InvokeExceptionFilters執行之前,必須將IExceptionFilter準備好。 基於以上幾點,我們好像可以在FindAction方法GetFilters方法、InvokeAuthorizationFilters方法、InvokeActionResultWithFilters方法和GetFilters方法內部,或InvokeExceptionFilters執行前將必要的Filter準備好就可以了。當然,最合適的莫過於GetFilters方法了。

在GetFilters方法中,我們可以根據當前Action的特徵(如方法名,或包括請求方式Get或Post等),在“Filter和Action對應表”進行檢索;或者根據當前Controller的特徵(類型、完整類名都可以),在"Filter和Controller對應表"中進行檢索。將匹配的Filter累加入FilterInfo變數就可以了。

感興趣的可以去看看Oxite ,它實現了本章提到的一部分的需求。
相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.