1. 程式人生 > >對InvokeAction簡略分析瞭解驗證失敗為什麼Action還會繼續執行

對InvokeAction簡略分析瞭解驗證失敗為什麼Action還會繼續執行

一、前言

有些同學使用AuthorizationFilter來進行使用者是否登入驗證,如果未登入就跳到登入頁。

很簡單的一個場景,但是有些同學會發現雖然驗證失敗了,但是整個Action還會執行一遍。

於是google啊google啊,然後找到了解決方案,但是不知為啥,下面就對InvokeAction的簡略分析,說說到底是為啥。

二、分析InvokeAction執行順序

1、首先獲取Controller和Action描述類

ControllerDescriptor controllerDescriptor = GetControllerDescriptor(controllerContext);
ActionDescriptor actionDescriptor 
= FindAction(controllerContext, controllerDescriptor, actionName);

2、大家都知道每個Action執行前會先執行一些Filters,所以首先需要獲取所有FilterScope為Action的Filters

FilterInfo filterInfo = GetFilters(controllerContext, actionDescriptor);

3、Filter的執行是有順序的,AuthenticationFilter最先執行,接下來就是執行AuthenticationFilter的程式碼

AuthenticationContext authenticationContext = InvokeAuthenticationFilters(controllerContext, filterInfo.AuthenticationFilters, actionDescriptor);

這裡要注意,AuthenticationContext有個屬性Result,如果驗證失敗一定要設定這個屬性的值,否則看看下面的程式碼就知道了。

if (authenticationContext.Result != null)
{
    // An authentication filter signaled that we should short-circuit the request. Let all
    // authentication filters contribute to an action result (to combine authentication
    
// challenges). Then, run this action result. AuthenticationChallengeContext challengeContext = InvokeAuthenticationFiltersChallenge( controllerContext, filterInfo.AuthenticationFilters, actionDescriptor, authenticationContext.Result); InvokeActionResult(controllerContext, challengeContext.Result ?? authenticationContext.Result); } else { ... }

看到了吧,如果你設定Result的值,此時就會執行你設定的Result,如果不設定就會繼續走下去,會走哪去,一直往後看,你就知道了。

插一下,我們先看看 InvokeActionResult(ControllerContext controllerContext, ActionResult actionResult)是什麼東東。

protected virtual void InvokeActionResult(ControllerContext controllerContext, ActionResult actionResult)
{
    actionResult.ExecuteResult(controllerContext);
}

這個方法看字面意思是執行一個ActionResult,還是看兩個簡單的例子吧。

(1)ContentResult

public override void ExecuteResult(ControllerContext context)
{
    if (context == null)
    {
        throw new ArgumentNullException("context");
    }

    HttpResponseBase response = context.HttpContext.Response;

    if (!String.IsNullOrEmpty(ContentType))
    {
        response.ContentType = ContentType;
    }
    if (ContentEncoding != null)
    {
        response.ContentEncoding = ContentEncoding;
    }
    if (Content != null)
    {
        response.Write(Content);
    }
}

 很簡單吧,就是把你設定的Content輸出到頁面中,用的也是response.Write(content)方法,所以在Mvc中完全可以用ContentResult替換response.Write(content).

 (2)HttpStatusCodeResult

public override void ExecuteResult(ControllerContext context)
{
    if (context == null)
    {
        throw new ArgumentNullException("context");
    }

    context.HttpContext.Response.StatusCode = StatusCode;
    if (StatusDescription != null)
    {
        context.HttpContext.Response.StatusDescription = StatusDescription;
    }
}

HttpStatusCodeResult 是HttpUnauthorizedResult 的基類,HttpUnauthorizedResult是什麼?自己看字面意思也看出來了。

這個Result的ExecuteResult的更簡單,只是設定Response.StatusCode和StatusDescription。

4、回到InvokeAction中來,接著InvokeAuthenticationFilters這個方法的執行往下看 

if (authenticationContext.Result != null)已經解釋了,然後看看else。else中就開始執行AuthorizationFilters了,此處終於知道為什麼AuthenticationFilters執行在前了吧。

只有authenticationContext.Result不設定值的時候,才會執行AuthorizationFilters,所以如果你想AuthenticationFilters驗證失敗之後,就退出Action,一定要設定Result.

其實AuthorizationFilters的執行方式和AuthenticationFilters類似,如果設定了AuthorizationContext的Result屬性,就會執行Result,否則就繼續往下執行。

AuthorizationContext authorizationContext = InvokeAuthorizationFilters(controllerContext, filterInfo.AuthorizationFilters, actionDescriptor);
if (authorizationContext.Result != null)
{
    // An authorization filter signaled that we should short-circuit the request. Let all
    // authentication filters contribute to an action result (to combine authentication
    // challenges). Then, run this action result.
    AuthenticationChallengeContext challengeContext = InvokeAuthenticationFiltersChallenge(
        controllerContext, filterInfo.AuthenticationFilters, actionDescriptor,
        authorizationContext.Result);
    InvokeActionResult(controllerContext, challengeContext.Result ?? authorizationContext.Result);
}
else
{
    // 如果你沒有其他的Filters、沒有設定OnActionXXX等等,你的Action內的程式碼就會在這裡被呼叫執行了。
    ...
}

看到這裡,是不是有些同學會想:“原來如此啊”。

三、總結

由於微軟沒有提供AuthenticationFilter對應的基類,只提供了一個IAuthenticationFilter,所以我們就用AuthorizeAttribute來進行總結。

一些同學自定義AuthorizationFilters的時候,程式碼結尾沒有呼叫基類的 OnAuthorization,也沒有設定FilterContext的Result屬性,只是進行Response.Redirect或者其他認為跳出Action執行的程式碼。這樣其實等於沒有進行驗證,Action中的程式碼還是會執行。

在進行AuthorizationFilters的時候,如果想驗證失敗之後,不執行Action的方法,可以有以下兩種方法:

(1)呼叫基類的 OnAuthorization,我們看看基類 OnAuthorization 都幹了啥?

public virtual void OnAuthorization(AuthorizationContext filterContext)
{
    ...

    if (AuthorizeCore(filterContext.HttpContext))
    {
        ...
    }
    else
    {
        HandleUnauthorizedRequest(filterContext); // 主要看這個方法
    }
}
protected virtual void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
    // Returns HTTP 401 - see comment in HttpUnauthorizedResult.cs.
    filterContext.Result = new HttpUnauthorizedResult();
}

