Activities of "maliming"

Hi,

Thanks for the detailed report. We've fixed the template issues, including the multi-tenancy ones: AppUser, Book and Author are now tenant-aware in the generated code. The fix will be in a coming ABP Studio release. The LowCode test issue is in the LowCode module, and we've passed it to the LowCode team.

There are two things you can apply to your current solution now.

1. LowCode test isolation

Other LowCode services also subscribe to DynamicModelManager.OnModelChanged, not only AbpLowCodeApplicationModule. Resetting the backing field to a no-op also removes the handler the manager adds for itself. This version keeps that handler and removes the rest. Put it in BinyatTestModule:

using System;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using Volo.Abp.LowCode.Modeling;

public override void OnApplicationShutdown(ApplicationShutdownContext context)
{
    _database?.Dispose();
    RemoveLowCodeModelChangedHandlers();
}

private static void RemoveLowCodeModelChangedHandlers()
{
    var manager = DynamicModelManager.Instance;
    var handlers = typeof(DynamicModelManager)
        .GetFields(BindingFlags.Instance | BindingFlags.NonPublic)
        .Single(f => f.FieldType == typeof(Func<Task>))
        .GetValue(manager) as Func<Task>;

    if (handlers == null)
    {
        return;
    }

    foreach (var handler in handlers.GetInvocationList().Cast<Func<Task>>())
    {
        if (!ReferenceEquals(handler.Target, manager))
        {
            manager.OnModelChanged -= handler;
        }
    }
}

2. AppUser tenant isolation

The options.EtoMappings.Add<IdentityUser, AppIdentityUserEto>() line in BinyatModule replaces Identity's own IdentityUser to UserEto mapping. So besides the missing TenantId, the UserEto events are never published, and the permission grants of a deleted user are not cleaned up. To fix it:

  • Remove that EtoMappings.Add line, the AppIdentityUserEto class and its mapper in BinyatMappers.
  • Change AppUserSyncHandler to handle EntityCreatedEto<UserEto>, EntityUpdatedEto<UserEto> and EntityDeletedEto<UserEto>. Volo.Abp.Users.UserEto already has TenantId.
  • Make AppUser multi-tenant, and pass TenantId when you create it in AppUserSyncHandler (identityUser.TenantId from UserEto) and in AppUserSeedBootstrapper:
public class AppUser : AuditedAggregateRoot<Guid>, IMultiTenant
{
    public Guid? TenantId { get; private set; }

    public AppUser(
        Guid id,
        string userName,
        string? email,
        string? name,
        string? surname,
        string? phoneNumber,
        bool isActive,
        Guid? tenantId)
        : base(id)
    {
        TenantId = tenantId;
        UserName = Check.NotNullOrWhiteSpace(userName, nameof(userName), maxLength: AppUserConsts.MaxUserNameLength);
        Update(email, name, surname, phoneNumber, isActive);
    }

    // ...
}
  • Add a migration for the new column, and fill TenantId of the existing rows in the same migration. Without this, the existing tenant users stay under the host, and the tenant data seeding fails with a duplicate key error:
migrationBuilder.AddColumn<Guid>(
    name: "TenantId",
    table: "AppUsers",
    type: "uuid",
    nullable: true);

migrationBuilder.Sql(
    @"UPDATE ""AppUsers"" AS a SET ""TenantId"" = u.""TenantId"" FROM ""AbpUsers"" AS u WHERE u.""Id"" = a.""Id"";");
  • Update AppUserSyncHandler_Tests to use UserEto instead of AppIdentityUserEto.

Thanks

Hi,

1. Example applications on demo.abp.io

The example applications on the demo site are custom code written only for the demo. They aren't part of the startup templates or the modules, and we don't share that source code, so we can't send it by email either. For learning, you can check the sample applications here: https://abp.io/docs/latest/samples

2. The Order and OrderLineItem entities

