Activities of "bmulinari"

Awesome, thank you.

Hi there. Any updates on this?

There seems to be a bug in the way permissions are checked on toolbar buttons when the required policy is specified in the AddButton method, where we will get a spam of unnecessarily permissions checks on every minimal interaction with the page, like on every key press, for instance.

  • Steps to reproduce the issue:
  • While in debug mode, navigate to Administration -> Identity management -> Users (just an example, this seems to occur on any page with toolbar buttons)
  • Click New User or Edit on an existing user and start typing in any field
  • Verify in the console output that for every character typed a set of permission checks happen:
[15:26:29 DBG] PermissionStore.GetCacheItemAsync: pn:U,pk:3a1beb3d-8133-1e8d-48ee-2869932f2b57,n:AbpIdentity.Users.Import
[15:26:29 DBG] Found in the cache: pn:U,pk:3a1beb3d-8133-1e8d-48ee-2869932f2b57,n:AbpIdentity.Users.Import
[15:26:29 DBG] PermissionStore.GetCacheItemAsync: pn:R,pk:admin,n:AbpIdentity.Users.Import
[15:26:29 DBG] Found in the cache: pn:R,pk:admin,n:AbpIdentity.Users.Import
[15:26:29 DBG] PermissionStore.GetCacheItemAsync: pn:U,pk:3a1beb3d-8133-1e8d-48ee-2869932f2b57,n:AbpIdentity.Users.Export
[15:26:29 DBG] Found in the cache: pn:U,pk:3a1beb3d-8133-1e8d-48ee-2869932f2b57,n:AbpIdentity.Users.Export
[15:26:29 DBG] PermissionStore.GetCacheItemAsync: pn:R,pk:admin,n:AbpIdentity.Users.Export
[15:26:29 DBG] Found in the cache: pn:R,pk:admin,n:AbpIdentity.Users.Export
[15:26:29 DBG] PermissionStore.GetCacheItemAsync: pn:U,pk:3a1beb3d-8133-1e8d-48ee-2869932f2b57,n:AbpIdentity.Users.Create
[15:26:29 DBG] Found in the cache: pn:U,pk:3a1beb3d-8133-1e8d-48ee-2869932f2b57,n:AbpIdentity.Users.Create
[15:26:29 DBG] PermissionStore.GetCacheItemAsync: pn:R,pk:admin,n:AbpIdentity.Users.Create
[15:26:29 DBG] Found in the cache: pn:R,pk:admin,n:AbpIdentity.Users.Create
[15:26:29 DBG] PermissionStore.GetCacheItemAsync: pn:U,pk:3a1beb3d-8133-1e8d-48ee-2869932f2b57,n:AbpIdentity.Users.Import
[15:26:29 DBG] Found in the cache: pn:U,pk:3a1beb3d-8133-1e8d-48ee-2869932f2b57,n:AbpIdentity.Users.Import
[15:26:29 DBG] PermissionStore.GetCacheItemAsync: pn:R,pk:admin,n:AbpIdentity.Users.Import
[15:26:29 DBG] Found in the cache: pn:R,pk:admin,n:AbpIdentity.Users.Import
[15:26:29 DBG] PermissionStore.GetCacheItemAsync: pn:U,pk:3a1beb3d-8133-1e8d-48ee-2869932f2b57,n:AbpIdentity.Users.Export
[15:26:29 DBG] Found in the cache: pn:U,pk:3a1beb3d-8133-1e8d-48ee-2869932f2b57,n:AbpIdentity.Users.Export
[15:26:29 DBG] PermissionStore.GetCacheItemAsync: pn:R,pk:admin,n:AbpIdentity.Users.Export
[15:26:29 DBG] Found in the cache: pn:R,pk:admin,n:AbpIdentity.Users.Export
[15:26:29 DBG] PermissionStore.GetCacheItemAsync: pn:U,pk:3a1beb3d-8133-1e8d-48ee-2869932f2b57,n:AbpIdentity.Users.Create
[15:26:29 DBG] Found in the cache: pn:U,pk:3a1beb3d-8133-1e8d-48ee-2869932f2b57,n:AbpIdentity.Users.Create
[15:26:29 DBG] PermissionStore.GetCacheItemAsync: pn:R,pk:admin,n:AbpIdentity.Users.Create
[15:26:29 DBG] Found in the cache: pn:R,pk:admin,n:AbpIdentity.Users.Create
[15:26:29 DBG] PermissionStore.GetCacheItemAsync: pn:U,pk:3a1beb3d-8133-1e8d-48ee-2869932f2b57,n:AbpIdentity.Users
[15:26:29 DBG] Found in the cache: pn:U,pk:3a1beb3d-8133-1e8d-48ee-2869932f2b57,n:AbpIdentity.Users
[15:26:29 DBG] PermissionStore.GetCacheItemAsync: pn:R,pk:admin,n:AbpIdentity.Users
[15:26:29 DBG] Found in the cache: pn:R,pk:admin,n:AbpIdentity.Users
[15:26:29 DBG] PermissionStore.GetCacheItemAsync: pn:U,pk:3a1beb3d-8133-1e8d-48ee-2869932f2b57,n:AbpIdentity.Users.Import
[15:26:29 DBG] Found in the cache: pn:U,pk:3a1beb3d-8133-1e8d-48ee-2869932f2b57,n:AbpIdentity.Users.Import
[15:26:29 DBG] PermissionStore.GetCacheItemAsync: pn:R,pk:admin,n:AbpIdentity.Users.Import
[15:26:29 DBG] Found in the cache: pn:R,pk:admin,n:AbpIdentity.Users.Import
[15:26:29 DBG] PermissionStore.GetCacheItemAsync: pn:U,pk:3a1beb3d-8133-1e8d-48ee-2869932f2b57,n:AbpIdentity.Users.Export
[15:26:29 DBG] Found in the cache: pn:U,pk:3a1beb3d-8133-1e8d-48ee-2869932f2b57,n:AbpIdentity.Users.Export
[15:26:29 DBG] PermissionStore.GetCacheItemAsync: pn:R,pk:admin,n:AbpIdentity.Users.Export
[15:26:29 DBG] Found in the cache: pn:R,pk:admin,n:AbpIdentity.Users.Export
[15:26:29 DBG] PermissionStore.GetCacheItemAsync: pn:U,pk:3a1beb3d-8133-1e8d-48ee-2869932f2b57,n:AbpIdentity.Users.Create
[15:26:29 DBG] Found in the cache: pn:U,pk:3a1beb3d-8133-1e8d-48ee-2869932f2b57,n:AbpIdentity.Users.Create
[15:26:29 DBG] PermissionStore.GetCacheItemAsync: pn:R,pk:admin,n:AbpIdentity.Users.Create
[15:26:29 DBG] Found in the cache: pn:R,pk:admin,n:AbpIdentity.Users.Create
[15:26:29 DBG] PermissionStore.GetCacheItemAsync: pn:U,pk:3a1beb3d-8133-1e8d-48ee-2869932f2b57,n:AbpIdentity.Users.Import
[15:26:29 DBG] Found in the cache: pn:U,pk:3a1beb3d-8133-1e8d-48ee-2869932f2b57,n:AbpIdentity.Users.Import
[15:26:29 DBG] PermissionStore.GetCacheItemAsync: pn:R,pk:admin,n:AbpIdentity.Users.Import
[15:26:29 DBG] Found in the cache: pn:R,pk:admin,n:AbpIdentity.Users.Import
[15:26:29 DBG] PermissionStore.GetCacheItemAsync: pn:U,pk:3a1beb3d-8133-1e8d-48ee-2869932f2b57,n:AbpIdentity.Users.Export
[15:26:29 DBG] Found in the cache: pn:U,pk:3a1beb3d-8133-1e8d-48ee-2869932f2b57,n:AbpIdentity.Users.Export
[15:26:29 DBG] PermissionStore.GetCacheItemAsync: pn:R,pk:admin,n:AbpIdentity.Users.Export
[15:26:29 DBG] Found in the cache: pn:R,pk:admin,n:AbpIdentity.Users.Export
[15:26:29 DBG] PermissionStore.GetCacheItemAsync: pn:U,pk:3a1beb3d-8133-1e8d-48ee-2869932f2b57,n:AbpIdentity.Users.Create
[15:26:29 DBG] Found in the cache: pn:U,pk:3a1beb3d-8133-1e8d-48ee-2869932f2b57,n:AbpIdentity.Users.Create
[15:26:29 DBG] PermissionStore.GetCacheItemAsync: pn:R,pk:admin,n:AbpIdentity.Users.Create
[15:26:29 DBG] Found in the cache: pn:R,pk:admin,n:AbpIdentity.Users.Create
[15:26:32 DBG] Get dynamic claims cache for user: 3a1beb3d-8133-1e8d-48ee-2869932f2b57
  • Workaround we found for our own custom pages:

Default implementation where we observe the problem above, with the required permissions being specified as a parameter of the AddButton method:

private ValueTask SetToolbarItemsAsync()
{
    Toolbar.AddButton(L["Page:Button:ButtonA"],
        async () =>
        {
            await SomeActionAsync();
        },
        IconName.Sync,
        Blazorise.Color.Primary,
        requiredPolicyName: MyPermissions.MyPage.Default);

    Toolbar.AddButton(L["Page:Button:ButtonB"],
        async () =>
        {
            await SomeOtherActionAsync();
        },
        IconName.Download,
        Blazorise.Color.Secondary,
        requiredPolicyName: MyPermissions.MyPage.Default);

    return ValueTask.CompletedTask;
}

As a workaround, when we wrap the permission check around the AddButton method while removing it as a parameter, we no longer observe the permission check spam:

private async ValueTask SetToolbarItemsAsync()
{
    if (await PermissionChecker.IsGrantedAsync(MyPermissions.MyPage.Default))
    {
        Toolbar.AddButton(L["Page:Button:ButtonA"],
            async () =>
            {
                await SomeActionAsync();
            },
            IconName.Sync,
            Color.Primary);
        Toolbar.AddButton(L["Page:Button:ButtonB"],
            async () =>
            {
                await SomeOtherActionAsync();
            },
            IconName.Download,
            Color.Secondary);
    }
}

[maliming] said: hi

the distributed handler creates a separate UoW to run the migrations in with _unitOfWorkManager.Begin(requiresNew: true), which queries the database before the original UoW commits. Result: tenantConfiguration.ConnectionStrings.Default is null, so migrations don't run when they should.

The TenantCreatedEto event should be published when a tenant is created. Which means the new tenant is already in the database.

Thanks.

As I understand, the tenant technically is already in the database, but the transaction is not yet committed at that point, correct? Since if my TenantCreatedEto handler throws, the transaction is rolled back and it's like the entity was never added.

So, since the transaction is not yet committed, when MyAppTenantDatabaseMigrationHandler starts a completely new UoW with requiresNew=true before applying the migrations, it will not "see" the connection string I just set to the tenant in the previous handler, causing the migrations to not be applied. That is the problem.

We are testing a service to automatically provision the database and user for a tenant whenever a tenant is created, ensuring proper tenant isolation at a database level. Although the provisioning is working as we expect, the events that should be triggered after a tenant is created are not.

The current implementation consists of two handlers that listen to TenantCreatedEto:

1. Local handler (order -1): Runs first. Provisions database and sets connection string to the new tenant:

// Inside TenantDatabaseProvisioner.ProvisionAsync()
await CreateDatabaseAsync(hostConnectionString, databaseName);
await CreateUserAsync(hostConnectionString, userName, password);
await GrantPrivilegesAsync(hostConnectionString, databaseName, userName);

var tenantConnectionString = BuildTenantConnectionString(hostConnectionString, databaseName, userName, password);

var tenant = await tenantRepository.FindByIdAsync(tenantId);
tenant.SetDefaultConnectionString(tenantConnectionString);
await tenantRepository.UpdateAsync(tenant, true);  // Still within the original UoW

2. Default MyAppTenantDatabaseMigrationHandler: This is default ABP code to run migrations/seeding:

private async Task MigrateAndSeedForTenantAsync(Guid tenantId, string adminEmail, string adminPassword)
{
    using (_currentTenant.Change(tenantId))
    {
        // Creates a NEW Unit of Work
        using (var uow = _unitOfWorkManager.Begin(requiresNew: true, isTransactional: false))
        {
            var tenantConfiguration = await _tenantStore.FindAsync(tenantId);

            // THIS CHECK FAILS - configuration/connection string is still null here, so migrations don't run
            if (tenantConfiguration?.ConnectionStrings != null &&
                !tenantConfiguration.ConnectionStrings.Default.IsNullOrWhiteSpace())
            {
                foreach (var migrator in _dbSchemaMigrators)
                {
                    await migrator.MigrateAsync();
                }
            }
            await uow.CompleteAsync();
        }

        // Seed data
        // ...
    }
}

Problem: the distributed handler creates a separate UoW to run the migrations in with _unitOfWorkManager.Begin(requiresNew: true), which queries the database before the original UoW commits. Result: tenantConfiguration.ConnectionStrings.Default is null, so migrations don't run when they should.

  • Are there built-in mechanisms for this tenant database provisioning scenario?
  • What would be the recommended approach for running migrations when the connection string is set dynamically during tenant creation?

Thanks!

Hi there.

Any way to get this behavior in the permissions modal?

Hi there. In our application we implemented a page that utilizes the default ABP app services IIdentityUserAppService and IIdentityRoleAppService to list users and roles. Therefore, this page requires the permissions "AbpIdentity.Users" and "AbpIdentity.Roles" to work correctly.

So, to avoid unexpected permission errors when setting up the roles for our application, we need to implement a proper permission dependency when defining our own permissions, as explained in the documentation here and here.

With this in mind, we added the dependencies to our permission definition:

public class MyModulePermissionDefinitionProvider : PermissionDefinitionProvider
{
    public override void Define(IPermissionDefinitionContext context)
    {
        var myModuleGroupDefinition = context.AddGroup(MyModulePermissions.GroupName, L("Permission:MyModule"));
        var myModuleGroup = myModuleGroupDefinition.AddPermission(MyModulePermissions.Home, L("Permission:MyModule:Home"), MultiTenancySides.Tenant);

        var reportsPermissions = myModuleGroup.AddChild(MyModulePermissions.Reports.Default, L("Permission:MyModule:Reports"), MultiTenancySides.Tenant);
        reportsPermissions.AddChild(MyModulePermissions.Reports.ReportManagement, L("Permission:MyModule:Reports:ReportManagement"), MultiTenancySides.Tenant)
            .RequirePermissions("AbpIdentity.Users", "AbpIdentity.Roles"); // this is what we added
    }

    private static LocalizableString L(string name)
    {
        return LocalizableString.Create<MyModuleResource>(name);
    }
}

What I expected to happen: for my custom permission MyModulePermissions.Reports.ReportManagement to always require the permissions "AbpIdentity.Users" and "AbpIdentity.Roles", in a way that they should be automatically selected in the permissions pop-up when my custom permission is selected, or at least give me an error and not allow me to save until the dependent permissions are also selected, making the permission dependencies clear and mandatory when setting up the permissions for users or roles.

What actually happens: in the permissions pop-up I can select my custom permission MyModulePermissions.Reports.ReportManagement and save without any action or feedback that my custom permission depends on other permissions. From my tests, all RequirePermissions does is consider my custom permission not granted unless the other required permissions are also granted, which is very confusing since my menu entry won't appear for the user even when they have that permission granted (but not its dependencies).

Am I missing something or is this the expected behavior? If so, is there a way to implement permission dependencies in the way I described?

Thanks!

I appreciate the AI-generated response, but I'd like to hear the ABP team's input on this matter.

Thanks!

Hi,

From what I could find in the documentation and in this article, bundling JavaScript or CSS files is only mentioned for Blazor WebAssembly, no mention of Blazor Server. From my tests the BundleContributor approach does not seem to work for Blazor Server.

For now the workaround has been collocated JavaScript files with specific components.

Does ABP offer any way to do file bundling in Blazor Server? What would be the correct approach?

I have verified that adding UseAbpTimeZone to my application initialization does seem to solve the issue in the /audit-logs page and it now shows the correct local time:

Still, it's not clear to me when to have UseAbpTimeZone or not (I couldn't find any documentation on it besides your article).

The inconsistency shown in my previous post does seem like a bug... 🤔

Showing 1 to 10 of 34 entries
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.2.0-preview. Updated on January 30, 2026, 08:56
1
ABP Assistant
🔐 You need to be logged in to use the chatbot. Please log in first.