web-dev-qa-db-ja.com

Web APIの複数のフィルターを使用した実行順序

最新のweb apiを使用しています。

3つの異なるフィルター属性を持つコントローラーに注釈を付けますsome

1 [Authorize]
2 [RessourceOwnerAttribute derived from AuthorizationFilterAttribute]
3 [InvalidModelStateAttribute derived from ActionFilterAttribute]

フィルターが上から下に宣言された順序で実行されるかどうかはわかりません。

web api 2.1で実行順序を定義するにはどうすればよいですか?

https://aspnetwebstack.codeplex.com/workitem/1065#

http://aspnet.uservoice.com/forums/147201-asp-net-web-api/suggestions/3346720-execution-order-of-mvc4-webapi-action-filters

私はまだ自分でそれを修正する必要がありますか?

44
HelloWorld

ここで注意すべき点がいくつかあります。

  1. フィルターは、アクションに対して次の順序で実行されます。グローバルに定義されたフィルター->コントローラー固有のフィルター->アクション固有のフィルター。
  2. 許可フィルター->アクションフィルター->例外フィルター
  3. さて、あなたが言及していると思われる問題は、同じ種類の複数フィルターを持つことに関連しています(例:コントローラーまたはアクションで複数のActionFilterAttributeが装飾されています。反映に基づいて順序を保証します。)。この場合、System.Web.Http.Filters.IFilterProviderのカスタム実装を使用して、Web APIでそれを行う方法があります。私は次のことを試し、それを確認するためにいくつかのテストを行いました。それはうまくいくようです。試してみて、期待どおりに動作するかどうかを確認できます。

    // Start clean by replacing with filter provider for global configuration.
    // For these globally added filters we need not do any ordering as filters are 
    // executed in the order they are added to the filter collection
    config.Services.Replace(typeof(IFilterProvider), new System.Web.Http.Filters.ConfigurationFilterProvider());
    
    // Custom action filter provider which does ordering
    config.Services.Add(typeof(IFilterProvider), new OrderedFilterProvider());
    

    public class OrderedFilterProvider : IFilterProvider
    {
        public IEnumerable<FilterInfo> GetFilters(HttpConfiguration configuration, HttpActionDescriptor actionDescriptor)
        {
            // controller-specific
            IEnumerable<FilterInfo> controllerSpecificFilters = OrderFilters(actionDescriptor.ControllerDescriptor.GetFilters(), FilterScope.Controller);
    
            // action-specific
            IEnumerable<FilterInfo> actionSpecificFilters = OrderFilters(actionDescriptor.GetFilters(), FilterScope.Action);
    
            return controllerSpecificFilters.Concat(actionSpecificFilters);
        }
    
        private IEnumerable<FilterInfo> OrderFilters(IEnumerable<IFilter> filters, FilterScope scope)
        {
            return filters.OfType<IOrderedFilter>()
                            .OrderBy(filter => filter.Order)
                            .Select(instance => new FilterInfo(instance, scope));
        }
    }
    

    //NOTE: Here I am creating base attributes which you would need to inherit from.
    public interface IOrderedFilter : IFilter
    {
        int Order { get; set; }
    }
    
    public class ActionFilterWithOrderAttribute : ActionFilterAttribute, IOrderedFilter
    {
        public int Order { get; set; }
    }
    
    public class AuthorizationFilterWithOrderAttribute : AuthorizationFilterAttribute, IOrderedFilter
    {
        public int Order { get; set; }
    }
    
    public class ExceptionFilterWithOrderAttribute : ExceptionFilterAttribute, IOrderedFilter
    {
        public int Order { get; set; }
    }
    
67
Kiran Challa

Kiran Challaの答えからの解決策にいくつかの問題がありました。これが私の修正です。

問題はメソッドにありました

private IEnumerable<FilterInfo> OrderFilters(IEnumerable<IFilter> filters, FilterScope scope)
{
    return filters.OfType<IOrderedFilter>()
                    .OrderBy(filter => filter.Order)
                    .Select(instance => new FilterInfo(instance, scope));
}

ご覧のとおりonlyIOrderedFilterを実装するフィルターが返されます。サードパーティの属性があり、それが切り取られ、結果として実行されません。

だから私は2つの可能な解決策がありました。

  1. 継承を使用して、サードパーティ属性の拡張バージョンを作成し、IOrderFilterも実装するようにします。
  2. メソッドを変更して、順序番号が0のIOrderFilter属性のようなIOrderFilterを実装していないすべての属性を処理し、IOrderFilter属性と組み合わせて順序付けて返します。

2番目の解決策は、IOrderFilterを実装しないサードパーティの属性の前にIOrderFilter属性を持ってくることができるためです。

サンプル

[NonOrderableThirdPartyAttribute]
[OrderableAttributeA(Order = -1)]
[OrderableAttributeB(Order = 1)]
[OrderableAttributeC(Order = 2)]
public async Task<IHttpActionResult> Post(... request) 
{
    // do something
}

だから実行は

  • OrderableAttributeA
  • NonOrderableThirdPartyAttribute
  • OrderableAttributeB
  • OrderableAttributeC

これが修正されたコードです

public class OrderedFilterProvider : IFilterProvider
{
    public IEnumerable<FilterInfo> GetFilters(HttpConfiguration configuration, HttpActionDescriptor actionDescriptor)
    {
        // controller-specific
        var controllerSpecificFilters = OrderFilters(actionDescriptor.ControllerDescriptor.GetFilters(), FilterScope.Controller);

        // action-specific
        var actionSpecificFilters = OrderFilters(actionDescriptor.GetFilters(), FilterScope.Action);

        return controllerSpecificFilters.Concat(actionSpecificFilters);
    }

    private IEnumerable<FilterInfo> OrderFilters(IEnumerable<IFilter> filters, FilterScope scope)
    {
        // get all filter that dont implement IOrderedFilter and give them order number of 0
        var notOrderableFilter = filters.Where(f => !(f is IOrderedFilter))
            .Select(instance => new KeyValuePair<int, FilterInfo>(0, new FilterInfo(instance, scope)));

        // get all filter that implement IOrderFilter and give them order number from the instance
        var orderableFilter = filters.OfType<IOrderedFilter>().OrderBy(filter => filter.Order)
            .Select(instance => new KeyValuePair<int, FilterInfo>(instance.Order, new FilterInfo(instance, scope)));

        // concat lists => order => return
        return notOrderableFilter.Concat(orderableFilter).OrderBy(x => x.Key).Select(y => y.Value);
    }
}
15
dknaack

同じ種類のフィルターが複数ある場合、実行順序は[グローバル]-> [コントローラー]-> [アクション]になります。

許可フィルターの場合、異なるレベルで複数のフィルターを設定すると、それらは「AND」と結合され、上記の実行順序で計算されます。

そして、認証プロセスは、最初に失敗したフィルターで失敗します。

詳細については、この投稿を確認してください。

https://docs.Microsoft.com/en-us/aspnet/core/security/authorization/roles?view=aspnetcore-2.1

1
Shawn Teng