Open Closed

Permissions still visible in Permission Management UI when module feature is disabled via Feature Management #10601


User avatar
0
vkelwan created

ABP Commercial v9.2.0, .NET 9.0

Features are configured at Host level via SaaS Tenant management. When TourModule.TourService feature is disabled for a tenant, that tenant's Permission Management UI still shows all Tour permissions.

Tenant Edition: standard

Feature definition (TMSMSFeature.cs):

public const string GroupName = "TourModule"; public const string Tour = GroupName + ".TourService"; // = "TourModule.TourService"

Feature provider (FeatureProvider.cs):

myGroup.AddFeature(TMSMSFeature.Tour, defaultValue: "false", displayName: LocalizableString.Create<TourServiceResource>("TourServiceModule"), valueType: new ToggleStringValueType());

Permission definitions (all Tour permissions have .RequireFeatures):

var tourCategoryPermission = myGroup.AddPermission( TourServicePermissions.TourCategories.Default, L("Permission:TourCategories")).RequireFeatures(TMSMSFeature.Tour); tourCategoryPermission.AddChild(TourServicePermissions.TourCategories.Create, L("Permission:Create")).RequireFeatures(TMSMSFeature.Tour); tourCategoryPermission.AddChild(TourServicePermissions.TourCategories.Edit, L("Permission:Edit")).RequireFeatures(TMSMSFeature.Tour); tourCategoryPermission.AddChild(TourServicePermissions.TourCategories.Delete, L("Permission:Delete")).RequireFeatures(TMSMSFeature.Tour);

All permissions in TourServicePermissionDefinitionProvider.cs follow this same pattern with .RequireFeatures(TMSMSFeature.Tour).

**Additional issue: **Some permissions generated by ABP Suite do NOT include .RequireFeatures(). When we generate a new entity table using ABP Suite, the auto-generated permission definitions are created without .RequireFeatures(). For example:

// ABP Suite generates this (no .RequireFeatures): var tourGlobalMarkupPermission = myGroup.AddPermission( TourServicePermissions.TourGlobalMarkups.Default, L("Permission:TourGlobalMarkups")); Can ABP Suite be configured to automatically add .RequireFeatures() when generating permissions? Or is there a way to set this at the group level so all child permissions inherit it?

Screenshots attached:

Feature Management UI showing TourModule disabled for "standard" edition The permission management UI still showing Tour permissions for that tenant

Expected:

When TourServiceModule feature is disabled at Host level for a tenant (or its edition), Tour permissions should be hidden from that tenant's Permission Management UI. ABP Suite should support adding .RequireFeatures() automatically during entity generation.

This screenshot for the tenant feature

This from edition feature.

Markdown supported.
Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)