HttpUnauthorizedResult:這個類有沒有看著眼熟,對了,就是前面講Result中的第二個例子的子類。

(2)給filterContext的Result屬性設定一個Result,即使是EmptyResult都可以,如果你想輸出點內容到頁面中,那就使用ContetResult,前面說過它是通過呼叫Response.Write()來實現的。

最後附上ControllerActionInvoker:InvokeAction(L230)方法的詳細內容:

public virtual bool InvokeAction(ControllerContext controllerContext, string actionName)
{
    if (controllerContext == null)
    {
        throw new ArgumentNullException("controllerContext");
    }

    Contract.Assert(controllerContext.RouteData != null);
    if (String.IsNullOrEmpty(actionName) && !controllerContext.RouteData.HasDirectRouteMatch())
    {
        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
        {
            AuthenticationContext authenticationContext = InvokeAuthenticationFilters(controllerContext, filterInfo.AuthenticationFilters, actionDescriptor);

            if (authenticationContext.Result != null)
            {
                // An authentication filter signaled that we should short-circuit the request. Let all
                // authentication filters contribute to an action result (to combine authentication
                // challenges). Then, run this action result.
                AuthenticationChallengeContext challengeContext = InvokeAuthenticationFiltersChallenge(
                    controllerContext, filterInfo.AuthenticationFilters, actionDescriptor,
                    authenticationContext.Result);
                InvokeActionResult(controllerContext, challengeContext.Result ?? authenticationContext.Result);
            }
            else
            {
                AuthorizationContext authorizationContext = InvokeAuthorizationFilters(controllerContext, filterInfo.AuthorizationFilters, actionDescriptor);
                if (authorizationContext.Result != null)
                {
                    // An authorization filter signaled that we should short-circuit the request. Let all
                    // authentication filters contribute to an action result (to combine authentication
                    // challenges). Then, run this action result.
                    AuthenticationChallengeContext challengeContext = InvokeAuthenticationFiltersChallenge(
                        controllerContext, filterInfo.AuthenticationFilters, actionDescriptor,
                        authorizationContext.Result);
                    InvokeActionResult(controllerContext, challengeContext.Result ?? authorizationContext.Result);
                }
                else
                {
                    if (controllerContext.Controller.ValidateRequest)
                    {
                        ValidateRequest(controllerContext);
                    }

                    IDictionary<string, object> parameters = GetParameterValues(controllerContext, actionDescriptor);
                    ActionExecutedContext postActionContext = InvokeActionMethodWithFilters(controllerContext, filterInfo.ActionFilters, actionDescriptor, parameters);

                    // The action succeeded. Let all authentication filters contribute to an action result (to
                    // combine authentication challenges; some authentication filters need to do negotiation
                    // even on a successful result). Then, run this action result.
                    AuthenticationChallengeContext challengeContext = InvokeAuthenticationFiltersChallenge(
                        controllerContext, filterInfo.AuthenticationFilters, actionDescriptor,
                        postActionContext.Result);
                    InvokeActionResultWithFilters(controllerContext, filterInfo.ResultFilters,
                        challengeContext.Result ?? 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;
}
View Code