Web APi之過濾器創建過程原理解析【一】(十)


前言

Web API的簡單流程就是從請求到執行到Action並最終作出響應,但是在這個過程有一把【篩子】,那就是過濾器Filter,在從請求到Action這整個流程中使用Filter來進行相應的處理從而作出響應,這對於認證以及授權等是及其重要的,所以說過濾器應用是Web API框架中非常重要的一種實現方式,我們有必要去探討其原理。

過濾器及其提供機制

Web API框架提供了一個請求、響應的消息處理管道,並且其框架極具擴展性,通過其擴展可以對執行的流程進行適當的干預,其擴展點就體現在以下三個方面:

  • 將自定義的HttpMessageHandler注冊到消息處理管道中。

  • 將自定義的標准化組件注冊到當前的服務容器(ServicesContainer)或者(HttpConfiguration)上。

  • 將自定義的過濾器Filter注冊到控制器或者Action方法上。

Filter創建原理解析

上一節我們講到了HttpActionDescriptor,顧名思義是在控制器方法上返回的是對控制器方法的描述類型,它封裝了控制器方法的一切信息,而過濾器Filter就是在這個類中初始化和創建的,我們首先來看看這個類中我們會用到的方法及其屬性:

 1 public abstract class HttpActionDescriptor
 2 {
 3     // Fields
 4     private HttpActionBinding _actionBinding;
 5     private HttpConfiguration _configuration;
 6     private HttpControllerDescriptor _controllerDescriptor;
 7     private IActionResultConverter _converter;
 8     private readonly Lazy<Collection<FilterInfo>> _filterPipeline;
 9     private readonly ConcurrentDictionary<object, object> _properties;
10     private static readonly ResponseMessageResultConverter _responseMessageResultConverter;
11     private readonly Collection<HttpMethod> _supportedHttpMethods;
12     private static readonly VoidResultConverter _voidResultConverter;
13 
14     // Methods
15     static HttpActionDescriptor();
16     protected HttpActionDescriptor();
17     protected HttpActionDescriptor(HttpControllerDescriptor controllerDescriptor);
18     private static bool AllowMultiple(object filterInstance);
19     public abstract Task<object> ExecuteAsync(HttpControllerContext controllerContext, IDictionary<string, object> arguments, CancellationToken cancellationToken);
20     public virtual Collection<T> GetCustomAttributes<T>() where T: class;
21     public virtual Collection<FilterInfo> GetFilterPipeline();
22     public virtual Collection<IFilter> GetFilters();
23     public abstract Collection<HttpParameterDescriptor> GetParameters();
24     internal static IActionResultConverter GetResultConverter(Type type);
25     private Collection<FilterInfo> InitializeFilterPipeline();
26     private static IEnumerable<FilterInfo> RemoveDuplicates(IEnumerable<FilterInfo> filters);
27 } 

我們首先看看這個此類的構造函數  protected HttpActionDescriptor(); 

protected HttpActionDescriptor()
{
    this._properties = new ConcurrentDictionary<object, object>();
    this._supportedHttpMethods = new Collection<HttpMethod>();
    this._filterPipeline = new Lazy<Collection<FilterInfo>>(new Func<Collection<FilterInfo>>(this.InitializeFilterPipeline));
} 

從上知過濾器管道的創建就是在此類的構造函數中進行。首先我們將構造函數進行擱置,我們先來了解過濾器的一些基本的信息。

過濾器接口(IFilter)

public interface IFilter
{
    // Properties
    bool AllowMultiple { get; }
}

此接口只有一個只讀屬性AllowMultiple,它表示多個同類的過濾器類型是否允許指定應用到同一個目標對象上,如果允許指定,則該值為true,否則為false,默認是false。  

過濾器特性(FilterAttribute)

[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited=true, AllowMultiple=true)]
public abstract class FilterAttribute : Attribute, IFilter
{
    // Fields
    private static readonly ConcurrentDictionary<Type, bool> _attributeUsageCache;

    // Methods
    static FilterAttribute();
    protected FilterAttribute();
    private static bool AllowsMultiple(Type attributeType);

    // Properties
    public virtual bool AllowMultiple { get; }
}

由上知,此過濾器特性類為過濾器的基類,當我們設置 AttributeUsage 中的 AllowMultiple 來指定是否允許多個特性應用到相同的對象上,同時通過其屬性AllowMultiple來返回其值,當我們想要實現自定義的過濾器特性類時僅僅繼承此類是不夠的,因為此特性類只是特性中一個【屬性】而已,而過濾器中的行為是通過IActionFilter來提供的。