There are two problems in the code:

  • Add [DynamicEntity] to both classes so Low-Code can discover them. [DynamicEntityUI] only sets the display name.
  • The parent-child relation is defined on the child, with [DynamicEntity(typeof(Order))]. Low-Code adds the OrderId property to the child for you. A collection property like List<OrderLineItem> Items isn't supported, so please remove it.

[DynamicForeignKey] is for lookup fields, for example a ProductId that points to a Product entity. Its second parameter is the property shown in the dropdown (like "Name"), not the foreign key name.

Here are the corrected classes:

using System;
using System.ComponentModel.DataAnnotations;
using Volo.Abp.LowCode;
using Volo.Abp.LowCode.Entities;

namespace DemoApp.Orders;

[DynamicEntity(DefaultDisplayPropertyName = nameof(OrderNumber))]
[DynamicEntityUI("Orders")]
public class Order : DynamicEntityBase
{
    [Required]
    [MaxLength(32)]
    [DynamicPropertyUnique]
    public string OrderNumber { get; set; } = null!;

    [DynamicPropertyType(EntityPropertyType.Date)]
    public DateOnly OrderDate { get; set; }

    [MaxLength(128)]
    public string? CustomerName { get; set; }
}

[DynamicEntity(typeof(Order), DefaultDisplayPropertyName = nameof(ProductName))]
[DynamicEntityUI("Order Line Items")]
public class OrderLineItem : DynamicEntityBase
{
    [Required]
    [MaxLength(128)]
    public string ProductName { get; set; } = null!;

    public int Quantity { get; set; }

    [DynamicPropertyType(EntityPropertyType.Money)]
    public decimal UnitPrice { get; set; }
}

Since these are C# classes, they also need to be mapped in your DbContext, like any other EF Core entity. Use your existing DbContext (the one that already calls ConfigureDynamicEntities() and ConfigureLowCode()) and keep everything else in it as is.

Add the DbSet properties:

public DbSet<Order> Orders { get; set; }
public DbSet<OrderLineItem> OrderLineItems { get; set; }

And add the mappings at the end of your existing OnModelCreating method. DbTablePrefix and DbSchema are the ones already defined in your project:

builder.Entity<Order>(b =>
{
    b.ToTable(DbTablePrefix + "Orders", DbSchema);
    b.ConfigureByConvention();
    b.Property(x => x.OrderNumber).IsRequired().HasMaxLength(32);
    b.Property(x => x.CustomerName).HasMaxLength(128);
});

builder.Entity<OrderLineItem>(b =>
{
    b.ToTable(DbTablePrefix + "OrderLineItems", DbSchema);
    b.ConfigureByConvention();
    b.Property(x => x.ProductName).IsRequired().HasMaxLength(128);
    b.Property(x => x.UnitPrice).HasColumnType("decimal(18,2)");
});

Then add a new EF Core migration and apply it to the database. The OrderId column is created in the OrderLineItems table automatically.

3. Order and line items on one form

The Low-Code System is still in preview, and its forms can't show an editable line items grid inside the order form yet.

What works now: create the order first, then add its line items from the order row. Low-Code shows the child entity as a related grid for each order. Click Actions > Order Line Items on an order row, and use New in that dialog to add lines. The OrderId is filled in automatically.

Here is the order-form.json and the page for the order itself. Put them under the _Dynamic/model/forms/ and _Dynamic/model/pages/ folders of your project. In a layered solution _Dynamic is in the Domain project, and in a single-layer solution it's in the Host project:

_Dynamic/model/forms/order-form.json

