Order of execution with multiple filters in web api
Asked Answered
B

5

56

I am using latest web api.

I do annotate some controllers with 3 different filter attributes.

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

I can not be sure that the filters run in the order they are declared from top to down.

How do I define the order of execution in 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

Do I still have to fix that for myself ??

Breastsummer answered 7/2, 2014 at 13:0 Comment(1)
Does this link: strathweb.com/2012/06/… help?Halicarnassus
S
80

Some things to note here:

  1. Filters get executed in the following order for an action: Globally Defined Filters -> Controller-specific Filters -> Action-specific Filters.
  2. Authorization Filters -> Action Filters -> Exception Filters
  3. Now the problem that you seem to mention is related to having multiple filters of the same kind (ex: Multiple ActionFilterAttribute decorated on a controller or an action. This is the case which would not guarantee the order as its based on reflection.). For this case, there is a way to do it in Web API using custom implementation of System.Web.Http.Filters.IFilterProvider. I have tried the following and did some testing to verify it. It seems to work fine. You can give it a try and see if it works as you expected.

    // 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; }
    }
    
Serology answered 7/2, 2014 at 14:59 Comment(9)
Which file to add the config.Services.Replace(... Code?Bawl
@KiranChalla What about globally defined action filters?Skylight
Its executing the globally defined filter no matter what the collection is ordered. Anyway around this?Skylight
And once again, where does the config.Services.Replace(... code go?Lea
@freakinthesun: The call to config.Services.Replace goes in your Startup if you're hosting with the OWIN pipeline, or in Global.asax.cs if you're not.Felic
I had another answer providing some improvements. Feel free to have a look.Lophobranch
@MIKE I'm having the same problem - it's caused by the call to config.Services.Replace, but I don't have a solution right now.Gluttony
For reference, order of filters at: tutorialspoint.com/asp.net_mvc/asp.net_mvc_filters.htmCalyptrogen
@Calyptrogen that reference is for MVC filters. The ordering being discussed is for Web API filters. MVC filters have the ability to specify ordering built-in.Halfbreed
L
24

I had some problems with the solution from Kiran Challa 's answer. Here is my modification.

The problem was in the method

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

As you can see only filters that implement IOrderedFilter will get returned. I had a third party attribute that gets cut of and as a result not executed.

So i had two possible solutions.

  1. Use inheritance to create a extended version of the third party attribute in order to make it implement IOrderFilter too.
  2. Change the method to treat every attribute wich does not implement IOrderFilter like a IOrderFilter attribute with the order number of 0 and combine it with the IOrderFilter attributes, order and return them.

The second solution is better cause it allows me to bring my IOrderFilter attribute before third party attributes that does not implement IOrderFilter.

Sample

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

So the execution would be

  • OrderableAttributeA
  • NonOrderableThirdPartyAttribute
  • OrderableAttributeB
  • OrderableAttributeC

So here is the modified code

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);
    }
}
Lophobranch answered 1/3, 2016 at 9:36 Comment(0)
P
2

In asp.net core you can manage that using IOrderedFilter

public class MyFilter : IActionFilter, IOrderedFilter
{
    public int Order => 1; // define the order here
    ...
}
Poky answered 19/4, 2022 at 7:26 Comment(0)
B
1

If you have multiple filters of the same kind, the execution order will be Global -> Controller -> Action

And for authorization filter, if you set multiple filters at different levels, they will be combined with an "AND" and will be calculated in the above execution order.

And the authorization process will fail at the first failed filter.

For more details, you can check this post.

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

Bolanos answered 12/9, 2018 at 8:54 Comment(0)
V
0

If multiple filters are implemented in MVC then the order of executing will be

  1. Authorization filter
  2. Action filter
  3. Result filter
  4. Exception filter
Varicella answered 25/5, 2022 at 14:10 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.