過濾器行為接口(IActionFilter)

public interface IActionFilter : IFilter
{
    // Methods
    Task<HttpResponseMessage> ExecuteActionFilterAsync(HttpActionContext actionContext, CancellationToken cancellationToken, Func<Task<HttpResponseMessage>> continuation);
}

過濾器作用域(FilterScope)  

public enum FilterScope
{
    Action = 20,
    Controller = 10,
    Global = 0
}

該類為枚舉類,它有三個作用域選項: Action = 20  在控制器之后指定一個操作。 Controller = 10 在Global之后,Action方法之前指定一個操作。  Global = 0 在控制器之前指定一個操作。 

過濾器信息(FilterInfo)

public sealed class FilterInfo
{
    // Methods
    public FilterInfo(IFilter instance, FilterScope scope);

    // Properties
    public IFilter Instance { get; private set; }
    public FilterScope Scope { get; private set; }
}

顯然,此類就是一個對過濾器封裝的對象,通過它我們不僅獲得封裝的Filter並且可以獲得其作用域范圍。在此類中有兩個只讀屬性 Instance  和 Scope 。  

好了,說完了有關過濾器一些基本的概念,下面我們繼續回到HttpActionDescriptor的構造函數中。構造函數中用來生成過濾器管道的一句,如下:

 this._filterPipeline = new Lazy<Collection<FilterInfo>>(new Func<Collection<FilterInfo>>(this.InitializeFilterPipeline)); 

在此類中的屬性  _filterPipeline 是一個FliterInfo的集合,由上知,此時還需進行初始化過濾器管道,我們繼續查看此方法的實現:

private Collection<FilterInfo> InitializeFilterPipeline()
{
    return new Collection<FilterInfo>(RemoveDuplicates((from fp in this._configuration.Services.GetFilterProviders() select fp.GetFilters(this._configuration, this)).OrderBy<FilterInfo, FilterInfo>(f => f, FilterInfoComparer.Instance).Reverse<FilterInfo>()).Reverse<FilterInfo>().ToList<FilterInfo>());
}

我們從最外面到最里面進行講解,利用 RemoveDuplicates() 方法來移除重復的,下面會講到,我們來看看此方法 this._configuration.Services.GetFilterProviders() 

public static IEnumerable<IFilterProvider> GetFilterProviders(this ServicesContainer services)
{
    return services.GetServices<IFilterProvider>();
}

首先我們還需要看一個對象FilterPrivoder對象:

過濾器提供者(FilterPrivoder)

FilterProvider是過濾器管道機制中的核心對象,它根據指定的HttpActionDescriptor對象將其應用到對應Action上的過濾去Filter中,他們都實現了IFilterProvider接口,並且還定義了唯一的一個GetFilters方法,最終返回過濾器信息的集合列表,兩個參數分別是表示當前的HttpConfiguration對象以及描述Action方法的HttpActionDescriptor對象。

public interface IFilterProvider
{
    // Methods
    IEnumerable<FilterInfo> GetFilters(HttpConfiguration configuration, HttpActionDescriptor actionDescriptor);
}

從HttpConfiguration實例中的屬性Services來獲取實現了接口IFilterProvider的注冊服務,既然如此我們去看看我們早已經很熟悉的DefaultServices子類,下面我們來看看FilterProvider。

this.SetMultiple<IFilterProvider>(new IFilterProvider[] { new ConfigurationFilterProvider(), new ActionDescriptorFilterProvider() });

由上知,FilterProvider是 ConfigurationFilterProvider 和 ActionDescriptorFilterProvider 兩個對象。下面我們就一一來看。

  • ConfiguraionFilterProvider  
public class ConfigurationFilterProvider : IFilterProvider
{
    // Methods
    public ConfigurationFilterProvider();
    public IEnumerable<FilterInfo> GetFilters(HttpConfiguration configuration, HttpActionDescriptor actionDescriptor);
}

我們查看其方法的實現:

public IEnumerable<FilterInfo> GetFilters(HttpConfiguration configuration, HttpActionDescriptor actionDescriptor)
{
    if (configuration == null)
    {
        throw Error.ArgumentNull("configuration");
    }
    return configuration.Filters;
}

我們繼續看看HttpConfiguration中的屬性Filters具體是什么:

public HttpFilterCollection Filters
{
    get
    {
        return this._filters;
    }
}

知,Filters是過濾器的集合HttpFilterCollection,我們再來詳細看看此類