{
  "name": "order-form",
  "entityName": "DemoApp.Orders.Order",
  "fields": [
    { "id": "order-number", "label": "Order Number", "type": "text", "binding": "OrderNumber" },
    { "id": "order-date", "label": "Order Date", "type": "date", "binding": "OrderDate" },
    { "id": "customer-name", "label": "Customer", "type": "text", "binding": "CustomerName" }
  ],
  "layout": {
    "tabs": [
      {
        "id": "main",
        "title": "Order",
        "isDefault": true,
        "groups": [
          {
            "id": "header",
            "title": "Order Info",
            "isDefault": true,
            "fields": [
              { "fieldId": "order-number", "row": 0, "colSpan": 2, "colStart": 1 },
              { "fieldId": "order-date", "row": 0, "colSpan": 2, "colStart": 3 },
              { "fieldId": "customer-name", "row": 1, "colSpan": 4 }
            ]
          }
        ]
      }
    ]
  },
  "rules": []
}

_Dynamic/model/pages/orders.json

{
  "name": "orders",
  "title": "Orders",
  "icon": "fa-solid fa-cart-shopping",
  "type": "dataGrid",
  "entityName": "DemoApp.Orders.Order",
  "createFormName": "order-form",
  "editFormName": "order-form",
  "columns": [
    { "propertyName": "OrderNumber", "order": 0, "visible": true },
    { "propertyName": "OrderDate", "order": 1, "visible": true },
    { "propertyName": "CustomerName", "order": 2, "visible": true }
  ]
}

Replace DemoApp.Orders.Order with the full name of your Order class. You can check the files with:

dotnet run --project <your-host-project> -- --check-lowcode-model-files

Documentation:

  • https://abp.io/docs/latest/low-code
  • https://abp.io/docs/latest/low-code/fluent-api
  • https://abp.io/docs/latest/low-code/model-json

Thanks

Hi,

If the user changes the password on the My account page, on the host or in any tenant, the new password is synced to all four records. So changing it in Tenant C also updates the host, Tenant A and Tenant B. Your account has a host record, so the next login checks the password against it and the new password works.

The "current password" on that page is checked against the record you are signed in to. In Tenant C, it's the Tenant C password. If a tenant admin set a different password there before, enter that one. After the change, all four records have the same password again.

Name, surname and phone number aren't synced, even from My account. They're kept separately for the host and each tenant. Only the username, email and password are shared.

Thanks

Hi,

This is a bug. The workspace configuration treats an empty EmbedderProvider as configured, and the Workspaces page saves empty fields as ''.

For now, you can add this class to your Application project (it runs in the HttpApi.Host in your tiered solution). It saves empty providers as NULL when a workspace is created, updated or duplicated:

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Repositories;
using Volo.AIManagement.ApplicationWorkspaceProviders;
using Volo.AIManagement.McpServers;
using Volo.AIManagement.VectorStores;
using Volo.AIManagement.WorkspaceDataSources;
using Volo.AIManagement.Workspaces;

namespace MyCompanyName.MyProjectName;

[Dependency(ReplaceServices = true)]
[ExposeServices(typeof(IWorkspaceAppService), typeof(WorkspaceAppService))]
public class MyWorkspaceAppService : WorkspaceAppService
{
    public MyWorkspaceAppService(
        IWorkspaceRepository repository,
        IRepository<ApplicationAIProvider, Guid> applicationWorkspaceProviderRepository,
        IMcpServerRepository mcpServerRepository,
        ApplicationWorkspaceManager applicationWorkspaceManager,
        VectorStoreInitializer vectorStoreInitializer,
        IEnumerable<IVectorStoreFactory> vectorStoreFactories,
        IWorkspaceDataSourceAppService workspaceDataSourceAppService)
        : base(
            repository,
            applicationWorkspaceProviderRepository,
            mcpServerRepository,
            applicationWorkspaceManager,
            vectorStoreInitializer,
            vectorStoreFactories,
            workspaceDataSourceAppService)
    {
    }

    public override Task<WorkspaceDetailDto> CreateAsync(WorkspaceCreateInput input)
    {
        input.EmbedderProvider = NullIfWhiteSpace(input.EmbedderProvider);
        input.VectorStoreProvider = NullIfWhiteSpace(input.VectorStoreProvider);
        return base.CreateAsync(input);
    }

