在Web API中使用多个过滤器的执行顺序

55

我正在使用最新的Web API

我在一些控制器上注释了三个不同的过滤器属性。

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

我还需要自己修复吗?


1
这个链接:https://www.strathweb.com/2012/06/control-the-execution-order-of-your-filters-in-asp-net-web-api/ 有帮助吗? - Devesh
5个回答

80

需要注意的几点:

  1. 过滤器按照以下顺序执行:全局定义的过滤器 -> 控制器特定的过滤器 -> 操作特定的过滤器。
  2. 授权过滤器 -> 操作过滤器 -> 异常过滤器。
  3. 现在您提到的问题似乎与拥有相同类型的多个过滤器有关(例如:多个装饰在控制器或操作上的ActionFilterAttribute)。这种情况不能保证顺序,因为它是基于反射的。对于这种情况,在Web API中可以使用System.Web.Http.Filters.IFilterProvider的自定义实现来解决。我已经尝试并进行了一些测试以验证它的有效性。它似乎可以正常工作。您可以尝试一下,看看它是否符合您的预期。

// 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; }
}

3
在哪个文件中添加 config.Services.Replace(... 代码? - Jeff Tian
2
再次提问,config.Services.Replace(... 代码应该放在哪里? - freakinthesun
如果您使用OWIN管道进行托管,则调用config.Services.Replace应放在您的Startup中,否则应放在Global.asax.cs中。 - Tomas Aschan
1
我有另一个答案提供了一些改进。随便看看。 - dknaack
@Abacus 这个引用是针对MVC过滤器的。讨论中所提到的排序是针对Web API过滤器的。MVC过滤器内置了指定顺序的功能。 - Lovethenakedgun
显示剩余4条评论

24

我对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));
}

你可以看到,只有实现了IOrderedFilter接口的筛选器才会被返回。我有一个第三方属性被删掉了,因此没有被执行。

所以我有两个可能的解决方案:

  1. 使用继承创建第三方属性的扩展版本,使它也实现IOrderFilter接口。
  2. 更改方法,将每个未实现IOrderFilter接口的属性视为具有排序号为0的IOrderFilter属性,并将其与IOrderFilter属性组合排序并返回。

第二种解决方案更好,因为它允许我将我的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);
    }
}

2
在 asp.net core 中,您可以使用 IOrderedFilter 进行管理。
public class MyFilter : IActionFilter, IOrderedFilter
{
    public int Order => 1; // define the order here
    ...
}

1

0
如果在MVC中实现了多个过滤器,则执行顺序如下:
  1. 授权过滤器
  2. 操作过滤器
  3. 结果过滤器
  4. 异常过滤器

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接