public class HttpFilterCollection : IEnumerable<FilterInfo>, IEnumerable
{
    // Fields
    private readonly List<FilterInfo> _filters;

    // Methods
    public HttpFilterCollection();
    public void Add(IFilter filter);
    private void AddInternal(FilterInfo filter);
    public void Clear();
    public bool Contains(IFilter filter);
    public IEnumerator<FilterInfo> GetEnumerator();
    public void Remove(IFilter filter);
    IEnumerator IEnumerable.GetEnumerator();

    // Properties
    public int Count { get; }
}

由上知,在HttpCofiguraiton上屬於全局范圍的Filters可以直接注冊到HttpConfiguration上,此Filters是一個只讀屬性,其類型為HttpFilterCollection為FilterInfo對象的集合,此HttpFilterCollection類上的Add、Remve等方法真正作用的不是FilterInfo,而是有FilterInfo封裝的Filter對象,當我們傳入一個Filter進去時,通過調用Add方法,則此對象的FilterInfo則將添加到HttpFilterCollection集合列表中,則這些全局注冊的Filter的FilterScope應該就是Global,不信我們查看其Add方法便知:

public void Add(IFilter filter)
{
    if (filter == null)
    {
        throw Error.ArgumentNull("filter");
    }
    this.AddInternal(new FilterInfo(filter, FilterScope.Global));
}

與我們猜想一致。所以上述ConfigutaionFilterProvider旨在通過FilterInfo來封裝所有注冊的Filter,所以最終返回的就是 configuration.Filters   

  • ActionDescriptorFilterProvider  

由於都是繼承於IFilterProvider,定義必定一樣,只是實現不一樣,我們只需查看具體的實現方法即可:

public IEnumerable<FilterInfo> GetFilters(HttpConfiguration configuration, HttpActionDescriptor actionDescriptor)
{
    if (configuration == null)
    {
        throw Error.ArgumentNull("configuration");
    }
    if (actionDescriptor == null)
    {
        throw Error.ArgumentNull("actionDescriptor");
    }
    IEnumerable<FilterInfo> first = from instance in actionDescriptor.ControllerDescriptor.GetFilters() select new FilterInfo(instance, FilterScope.Controller);
    IEnumerable<FilterInfo> second = from instance in actionDescriptor.GetFilters() select new FilterInfo(instance, FilterScope.Action);
    return first.Concat<FilterInfo>(second);
}

由上知,由ActionDescriptorFilterProvider對象提供的FilterInfo封裝的Filter具有兩個來源:

  1. 作用於Action方法上的Filter

  2. 作用於Action方法所對應的控制器的Filter

對於這二者其對應的FilterScope屬性分別為Action和Controller。

最終整個初始化過濾器管道以及創建過程就已經完成,在HttpActionDesciptor類上的 GetFilterPipeline 方法,如下:

public virtual Collection<FilterInfo> GetFilterPipeline()
{
    return this._filterPipeline.Value;
}

通過此方法來獲得FilterInfo封裝的Filter集合。  

過濾器生成管道順序以及優先級

說了這么久,它們生成的順序到底是怎樣的呢?它們的優先級又是怎樣的呢?我們通過對上述整個創建過濾器過程的了解,我們來手動實現並看看其生成順序以及優先級:

(1)生成管道順序

我們自定義以下三個過濾器:

    public class OneFilter :System.Web.Http.Filters.ActionFilterAttribute
    {
        
    }
    public class TwoFilter : System.Web.Http.Filters.ActionFilterAttribute
    {
     
    }
    public class ThreeFilter : System.Web.Http.Filters.ActionFilterAttribute
    {
        
    }

在Web API配置文件中添加一個全局過濾器:

 config.Filters.Add(new ThreeFilter());

最后實現調用並查看其順序:

    [OneFilter]
    public class ProductController : ApiController
    {

        [TwoFilter]
        public IEnumerable<Tuple<string, System.Web.Http.Filters.FilterScope>> GetFilter()
        {
            var actionSelector = this.Configuration.Services.GetActionSelector();
            var actionDesciptor = actionSelector.SelectAction(this.ControllerContext);
            foreach (var filterInfo in actionDesciptor.GetFilterPipeline())
            {
               yield return new Tuple<string, System.Web.Http.Filters.FilterScope>(filterInfo.Instance.GetType().Name, filterInfo.Scope);
            }
        }
   }

最后訪問生成如圖所示結果:

過濾器生成順序:Global--------------------------------------->Controller------------------------------------->Action

(2)優先級 