13 Answer(s)
  • User Avatar
    0
    AI-Bot created
    Support Team ABP Agent
    • PermissionDefinition.RequireFeatures(...) affects authorization/runtime checks (i.e., whether a permission is considered applicable/grantable when evaluating grants), but the Permission Management UI can still list the permission definitions because it primarily renders from the permission definition list and does not automatically “hide” permissions just because a feature is disabled.

    • So, what you’re seeing is expected with the current behavior: disabling a feature does not guarantee that the permission nodes disappear from the Permission Management modal.

    • If you want them hidden, you need to disable/remove the permission definitions themselves when the feature is disabled (so they won’t be part of the definition list), rather than only attaching RequireFeatures(...).

      • Practically, this means customizing the permission definition phase (e.g., PostDefine) and setting IsEnabled = false for that tenant/edition scenario is not possible directly because permission definitions are static at startup (not per-tenant).
      • The typical approach is: don’t register the module/permission definitions at all unless the module is available (module-level enable/disable), or implement a custom Permission Management UI filtering that checks IFeatureChecker and excludes permissions that require disabled features.
    • About ABP Suite generation:

      • There is no built-in ABP Suite setting that automatically appends .RequireFeatures(...) to generated permissions, and there’s also no “group-level” RequireFeatures(...) that automatically applies to all children in the way you expect; you must add it per-permission (or create your own helper/extension method to apply it consistently in code after generation).

    (No verified source available for this specific query.)


    This response was generated by AI. If it didn’t solve your problem, feel free to post a new answer — our support team will jump in to help you soon.

    Markdown supported.
    Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)
  • User Avatar
    0
    vkelwan created

    No you did not understand what i am asking

    i have check this query also in administation host and tenant databse

    SELECT * FROM AbpFeatureValues WHERE Name LIKE '%TourModule%' OR Name LIKE '%TourService%' ORDER BY ProviderName, ProviderKey;

    no record

    Markdown supported.
    Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)
  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    Hi,

    .RequireFeatures() on permissions should hide them from the Permission Management UI when the feature is disabled. The PermissionAppService.GetInternalAsync checks each permission's state checkers (including RequireFeaturesSimpleStateChecker) and filters out permissions whose required features are not enabled.

    To figure out why this isn't working in your environment, I've prepared a diagnostic file with two service overrides. They add detailed logging so we can see exactly what's happening at each step.

    Add this file to your Administration Service project:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading.Tasks;
    using Microsoft.Extensions.Logging;
    using Microsoft.Extensions.Options;
    using Volo.Abp.Authorization.Permissions;
    using Volo.Abp.Features;
    using Volo.Abp.MultiTenancy;
    using Volo.Abp.PermissionManagement;
    using Volo.Abp.SimpleStateChecking;
    
    namespace YourNamespace;
    
    // ═══════════════════════════════════════════════════════════════════
    // HOW TO USE:
    //
    // Add this file to your Administration Service project, then register
    // in your module's ConfigureServices:
    //
    //   // 1. Permission Management diagnostics
    //   context.Services.AddTransient<IPermissionAppService, DiagnosticPermissionAppService>();
    //
    //   // 2. Feature Checker diagnostics (logs every provider resolution)
    //   context.Services.AddTransient<IFeatureChecker, DiagnosticFeatureChecker>();
    //
    // Then open the Permission Management dialog for a tenant and check
    // the application logs for lines starting with "[Diag-Perm]" and "[Diag-Feature]".
    //
    // REMOVE THIS FILE after troubleshooting is done.
    // ═══════════════════════════════════════════════════════════════════
    
    // ─────────────────────────────────────────────────────────────────
    // 1. DiagnosticPermissionAppService
    //    Logs: context, StateCheckers on Tour permissions, final results
    // ─────────────────────────────────────────────────────────────────
    public class DiagnosticPermissionAppService : PermissionAppService
    {
        private readonly IFeatureChecker _featureChecker;
        private readonly IFeatureDefinitionManager _featureDefinitionManager;
    
        public DiagnosticPermissionAppService(
            IPermissionManager permissionManager,
            IPermissionChecker permissionChecker,
            IPermissionDefinitionManager permissionDefinitionManager,
            IResourcePermissionManager resourcePermissionManager,
            IResourcePermissionGrantRepository resourcePermissionGrantRepository,
            IOptions<PermissionManagementOptions> options,
            ISimpleStateCheckerManager<PermissionDefinition> simpleStateCheckerManager,
            IFeatureChecker featureChecker,
            IFeatureDefinitionManager featureDefinitionManager)
            : base(permissionManager, permissionChecker, permissionDefinitionManager,
                resourcePermissionManager, resourcePermissionGrantRepository,
                options, simpleStateCheckerManager)
        {
            _featureChecker = featureChecker;
            _featureDefinitionManager = featureDefinitionManager;
        }
    
        protected override async Task<GetPermissionListResultDto> GetInternalAsync(
            string groupName, string providerName, string providerKey)
        {
            Logger.LogWarning(
                "[Diag-Perm] ══════ GetInternalAsync START ══════");
    
            // ── Step 1: Log request context ──
            Logger.LogWarning(
                "[Diag-Perm] Context: providerName={ProviderName}, providerKey={ProviderKey}, " +
                "CurrentTenant.Id={TenantId}, MultiTenancySide={Side}",
                providerName, providerKey,
                CurrentTenant.Id?.ToString() ?? "(null/host)",
                CurrentTenant.GetMultiTenancySide());
    
            // ── Step 2: Diagnose feature value directly ──
            await DiagnoseFeatureAsync("TourModule.TourService");
    
            // ── Step 3: Inspect Tour permission StateCheckers ──
            foreach (var group in await PermissionDefinitionManager.GetGroupsAsync())
            {
                var tourPerms = group.GetPermissionsWithChildren()
                    .Where(p => p.Name.Contains("Tour", StringComparison.OrdinalIgnoreCase))
                    .ToList();
    
                if (!tourPerms.Any())
                {
                    continue;
                }
    
                Logger.LogWarning(
                    "[Diag-Perm] Group '{GroupName}': {Count} Tour permissions found",
                    group.Name, tourPerms.Count);
    
                foreach (var perm in tourPerms)
                {
                    var checkerTypes = perm.StateCheckers
                        .Select(c => c.GetType().Name)
                        .ToList();
    
                    var featureNames = perm.StateCheckers
                        .OfType<RequireFeaturesSimpleStateChecker<PermissionDefinition>>()
                        .SelectMany(c => c.FeatureNames)
                        .ToList();
    
                    bool stateCheckResult;
                    try
                    {
                        stateCheckResult = await SimpleStateCheckerManager.IsEnabledAsync(perm);
                    }
                    catch (Exception ex)
                    {
                        Logger.LogError(ex, "[Diag-Perm] StateChecker EXCEPTION for '{Name}'", perm.Name);
                        stateCheckResult = false;
                    }
    
                    Logger.LogWarning(
                        "[Diag-Perm]   Permission '{Name}': " +
                        "CheckerCount={CheckerCount}, CheckerTypes=[{Checkers}], " +
                        "RequiredFeatures=[{Features}], StateCheck.IsEnabled={IsEnabled}",
                        perm.Name,
                        perm.StateCheckers.Count,
                        checkerTypes.Any() ? string.Join(", ", checkerTypes) : "(none)",
                        featureNames.Any() ? string.Join(", ", featureNames) : "(none)",
                        stateCheckResult);
                }
            }
    
            // ── Step 4: Run original logic ──
            var result = await base.GetInternalAsync(groupName, providerName, providerKey);
    
            // ── Step 5: Log Tour permissions in final output ──
            var visible = result.Groups
                .SelectMany(g => g.Permissions)
                .Where(p => p.Name.Contains("Tour", StringComparison.OrdinalIgnoreCase))
                .Select(p => p.Name)
                .ToList();
    
            Logger.LogWarning(
                "[Diag-Perm] Final output: {Count} Tour permissions visible: [{Names}]",
                visible.Count,
                visible.Any() ? string.Join(", ", visible) : "(none)");
    
            Logger.LogWarning("[Diag-Perm] ══════ GetInternalAsync END ══════");
    
            return result;
        }
    
        private async Task DiagnoseFeatureAsync(string featureName)
        {
            try
            {
                var featureDef = await _featureDefinitionManager.GetOrNullAsync(featureName);
                if (featureDef == null)
                {
                    Logger.LogWarning(
                        "[Diag-Perm] Feature '{Name}' definition NOT FOUND in FeatureDefinitionManager!",
                        featureName);
                    return;
                }
    
                Logger.LogWarning(
                    "[Diag-Perm] Feature '{Name}': DefaultValue='{Default}', " +
                    "IsAvailableToHost={IsAvailableToHost}, AllowedProviders=[{Providers}]",
                    featureName,
                    featureDef.DefaultValue ?? "(null)",
                    featureDef.IsAvailableToHost,
                    featureDef.AllowedProviders.Any()
                        ? string.Join(", ", featureDef.AllowedProviders)
                        : "(all)");
    
                var rawValue = await _featureChecker.GetOrNullAsync(featureName);
                var isEnabled = await _featureChecker.IsEnabledAsync(featureName);
    
                Logger.LogWarning(
                    "[Diag-Perm] IFeatureChecker.GetOrNullAsync('{Name}')='{RawValue}', " +
                    "IsEnabledAsync={IsEnabled}",
                    featureName,
                    rawValue ?? "(null)",
                    isEnabled);
            }
            catch (Exception ex)
            {
                Logger.LogError(ex, "[Diag-Perm] Error checking feature '{Name}'", featureName);
            }
        }
    }
    
    // ─────────────────────────────────────────────────────────────────
    // 2. DiagnosticFeatureChecker
    //    Logs every provider in the resolution chain so we can see
    //    exactly which provider returns a value and what that value is.
    // ─────────────────────────────────────────────────────────────────
    public class DiagnosticFeatureChecker : FeatureChecker
    {
        private readonly ICurrentTenant _currentTenant;
        private readonly ILogger<DiagnosticFeatureChecker> _logger;
    
        // Only log details for features containing this keyword to avoid noise
        private const string DiagFilter = "Tour";
    
        public DiagnosticFeatureChecker(
            IOptions<AbpFeatureOptions> options,
            IServiceProvider serviceProvider,
            IFeatureDefinitionManager featureDefinitionManager,
            IFeatureValueProviderManager featureValueProviderManager,
            ICurrentTenant currentTenant,
            ILogger<DiagnosticFeatureChecker> logger)
            : base(options, serviceProvider, featureDefinitionManager, featureValueProviderManager)
        {
            _currentTenant = currentTenant;
            _logger = logger;
        }
    
        public override async Task<string?> GetOrNullAsync(string name)
        {
            if (!name.Contains(DiagFilter, StringComparison.OrdinalIgnoreCase))
            {
                return await base.GetOrNullAsync(name);
            }
    
            _logger.LogWarning(
                "[Diag-Feature] ── GetOrNullAsync('{Name}') START, CurrentTenant.Id={TenantId} ──",
                name, _currentTenant.Id?.ToString() ?? "(null/host)");
    
            var featureDefinition = await FeatureDefinitionManager.GetOrNullAsync(name);
            if (featureDefinition == null)
            {
                _logger.LogWarning(
                    "[Diag-Feature] Feature '{Name}' definition NOT FOUND → returning null",
                    name);
                return null;
            }
    
            _logger.LogWarning(
                "[Diag-Feature] Feature '{Name}' found: DefaultValue='{Default}'",
                name, featureDefinition.DefaultValue ?? "(null)");
    
            var providers = FeatureValueProviderManager.ValueProviders.Reverse().ToList();
    
            if (featureDefinition.AllowedProviders.Any())
            {
                providers = providers
                    .Where(p => featureDefinition.AllowedProviders.Contains(p.Name))
                    .ToList();
                _logger.LogWarning(
                    "[Diag-Feature] AllowedProviders filter active: [{Providers}]",
                    string.Join(", ", featureDefinition.AllowedProviders));
            }
    
            _logger.LogWarning(
                "[Diag-Feature] Provider chain (in evaluation order): [{Chain}]",
                string.Join(" → ", providers.Select(p => p.Name + "(" + p.GetType().Name + ")")));
    
            // Manually iterate providers to log each step
            foreach (var provider in providers)
            {
                string? value;
                try
                {
                    value = await provider.GetOrNullAsync(featureDefinition);
                }
                catch (Exception ex)
                {
                    _logger.LogError(ex,
                        "[Diag-Feature] Provider '{Provider}' threw exception!", provider.Name);
                    continue;
                }
    
                if (value != null)
                {
                    _logger.LogWarning(
                        "[Diag-Feature] Provider '{Provider}' ({Type}) returned '{Value}' ← WINNER",
                        provider.Name, provider.GetType().Name, value);
                    return value;
                }
    
                _logger.LogWarning(
                    "[Diag-Feature] Provider '{Provider}' ({Type}) returned null → next",
                    provider.Name, provider.GetType().Name);
            }
    
            _logger.LogWarning(
                "[Diag-Feature] All providers returned null → returning null");
            return null;
        }
    }
    

    Register in your module's ConfigureServices:

    context.Services.AddTransient<IPermissionAppService, DiagnosticPermissionAppService>();
    context.Services.AddTransient<IFeatureChecker, DiagnosticFeatureChecker>();
    

    After deploying, open the Permission Management dialog for the tenant where TourServiceModule is disabled. Then search your logs for [Diag-Perm] and [Diag-Feature].

    The logs will tell us:

    1. Whether the TourModule.TourService feature definition exists in the Administration Service
    2. What value IFeatureChecker returns and which provider (Tenant/Edition/Config/Default) provided it
    3. Whether Tour permissions have RequireFeaturesSimpleStateChecker in their StateCheckers list
    4. Whether StateCheck.IsEnabled returns true or false for each Tour permission
    5. Which Tour permissions end up in the final output

    Please share the log output and we'll identify the root cause.

    Thanks

    Markdown supported.
    Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)
  • User Avatar
    0
    vkelwan created

    Hello ResourcePermissionManager and ResourcePermissionGrantRepository might be do not exit in my abp (9.2.0) version Because I am getting these errors

    Markdown supported.
    Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)
  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    Hi,

    Sorry about that, I provided code targeting a newer ABP version. Here is the corrected version for ABP 9.2.0 — the PermissionAppService constructor in 9.2.0 only has 4 parameters:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading.Tasks;
    using Microsoft.Extensions.Logging;
    using Microsoft.Extensions.Options;
    using Volo.Abp.Authorization.Permissions;
    using Volo.Abp.Features;
    using Volo.Abp.MultiTenancy;
    using Volo.Abp.PermissionManagement;
    using Volo.Abp.SimpleStateChecking;
    
    namespace YourNamespace;
    
    public class DiagnosticPermissionAppService : PermissionAppService
    {
        private readonly IFeatureChecker _featureChecker;
        private readonly IFeatureDefinitionManager _featureDefinitionManager;
    
        public DiagnosticPermissionAppService(
            IPermissionManager permissionManager,
            IPermissionDefinitionManager permissionDefinitionManager,
            IOptions<PermissionManagementOptions> options,
            ISimpleStateCheckerManager<PermissionDefinition> simpleStateCheckerManager,
            IFeatureChecker featureChecker,
            IFeatureDefinitionManager featureDefinitionManager)
            : base(permissionManager, permissionDefinitionManager, options, simpleStateCheckerManager)
        {
            _featureChecker = featureChecker;
            _featureDefinitionManager = featureDefinitionManager;
        }
    
        public override async Task<GetPermissionListResultDto> GetAsync(
            string providerName, string providerKey)
        {
            Logger.LogWarning("[Diag-Perm] ══════ GetAsync START ══════");
    
            Logger.LogWarning(
                "[Diag-Perm] Context: providerName={ProviderName}, providerKey={ProviderKey}, " +
                "CurrentTenant.Id={TenantId}, MultiTenancySide={Side}",
                providerName, providerKey,
                CurrentTenant.Id?.ToString() ?? "(null/host)",
                CurrentTenant.GetMultiTenancySide());
    
            await DiagnoseFeatureAsync("TourModule.TourService");
    
            foreach (var group in await PermissionDefinitionManager.GetGroupsAsync())
            {
                var tourPerms = group.GetPermissionsWithChildren()
                    .Where(p => p.Name.Contains("Tour", StringComparison.OrdinalIgnoreCase))
                    .ToList();
    
                if (!tourPerms.Any())
                {
                    continue;
                }
    
                Logger.LogWarning(
                    "[Diag-Perm] Group '{GroupName}': {Count} Tour permissions found",
                    group.Name, tourPerms.Count);
    
                foreach (var perm in tourPerms)
                {
                    var checkerTypes = perm.StateCheckers
                        .Select(c => c.GetType().Name)
                        .ToList();
    
                    var featureNames = perm.StateCheckers
                        .OfType<RequireFeaturesSimpleStateChecker<PermissionDefinition>>()
                        .SelectMany(c => c.FeatureNames)
                        .ToList();
    
                    bool stateCheckResult;
                    try
                    {
                        stateCheckResult = await SimpleStateCheckerManager.IsEnabledAsync(perm);
                    }
                    catch (Exception ex)
                    {
                        Logger.LogError(ex, "[Diag-Perm] StateChecker EXCEPTION for '{Name}'", perm.Name);
                        stateCheckResult = false;
                    }
    
                    Logger.LogWarning(
                        "[Diag-Perm]   Permission '{Name}': " +
                        "CheckerCount={CheckerCount}, CheckerTypes=[{Checkers}], " +
                        "RequiredFeatures=[{Features}], StateCheck.IsEnabled={IsEnabled}",
                        perm.Name,
                        perm.StateCheckers.Count,
                        checkerTypes.Any() ? string.Join(", ", checkerTypes) : "(none)",
                        featureNames.Any() ? string.Join(", ", featureNames) : "(none)",
                        stateCheckResult);
                }
            }
    
            var result = await base.GetAsync(providerName, providerKey);
    
            var visible = result.Groups
                .SelectMany(g => g.Permissions)
                .Where(p => p.Name.Contains("Tour", StringComparison.OrdinalIgnoreCase))
                .Select(p => p.Name)
                .ToList();
    
            Logger.LogWarning(
                "[Diag-Perm] Final output: {Count} Tour permissions visible: [{Names}]",
                visible.Count,
                visible.Any() ? string.Join(", ", visible) : "(none)");
    
            Logger.LogWarning("[Diag-Perm] ══════ GetAsync END ══════");
    
            return result;
        }
    
        private async Task DiagnoseFeatureAsync(string featureName)
        {
            try
            {
                var featureDef = await _featureDefinitionManager.GetOrNullAsync(featureName);
                if (featureDef == null)
                {
                    Logger.LogWarning(
                        "[Diag-Perm] Feature '{Name}' definition NOT FOUND!", featureName);
                    return;
                }
    
                Logger.LogWarning(
                    "[Diag-Perm] Feature '{Name}': DefaultValue='{Default}', " +
                    "IsAvailableToHost={IsAvailableToHost}, AllowedProviders=[{Providers}]",
                    featureName,
                    featureDef.DefaultValue ?? "(null)",
                    featureDef.IsAvailableToHost,
                    featureDef.AllowedProviders.Any()
                        ? string.Join(", ", featureDef.AllowedProviders)
                        : "(all)");
    
                var rawValue = await _featureChecker.GetOrNullAsync(featureName);
                var isEnabled = await _featureChecker.IsEnabledAsync(featureName);
    
                Logger.LogWarning(
                    "[Diag-Perm] IFeatureChecker for '{Name}': raw='{RawValue}', enabled={IsEnabled}",
                    featureName, rawValue ?? "(null)", isEnabled);
            }
            catch (Exception ex)
            {
                Logger.LogError(ex, "[Diag-Perm] Error checking feature '{Name}'", featureName);
            }
        }
    }
    
    public class DiagnosticFeatureChecker : FeatureChecker
    {
        private readonly ICurrentTenant _currentTenant;
        private readonly ILogger<DiagnosticFeatureChecker> _logger;
    
        private const string DiagFilter = "Tour";
    
        public DiagnosticFeatureChecker(
            IOptions<AbpFeatureOptions> options,
            IServiceProvider serviceProvider,
            IFeatureDefinitionManager featureDefinitionManager,
            IFeatureValueProviderManager featureValueProviderManager,
            ICurrentTenant currentTenant,
            ILogger<DiagnosticFeatureChecker> logger)
            : base(options, serviceProvider, featureDefinitionManager, featureValueProviderManager)
        {
            _currentTenant = currentTenant;
            _logger = logger;
        }
    
        public override async Task<string?> GetOrNullAsync(string name)
        {
            if (!name.Contains(DiagFilter, StringComparison.OrdinalIgnoreCase))
            {
                return await base.GetOrNullAsync(name);
            }
    
            _logger.LogWarning(
                "[Diag-Feature] ── GetOrNullAsync('{Name}'), CurrentTenant.Id={TenantId} ──",
                name, _currentTenant.Id?.ToString() ?? "(null/host)");
    
            var featureDefinition = await FeatureDefinitionManager.GetOrNullAsync(name);
            if (featureDefinition == null)
            {
                _logger.LogWarning(
                    "[Diag-Feature] Feature '{Name}' NOT FOUND → returning null", name);
                return null;
            }
    
            _logger.LogWarning(
                "[Diag-Feature] Feature '{Name}': DefaultValue='{Default}'",
                name, featureDefinition.DefaultValue ?? "(null)");
    
            var providers = FeatureValueProviderManager.ValueProviders.Reverse().ToList();
    
            if (featureDefinition.AllowedProviders.Any())
            {
                providers = providers
                    .Where(p => featureDefinition.AllowedProviders.Contains(p.Name))
                    .ToList();
            }
    
            _logger.LogWarning(
                "[Diag-Feature] Provider chain: [{Chain}]",
                string.Join(" → ", providers.Select(p => p.Name + "(" + p.GetType().Name + ")")));
    
            foreach (var provider in providers)
            {
                string? value;
                try
                {
                    value = await provider.GetOrNullAsync(featureDefinition);
                }
                catch (Exception ex)
                {
                    _logger.LogError(ex,
                        "[Diag-Feature] Provider '{Provider}' threw exception!", provider.Name);
                    continue;
                }
    
                if (value != null)
                {
                    _logger.LogWarning(
                        "[Diag-Feature] Provider '{Provider}' ({Type}) → '{Value}' ← WINNER",
                        provider.Name, provider.GetType().Name, value);
                    return value;
                }
    
                _logger.LogWarning(
                    "[Diag-Feature] Provider '{Provider}' ({Type}) → null",
                    provider.Name, provider.GetType().Name);
            }
    
            _logger.LogWarning("[Diag-Feature] All providers returned null");
            return null;
        }
    }
    

    Register both in your module's ConfigureServices:

    context.Services.AddTransient<IPermissionAppService, DiagnosticPermissionAppService>();
    context.Services.AddTransient<IFeatureChecker, DiagnosticFeatureChecker>();
    

    After deploying, open the Permission Management dialog for the tenant and share the log output (search for [Diag-Perm] and [Diag-Feature]).

    Thanks

    Markdown supported.
    Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)
  • User Avatar
    0
    vkelwan created

    Hi,

    We have deployed the diagnostic services and collected the logs. Please find the detailed diagnostic output attached in the email.

    Thanks

    Markdown supported.
    Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)
  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    Hi,

    Thanks for the detailed logs, they confirmed the root cause clearly.

    The logs show that permissions with .RequireFeatures("TourModule.TourService") are correctly filtered (StateCheck.IsEnabled=False). The ones that were still showing had CheckerCount=0 — meaning they didn't have .RequireFeatures() at all. Glad you already fixed those on your end.

    Regarding your question about EditionFeatureValueProvider returning null:

    This is expected behavior. EditionFeatureValueProvider queries the AbpFeatureValues table for an explicit record with ProviderName='E' and ProviderKey='{editionId}'. If no record exists, it returns null.

    When you open the Feature Management dialog for the "standard" edition, the UI shows the checkbox as unchecked — but that's just displaying the resolved default value ("false" from your code), not a value that was saved to the database. Since you never explicitly changed and saved the feature value for this edition, no record was written to AbpFeatureValues. That's why EditionFeatureValueProvider returns null and the chain falls through to DefaultValueFeatureValueProvider which returns "false".

    The provider chain works like a cascading lookup:

    1. TenantFeatureValueProvider → looks for tenant-specific override in DB → no record → null
    2. EditionFeatureValueProvider → looks for edition-specific override in DB → no record → null
    3. DefaultValueFeatureValueProvider → returns the code-defined default "false" ← used

    If you want the edition to have an explicit value, you'd need to actually change the toggle in the Feature Management UI for that edition and click Save — that creates a record in the database.

    About ABP Suite not adding .RequireFeatures() automatically — we'll discuss this internally as a potential improvement.

    You can now remove the diagnostic file from your project.

    Thanks

    Markdown supported.
    Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)
  • User Avatar
    0
    vkelwan created

    But if the feature tour is not checked, then also get permission?

    Markdown supported.
    Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)
  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    Hi,

    Disabling a feature alone does not automatically hide permissions. Each permission must explicitly declare .RequireFeatures() to be filtered.

    From your diagnostic logs, here's what happens:

    Permission WITH .RequireFeatures() — feature disabled → hidden ✓

    TourService.TourMasterTransferTypes: CheckerCount=1, RequiredFeatures=[TourModule.TourService], IsEnabled=False
    

    Permission WITHOUT .RequireFeatures() — feature disabled → still visible ✗

    TourService.TourMasterRateTypes: CheckerCount=0, RequiredFeatures=(none), IsEnabled=True
    

    The second permission has CheckerCount=0 — it doesn't know about the TourModule.TourService feature at all, so it's always visible regardless of the feature state.

    To fix this, every permission that should be tied to the feature needs .RequireFeatures():

    // This permission will be hidden when feature is disabled ✓
    group.AddPermission("TourService.TourMasterRateTypes", L("..."))
        .RequireFeatures(TMSMSFeature.Tour);
    
    // This permission ignores feature state entirely ✗
    group.AddPermission("TourService.TourMasterRateTypes", L("..."));
    

    There's no group-level or module-level way to apply .RequireFeatures() to all permissions at once — it must be added to each permission individually.

    Thanks

    Markdown supported.
    Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)
  • User Avatar
    0
    vkelwan created

    Okay so once I am done with all, then it will work and hide form permission when I disable that tour feature right?

    Markdown supported.
    Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)
  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    Hi,

    Yes, exactly. Once all Tour permissions have .RequireFeatures(TMSMSFeature.Tour), they will be hidden from the Permission Management UI when the TourModule.TourService feature is disabled.

    Your diagnostic logs already confirmed this — the permissions with CheckerCount=1 were correctly filtered (IsEnabled=False).

    You can remove the diagnostic file from your project now.

    Thanks

    Markdown supported.
    Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)
  • User Avatar
    0
    vkelwan created

    Okay, I will check and update you

    Markdown supported.
    Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)
  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    ok

    Markdown supported.
    Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)
Boost Your Development
ABP Live Training
Packages
See Trainings
Mastering ABP Framework Book
The Official Guide
Mastering
ABP Framework
Learn More
Mastering ABP Framework Book
Made with ❤️ on ABP v10.8.0-preview. Updated on September 16, 2026, 14:50
1
ABP Assistant
🔐 You need to be logged in to use the chatbot. Please log in first.