    public override async Task<WorkspaceDetailDto> UpdateAsync(Guid id, WorkspaceUpdateInput input)
    {
        input.EmbedderProvider = NullIfWhiteSpace(input.EmbedderProvider);
        input.VectorStoreProvider = NullIfWhiteSpace(input.VectorStoreProvider);

        // Normalize the stored values first, so '' -> NULL is not treated as an embedder change
        var workspace = await Repository.GetAsync(id);
        var embedderProvider = NullIfWhiteSpace(workspace.EmbedderProvider);
        var vectorStoreProvider = NullIfWhiteSpace(workspace.VectorStoreProvider);
        if (workspace.EmbedderProvider != embedderProvider || workspace.VectorStoreProvider != vectorStoreProvider)
        {
            workspace.EmbedderProvider = embedderProvider;
            workspace.VectorStoreProvider = vectorStoreProvider;
            await Repository.UpdateAsync(workspace, autoSave: true);
        }

        return await base.UpdateAsync(id, input);
    }

    public override async Task<WorkspaceDetailDto> DuplicateAsync(Guid id, WorkspaceDuplicateInput input)
    {
        var result = await base.DuplicateAsync(id, input);
        await CurrentUnitOfWork!.SaveChangesAsync();

        var workspace = await Repository.GetAsync(result.Id);
        workspace.EmbedderProvider = NullIfWhiteSpace(workspace.EmbedderProvider);
        workspace.VectorStoreProvider = NullIfWhiteSpace(workspace.VectorStoreProvider);
        await Repository.UpdateAsync(workspace);

        return ObjectMapper.Map<Workspace, WorkspaceDetailDto>(workspace);
    }

    private static string? NullIfWhiteSpace(string? value)
    {
        return string.IsNullOrWhiteSpace(value) ? null : value;
    }
}

After you deploy it:

  1. Open each workspace that has no embedder in the UI and save it once. This changes the stored '' values to NULL.
  2. Remove the cached workspace configurations, including the entries cached for tenants. Run this against the Redis server your applications use (add -h, -p, -n and -a to both redis-cli calls as needed):
redis-cli --scan --pattern '*Volo.AIManagement.Factory.ChatClientCreationConfiguration*' | while IFS= read -r key; do redis-cli del "$key"; done

If you use the default in-memory cache instead of Redis, skip this step. The restart in the next step clears it.

  1. Restart your applications (HttpApi.Host and Blazor), so no running chat client keeps the old configuration.

After that, the search_documents tool is no longer added for these workspaces, and later edits in the UI keep the values as NULL.

We'll also fix this in the AI Management module.

Thanks

Hi,

No, it isn't synced. When a tenant admin sets the password on the Users page, only that tenant's record changes. The host record and the other tenants keep the old password.

Since the user signs in with the host record, the new password won't work at login, and the old one still does. The same happens when a host admin sets the password: only the host record changes.

The only way that syncs today is when the user changes the password on the My account page. That updates the host record and all tenant records. I need to correct my earlier answer here: "password changes stay in sync" is only true for this case.

We'll improve the scenarios you ran into as soon as possible.

Thanks

Hi,

The MudBlazor UI of AI Management is missing these parts. The backend already supports them, only the pages were not added. We have added the MCP server connections and the Embedding / Vector Store tabs to the MudBlazor pages, and they will be in the 10.7.0 release. Your ticket has been refunded.

Until then, you can configure them with the HTTP API, for example from the Swagger UI of your HttpApi.Host.

Embedding and Vector Store

  1. Get the workspace with GET /api/ai-management/workspaces/by-name/{workspaceName}.
  2. Copy the whole response, set the fields below, and send it to PUT /api/ai-management/workspaces/{id}. Keep the other fields as they are. The update saves every field it gets, so a missing field such as apiKey is saved as empty.
{
  "embedderProvider": "OpenAI",
  "embedderModelName": "text-embedding-3-small",
  "embedderApiKey": "<your-api-key>",
  "vectorStoreProvider": "Pgvector",
  "vectorStoreSettings": "Host=<host>;Port=5432;Database=<database>;Username=<user>;Password=<password>"
}