首先我們重新建立一個過濾器特性類,如下:

    [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = true)]
    public class OneFilter :System.Web.Http.Filters.FilterAttribute,System.Web.Http.Filters.IActionFilter
    {
        
        public System.Threading.Tasks.Task<HttpResponseMessage> ExecuteActionFilterAsync(System.Web.Http.Controllers.HttpActionContext actionContext, System.Threading.CancellationToken cancellationToken, Func<System.Threading.Tasks.Task<HttpResponseMessage>> continuation)
        {
            return continuation();
        }
    }

接着將控制器以及Action方法進行如下修改:

    [OneFilter]
    public class ProductController : ApiController
    {
        [OneFilter]
        public IEnumerable<Tuple<string, System.Web.Http.Filters.FilterScope>> GetFilter()
        {
            var actionSelector = this.Configuration.Services.GetActionSelector();
            var actionDesciptor = actionSelector.SelectAction(this.ControllerContext);
            foreach (var filterInfo in actionDesciptor.GetFilterPipeline())
            {
                yield return new Tuple<string, System.Web.Http.Filters.FilterScope>(filterInfo.Instance.GetType().Name, filterInfo.Scope);
            }
        }
   }

然后我們將配置文件也進行修改:

  config.Filters.Add(new OneFilter());

此時結果如下:

似乎有點不太對勁,我們在全局和控制器對應的Action方法都使用同一特性,此時並沒有起到過濾的作用,這是在我們意料之外,接下來我們將上述自定義過濾器特性的應用范圍進行如下修改:

[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false)]

我們僅僅是將 AllowMultiple = true 修改為了 AllowMultiple = false ,我們知道位於 AttributeUsage 上的AllowMutiple屬性只能控制我們是否將多個目標特性多次應用到同一對象上而已,接下來我們再來看看其結果。

 

由上知,當使用同一過濾器特性來進行修飾時,Action優先執行。

我們先不管將 AllowMultiple 修改為false,為什么出現如此結果,我們在上述修改基礎上,來修改控制器及Action方法的過濾器特性而配置文件中不變:

   [OneFilter]
    public class ProductController : ApiController
    {

        public IEnumerable<Tuple<string, System.Web.Http.Filters.FilterScope>> GetFilter()
        {
            List<string> list = new List<string>();
         
            var actionSelector = this.Configuration.Services.GetActionSelector();
            var actionDesciptor = actionSelector.SelectAction(this.ControllerContext);
            foreach (var filterInfo in actionDesciptor.GetFilterPipeline())
            {
                yield return new Tuple<string, System.Web.Http.Filters.FilterScope>(filterInfo.Instance.GetType().Name, filterInfo.Scope);
            }
        }
    }

由上知,我們只是去掉了Action方法上的特性,接下來我們來再看看其結果:

 

至此,我們可以得出如下結論

Web API框架多次利用同一Filter在同一目標元素時,所采用唯一性Filter策略,並且Filter總是采用FilterScope最大的那一個,換言之,對於其過濾器作用域(FilterScope)的優先級是:Action-----------------> Controller ------------------>Global

那么問題來了,對於這種唯一性Filter策略是如何實現的呢?請往下看。  

唯一性Filter策略實現原理  

在實現唯一性Filter策略也就是在初始化過濾器的管道過程中,如下:

private Collection<FilterInfo> InitializeFilterPipeline()
{
    return new Collection<FilterInfo>(RemoveDuplicates((from fp in this._configuration.Services.GetFilterProviders() select fp.GetFilters(this._configuration, this)).OrderBy<FilterInfo, FilterInfo>(f => f, FilterInfoComparer.Instance).Reverse<FilterInfo>()).Reverse<FilterInfo>().ToList<FilterInfo>());
}

至於是利用上述如何來實現的,我們只需關注,上述三個紅色標記即可。下面我們一一進行敘述。

  • GetFilters

這個就不用說了,通過注冊了服務容器並實現了其過濾器接口的服務實例,並利用HttpConfiguration中封裝了Filter的FilterInfo的集合列表來進行Linq篩選。

  • FilterInfoComparer 

通過其單詞意思我們就能猜想得到,是進行FilterInfo比較,下面我們具體來看看此類的實現:

internal sealed class FilterInfoComparer : IComparer<FilterInfo>
{
    // Fields
    private static readonly FilterInfoComparer _instance;

    // Methods
    static FilterInfoComparer();
    public FilterInfoComparer();
    public int Compare(FilterInfo x, FilterInfo y);

    // Properties
    public static FilterInfoComparer Instance { get; }
}

