web-dev-qa-db-fra.com

Ordre d'exécution avec plusieurs filtres dans l'API Web

J'utilise le dernier web api.

J'annote certains contrôleurs avec 3 attributs de filtre différents.

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

Je ne peux pas être sûr que les filtres fonctionnent dans l'ordre dans lequel ils ont été déclarés, de haut en bas.

Comment définir l'ordre d'exécution dans 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

Dois-je toujours régler ce problème moi-même ??

38
HelloWorld

Quelques points à noter ici: 

  1. Les filtres sont exécutés dans l'ordre suivant pour une action: Globalement Filtres définis -> Filtres spécifiques au contrôleur -> Filtres spécifiques à l'action. 
  2. Filtres d'autorisation -> Filtres d'action -> Filtres d'exception
  3. Maintenant, le problème que vous semblez mentionner est lié au fait que Ayant plusieurs filtres du même genre (ex: Multiple ActionFilterAttribute décoré sur un contrôleur pas garantir l'ordre comme basé sur la réflexion.). Dans ce cas, il existe un moyen De le faire dans Web API en utilisant une implémentation personnalisée de System.Web.Http.Filters.IFilterProvider. J'ai essayé le suivant Et fait quelques tests pour le vérifier. Cela semble marcher correctement. Vous pouvez essayer et voir si cela fonctionne comme prévu. 

    // 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; }
    }
    
63
Kiran Challa

La solution de Kiran Challa me posait quelques problèmes .. Voici ma modification.

Le problème était dans la méthode 

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

Comme vous pouvez le constater, les filtres uniquement qui implémentent IOrderedFilter seront renvoyés. J'ai eu un attribut tiers qui est coupé et en conséquence non exécuté.

J'ai donc eu deux solutions possibles.

  1. Utilisez l'héritage pour créer une version étendue de l'attribut tiers afin de lui permettre également d'implémenter IOrderFilter.
  2. Modifiez la méthode pour traiter chaque attribut qui n'implémente pas IOrderFilter comme un attribut IOrderFilter avec le numéro de commande 0 et combinez-le avec les attributs IOrderFilter, commandez-le et renvoyez-le. 

La deuxième solution est préférable car elle me permet d’apporter mon attribut IOrderFilter avant des attributs tiers qui n’implémentent pas IOrderFilter.

Échantillon

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

Donc l'exécution serait 

  • OrderableAttributeA
  • NonOrderableThirdPartyAttribute
  • OrderableAttributeB
  • OrderableAttributeC

Alors voici le code modifié

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);
    }
}
14
dknaack

Si vous avez plusieurs filtres du même type, l'ordre d'exécution sera Global -> Controller -> Action

Et pour le filtre d'autorisation, si vous définissez plusieurs filtres à différents niveaux, ils seront combinés avec un "ET" et seront calculés dans l'ordre d'exécution ci-dessus. 

Et le processus d'autorisation échouera au premier filtre défaillant.

Pour plus de détails, vous pouvez consulter ce post.

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

0
Shawn Teng