GET /api/ai-management/workspaces/embedder-providers and GET /api/ai-management/workspaces/vector-store-providers return the provider names you can use.

If the Pgvector database doesn't exist yet, add Persist Security Info=true to the connection string. Otherwise creating the database fails with "No password has been provided".

MCP servers

  1. Get the MCP server id with GET /api/ai-management/mcp-servers/by-name/{mcpServerName}.
  2. Connect it with POST /api/ai-management/workspaces/{workspaceId}/mcp-servers:
{
  "mcpServerId": "<mcp-server-id>",
  "isEnabled": true
}

GET /api/ai-management/workspaces/{workspaceId}/mcp-servers lists the connected servers, and DELETE /api/ai-management/workspaces/{workspaceId}/mcp-servers/{mcpServerId} removes one.

Thanks

Hi,

Your app won't stop because of ABP. The ABP Azure Service Bus integration (Volo.Abp.AzureServiceBus and Volo.Abp.EventBus.Azure) uses the new Azure.Messaging.ServiceBus SDK. That's the same in 7.0 and 10.0. It doesn't use any of the retired packages.

The Microsoft.Azure.ServiceBus 4.2.1 reference you found doesn't come from ABP. It's in your own project. It will still work after 30 September, because Microsoft only stops support and updates for these libraries. But it won't get fixes anymore, so it's better to replace it:

  • If nothing in your code uses it, just remove the reference.
  • If your code uses types like QueueClient, TopicClient, MessageReceiver or Message, add the new package and move that code to ServiceBusClient, ServiceBusSender, ServiceBusProcessor and ServiceBusMessage. Changing only the package name won't compile.
<PackageReference Include="Azure.Messaging.ServiceBus" Version="7.21.0" />

Microsoft's migration guide maps the old API to the new one: https://github.com/Azure/azure-sdk-for-net/blob/main/sdk/servicebus/Azure.Messaging.ServiceBus/MigrationGuide.md

To make sure no other package brings in the old SDKs, run this in your solution folder and search the output for Microsoft.Azure.ServiceBus and WindowsAzure.ServiceBus:

dotnet list package --include-transitive

Thanks

Hi,

This is a bug in the Angular proxy generator. The fix is in https://github.com/abpframework/abp/pull/26212 and will be in the next release. After you upgrade, you won't need your local changes anymore.

Until then, you can keep your changes. If you want them to stay after reinstalling packages, save them as a patch with patch-package:

yarn add patch-package --dev
npx patch-package @abp/ng.schematics

Then add "postinstall": "patch-package" to the scripts in package.json. If you use npm, install it with npm install patch-package --save-dev instead. Remove the patch and the script after you upgrade.

We've refunded your ticket.

Thanks

Hi,

Renewal discounts only apply if you renew within 30 days after your license expires. For Business licenses, that's 40% off for 1 year, 45% for 2 years and 50% for 3 years. If you renew more than 30 days after the expiry date, the price is the same as buying a new license. See https://abp.io/faq#when-should-i-renew-my-license

For anything else about pricing, please contact us at info@abp.io with your organization name (KOConsulting) and your renewal details.

We've refunded the credit for this question.

Thanks

Hi,

Yes, that's correct for the current version. A host record makes the user a host member, so Host shows up in the tenant list at login. Without a host record, the host 2FA settings don't apply to that user.

We know this isn't ideal, and we're looking into improving it.

Thanks

Showing 81 to 90 of 12911 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.8.0-preview. Updated on October 30, 2026, 06:34
1
ABP Assistant
🔐 You need to be logged in to use the chatbot. Please log in first.