在初始化管道中利用 FilterInfoComparer.Instance 實例進行比較,此Instance就是獲取在上面此類的靜態構造函數中實例並賦給字段_instance。最重要的就是實現其比較接口的Compare方法了,我們來看看:

public int Compare(FilterInfo x, FilterInfo y)
{
    if ((x == null) && (y == null))
    {
        return 0;
    }
    if (x == null)
    {
        return -1;
    }
    if (y == null)
    {
        return 1;
    }
    return (int) (x.Scope - y.Scope);
}

接下來我們先進行擱置,最后來看看最后一步。

  • RemoveDuplicates  

從字面意思是去除重復,我們一起來看看:

private static IEnumerable<FilterInfo> RemoveDuplicates(IEnumerable<FilterInfo> filters)
{
    HashSet<Type> iteratorVariable0 = new HashSet<Type>();
    foreach (FilterInfo iteratorVariable1 in filters)
    {
        object instance = iteratorVariable1.Instance;
        Type type = instance.GetType();
        if (!iteratorVariable0.Contains(type) || AllowMultiple(instance))
        {
            yield return iteratorVariable1;
            iteratorVariable0.Add(type);
        }
    }
}

我們再來看看上述紅色標記的AllowMutiple方法:

private static bool AllowMultiple(object filterInstance)
{
    IFilter filter = filterInstance as IFilter;
    if (filter != null)
    {
        return filter.AllowMultiple;
    }
    return true;
}

到這里為止,想必大家知道了是怎樣來進行篩選的了,篩選過程分為兩類:

  • 相同控制器上及Action方法有不同的Filter特性時

此時通過上述比較方法並利用初始化管道中的OrderBy從小到大排序即Global->Controller->Action,然后通過Reverse方法進行扭轉即Action->Controller->Global,接着利用RemoveDuplicates方法來去除重復,去除重復是利用過濾器的類型進行篩選,此時不同的Filter都會添加到其中,最后通過Reverse繼續扭轉最終得到就是Global->Controller->Action。

  • 相同控制器上及Action方法有相同的Filter特性時 

這個和上述執行到RemoveDuplicate之前是是一致的,當執行到RemoveDuplicates時,此時的通過第一個Reverse順序依然是Action->Controller->Global,我們能想到過濾器特性類型都是一樣的,由於在 AttributeUsage 中的屬性AllowMutiple默認是true,當檢測到第二個類型一樣時,此時第一個條件就沒進行過濾,而第二個檢測到true則依然如上述一樣全部被執行並最終利用Reverse扭轉依然是Global->Controller->Action,如我們所演示,將其AllowMultiple設置為false,此時第二個條件也就不再起作用,也就是說不會添加到FilterInfo集合列表中,換言之,相同的過濾器類型應用到控制器及方法或者是全局中時,此時只有第一個即Action會添加到FilterInfo集合列表中,最終進行Reverse扭轉依然只有一個那就是優先級最高的Action,當然了前提是我們在Action方法使用了自定義特性,若未使用而是在控制器上使用最終輸出的當然也就是優先級次之的Controller了,以此類推。

總結

至此,過濾器的初始化以及創建過程中的篩選就全部完成,不去了解尚未知,一旦去研究了,樂趣還是挺多。

過濾器類型(Filter Type)

Filter類型有五種,它能夠在Action執行的不同時期被調用,下面列出:

AuthenticationFilter

AuthcnticationFiIter會在執行Action方法之前被調用以實現對請求的認證,所有的AuthenticationFilter類型均實現了IAuthenticationFilter接口。

AuthorizationFilter

應用的AuthorticationFilter會在Action執行之前將被調用以針對當前請求的授權檢查,所有的AuthorizationFilter類型均實現了IAuthorizationFilter接口。

ActionFilter

ActionFilter注冊的回調操作會在執行Action方法前后被調用,所有的ActionFilter類型均實現了IActionFilter接口。 

ExceptionFilter 

當在執行Action過程中拋出異常,ExceptionFilter會被調用來實現對異常的處理,所有的ExceptionFilter類型均實現了IExceptionFilter接口。 

OverrideFilter 

在默認情況下,全局注冊的Filter和注冊到控制器類上的Filter都會應用到Action方法上,如果我們希望某個Action方法屏蔽這些外層注冊的Filter,可以在該方法上實現OverrideFilter,反之亦然,所有的OverrideFilter類型均實現了IOverrideFilter接口。 

總結 

過濾器創建過程示意圖

圖片來源:過濾器創建過程  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM