Activities of "bmulinari"

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

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. 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!

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?

Question

Hi there.

We have identified an inconsistency in the audit logs page (/audit-logs), where the time shown in the log entries is always fixed to the value saved in the database (UTC), so any time zone setting is ignored in this page (host time zone or user account time zone setting).

This does not happen in the security logs (/Account/SecurityLogs) where I can verify that it correctly displays the time based on the time zone setting.

Is this intended or a bug?

For added context, we are currently using the following settings related to the time zone:

  • Npgsql.EnableLegacyTimestampBehavior enabled, since we are using PostgreSQL (as instructed here).
  • AbpClockOptions.Kind set to DateTimeKind.Utc (as instructed here).

Should both of these settings be used at the same time? I just want to ensure we are configuring time zones correctly in the application.

We are having an odd issue with the feature check where a feature can be considered both enabled and disabled depending on where it's checked.

For instance, we have a feature check on the menu contributor to only show the corresponding menu if the feature is enabled:

if (context.Menu.Name == StandardMenus.Main && await featureChecker.IsEnabledAsync(MyDefinitionProvider.MyFeature))
{
    // this correctly shows or hides the menu depending on the feature status
    await ConfigureMainMenuAsync(context);
}

However, when doing the same check on a background worker, sometimes we get a different result:

protected override async Task DoWorkAsync(PeriodicBackgroundWorkerContext workerContext)
{
    await Parallel.ForEachAsync(await TenantStore.GetListAsync(), tenantParallelOptions, async (tenant, ct) =>
    {
        try
        {
            using (CurrentTenant.Change(tenant.Id))
            {
                if (!await featureChecker.IsEnabledAsync(MyDefinitionProvider.MyFeature))
                    return;

                // some logic here that should only execute when the feature is enabled
                // this is where we get the inconsistency, since even with the feature enabled and menu visible, sometimes this is not executed
            }
        }
        catch (Exception ex)
        {
            Logger.LogError(ex, "Error for tenant {TenantId}", tenant.Id);
        }
    });
}

This is a bit convoluted to reproduce, but from what we could gather it has something to do with enabling/disabling the feature for the tenant or edition, and the edition being assigned/unassigned from the tenant.

  • Ensure the feature is enabled in both edition and tenant
  • Unassign the edition from the tenant
  • Reassign the edition to the tenant

This can result in the scenario below where the row that holds the tenant status for the feature is simply deleted from the table. And THIS is when we observe the inconsistency stated above.

I know it's a little confusing, but if necessary we can schedule a call to explain in detail.

Using the module entity extension system, I'm adding a few custom properties to the Plan entity from the Payment Module. That works as expected except for one of the properties, where I need the field in the UI to allow multiple lines (text area), but instead in the UI the field is still rendered as a simple single line text input, not allowing me to input what I need.

According to the documentation:

DataTypeAttribute is used to specify the type of the property. It is used to determine how to render the property on the user interface

So, my understanding was that by setting the data type of my property to DataType.MultilineText as shown below, it should result in a text area field where I can type multiple lines of text, but that is not the case. Am I missing something?

Here's my custom property definition:

ObjectExtensionManager.Instance.Modules()
    .ConfigurePayment(payment =>
    {
        payment.ConfigurePlan(plan => plan
            .AddOrUpdateProperty<string>("Features", property =>
            {
                property.DisplayName = LocalizableString.Create<PaymentGatewayResource>("PlanFeatures");
                property.Attributes.Add(new DataTypeAttribute(DataType.MultilineText));
                property.UI.OnTable.IsVisible = false;
            })
        );
    });
  • Steps to reproduce the issue:
  • Add new custom property to entity as shown in the code above
  • Verify in the UI that the field is a simple input instead of a text area, not allowing the user to input multiple lines of text
Question

Hi there!

Currently, we have implemented a custom branding per tenant by overriding the properties available in the DefaultBrandingProvider. However, this class only has AppName, LogoUrl and LogoReverseUrl, which is the logo displayed on the login screen.

We need two additional overrides for the branding:

  • Icon displayed on top of the side menu
  • Favicon displayed on browser tabs/favorites

Is there a programmatic way to override these?

For the menu icon, we are able to simply replace the icon files in wwwroot, but that doesn't work for our dynamic per tenant branding we want. For the favicon, we are also able to replace the file, but for some reason while on the login page we still see the default LeptonX favicon.

Any suggestions? Thanks.

  • ABP Framework version: v9.0.0
  • UI Type: Blazor Server
  • Database System: EF Core (SQL Server)
  • Steps to reproduce the issue:

Hi! We are having an issue with Entity Framework migrations in our solution which consists of an application and multiple custom modules. Each module has its own DbContext, like in the approach shown here. It's worth pointing out that we are using a separate database for the tenant.

To put it simply, here's the issue we are facing:

  • Module A creates Table_A
  • Migration correctly adds Table_A
  • Module B creates Table_B, which has a relationship to Table_A (one-to-many)
  • Migration incorrectly tries to add Table_A again, in addition to Table_B

This obviously results in the error "Table_A already exists" when running the migrator. I'm not sure what we are doing wrong, but we tried a bunch of different things and the migration for Table_B is always wrong (it shouldn't try to recreate Table_A).

Any help is appreciated. Thanks!

  • ABP Framework version: v8.3.2
  • UI Type: Blazor Server
  • Database System: EF Core (SQL Server)

Hi there! I'm new to ABP and I wanted to ask for advice on what would be the best approach for consuming our API back-end in a Windows Forms app. This app will run on multiple clients but all will be accessing the same API endpoint in our multi-tenant solution.

As far as I could gather, my options are:

  • Dynamic C# API Client Proxies
  • Static C# API Client Proxies
  • HttpClient with IIdentityModelAuthenticationService for authentication
  • HttpClient with IdentityModel for OIDC discovery and authentication

Which one do you suggest that would fit my scenario the best? Due to my multi-tenant solution, in all of the approaches above I would need to manually specify the current tenant, is that correct?

Showing 1 to 10 of 12 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 February 17, 2026, 09:10
1
ABP Assistant
🔐 You need to be logged in to use the chatbot. Please log in first.