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.
13 Answer(s)
-
0
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 settingIsEnabled = falsefor 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
IFeatureCheckerand excludes permissions that require disabled features.
- Practically, this means customizing the permission definition phase (e.g.,
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).
- There is no built-in ABP Suite setting that automatically appends
(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) -
0
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) -
0
Hi,
.RequireFeatures()on permissions should hide them from the Permission Management UI when the feature is disabled. ThePermissionAppService.GetInternalAsyncchecks each permission's state checkers (includingRequireFeaturesSimpleStateChecker) 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:
- Whether the
TourModule.TourServicefeature definition exists in the Administration Service - What value
IFeatureCheckerreturns and which provider (Tenant/Edition/Config/Default) provided it - Whether Tour permissions have
RequireFeaturesSimpleStateCheckerin their StateCheckers list - Whether
StateCheck.IsEnabledreturns true or false for each Tour permission - 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) - Whether the
-
0
Hi,
Sorry about that, I provided code targeting a newer ABP version. Here is the corrected version for ABP 9.2.0 — the
PermissionAppServiceconstructor 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) -
0
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 hadCheckerCount=0— meaning they didn't have.RequireFeatures()at all. Glad you already fixed those on your end.Regarding your question about
EditionFeatureValueProviderreturning null:This is expected behavior.
EditionFeatureValueProviderqueries theAbpFeatureValuestable for an explicit record withProviderName='E'andProviderKey='{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 toAbpFeatureValues. That's whyEditionFeatureValueProviderreturns null and the chain falls through toDefaultValueFeatureValueProviderwhich returns"false".The provider chain works like a cascading lookup:
- TenantFeatureValueProvider → looks for tenant-specific override in DB → no record → null
- EditionFeatureValueProvider → looks for edition-specific override in DB → no record → null
- 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) -
0
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=FalsePermission WITHOUT
.RequireFeatures()— feature disabled → still visible ✗TourService.TourMasterRateTypes: CheckerCount=0, RequiredFeatures=(none), IsEnabled=TrueThe second permission has
CheckerCount=0— it doesn't know about theTourModule.TourServicefeature 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) -
0
Hi,
Yes, exactly. Once all Tour permissions have
.RequireFeatures(TMSMSFeature.Tour), they will be hidden from the Permission Management UI when theTourModule.TourServicefeature is disabled.Your diagnostic logs already confirmed this — the permissions with
CheckerCount=1were 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)

