aspnet / Mvc

[Archived] ASP.NET Core MVC is a model view controller framework for building dynamic web sites with clean separation of concerns, including the merged MVC, Web API, and Web Pages w/ Razor. Project moved to https://github.com/aspnet/AspNetCore

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

DefaultActionDescriptorCollectionProvider is internal

tugberkugurlu opened this issue · comments

Is this a Bug or Feature request?:

I would say neither, I am looking for a migration guidance.

Description of the problem:

At v2.1 or below, ActionDescriptorCollectionProvider wasn't an abstract class and I was able to hold a reference to within my own implementation for an IActionDescriptorCollectionProvider. Including it below for reference but it won't compile as it's missing related components:


FeatureFlagAwareActionDescriptorCollectionProvider.cs

    public class FeatureFlagAwareActionDescriptorCollectionProvider : IActionDescriptorCollectionProvider
    {
        private bool _initialized;
        private object _initializationLock = new object();
        private object _initializationTarget;
        private readonly IServiceProvider _serviceProvider;
        private readonly ActionDescriptorCollectionProvider _actionDescriptorCollectionProvider;
        private IReadOnlyList<ActionDescriptor> _filteredActionDescriptors;

        public FeatureFlagAwareActionDescriptorCollectionProvider(IEnumerable<IActionDescriptorProvider> actionDescriptorProviders,
            IEnumerable<IActionDescriptorChangeProvider> actionDescriptorChangeProviders, IServiceProvider serviceProvider)
        {
            if (actionDescriptorProviders == null) throw new ArgumentNullException(nameof(actionDescriptorProviders));
            if (actionDescriptorChangeProviders == null) throw new ArgumentNullException(nameof(actionDescriptorChangeProviders));

            _serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider));
            _actionDescriptorCollectionProvider = new ActionDescriptorCollectionProvider(actionDescriptorProviders, actionDescriptorChangeProviders);
        }

        /// <seealso href="https://stackoverflow.com/a/39279457" />
        public ActionDescriptorCollection ActionDescriptors
        {
            get
            {
                EnsureInitialized();
                return new ActionDescriptorCollection(_filteredActionDescriptors, _actionDescriptorCollectionProvider.ActionDescriptors.Version);
            }
        }

        private bool IsFeatureFlagged(ControllerActionDescriptor descriptor)
        {
            var ffAttributeData = descriptor.MethodInfo.CustomAttributes.FirstOrDefault(attribute =>
                attribute.AttributeType == typeof(FeatureFlaggedAttribute));

            if (ffAttributeData != null)
            {
                var ffAttribute = (FeatureFlaggedAttribute) descriptor.MethodInfo.GetCustomAttribute(typeof(FeatureFlaggedAttribute));
                var optionsType = typeof(IOptions<>).MakeGenericType(ffAttribute.FeatureFlagType);
                var featureFlagOption = _serviceProvider.GetService(optionsType);

                // ReSharper disable once PossibleNullReferenceException - we know that GetProperty will not going to be null.
                var featureFlag = optionsType.GetProperty(nameof(IOptions<MarkerFeatureFlag>.Value)).GetValue(featureFlagOption) as IFeatureFlag;

                // ReSharper disable once PossibleNullReferenceException - we rely on the fact that this will be auto-registered by the DI system.
                return !featureFlag.IsEnabled;
            }

            return false;
        }

        private void EnsureInitialized()
        {
            LazyInitializer.EnsureInitialized(ref _initializationTarget, ref _initialized, ref _initializationLock, () =>
            {
                _filteredActionDescriptors = _actionDescriptorCollectionProvider.ActionDescriptors
                    .Items
                    .Where(descriptor =>
                    {
                        if (descriptor is ControllerActionDescriptor controllerActionDescriptor)
                        {
                            return !IsFeatureFlagged(controllerActionDescriptor);
                        }

                        return true;
                    })
                    .ToList();

                return null;
            });
        }

        private class MarkerFeatureFlag : IFeatureFlag
        {
            public bool IsEnabled { get; set; }
        }
    }


With 2.2.0, ActionDescriptorCollectionProvider seems to be an abstract class now and actual implementation is done inside the internal DefaultActionDescriptorCollectionProvider class.

With 2.2.0, would you please advice what my options are to implement the action selection filtering logic as above?

Version of Microsoft.AspNetCore.Mvc or Microsoft.AspNetCore.App or Microsoft.AspNetCore.All:

2.2.0

commented

Thanks for contacting us, @tugberkugurlu.
@rynowak, can you please help out @tugberkugurlu here? Thanks!

Hi @tugberkugurlu - you should instead register an additional implementation if IActionDescriptorProvider - make the order value a bigger number than the build in implementations.

These are a pipeline so when yours is called you'll see the results of the other providers in context.Results and you can do your filtering by removing items you want to turn off.

I think you'll find this more straightforward than what you're currently doing.

Lovely ✨ That's a much better way to approach the problem, thanks @rynowak! Will give that a go, I am hoping that NSwag will also honor that as it's honoring the custom IActionDescriptorCollectionProvider but seems like all is connected together and at the end produce a coherent set of actions.