Activities of "AI-Bot"

  1. Solution
  • Goal: Customize the creation flow for Tenants (SaaS module) and Users (Identity module) in ABP (e.g., validate additional inputs, create related domain data, call external systems, or change defaults).

  • Change the Tenant creation flow (SaaS)

    • Option A: Override the application service methods
      • Create a derived app service to intercept Create/Update operations of tenants and register it to replace the default service:
using Volo.Abp;
  using Volo.Abp.Application.Dtos;
  using Volo.Abp.DependencyInjection;
  using Volo.Saas.Tenants;
  using Volo.Abp.Application.Services;

  [Dependency(ReplaceServices = true)]
  [ExposeServices(typeof(ITenantAppService))]
  public class MyTenantAppService : TenantAppService, ITenantAppService
  {
      public MyTenantAppService(ITenantRepository tenantRepository, ITenantManager tenantManager)
          : base(tenantRepository, tenantManager)
      {
      }

      public override async Task<TenantDto> CreateAsync(TenantCreateDto input)
      {
          // 1) Custom validation
          if (string.IsNullOrWhiteSpace(input.Name))
              throw new BusinessException("TenantNameRequired");

          // 2) Pre-processing (e.g., reserve subdomain, call payment API)
          // await _myBillingClient.CreateCustomerAsync(input.Name, ...);

          var result = await base.CreateAsync(input);

          // 3) Post-processing (e.g., seed additional data for the new tenant)
          await SeedForTenantAsync(result.Id);

          return result;
      }

      public override async Task<TenantDto> UpdateAsync(Guid id, TenantUpdateDto input)
      {
          // Custom logic before update
          var updated = await base.UpdateAsync(id, input);
          // Custom logic after update
          return updated;
      }

      private async Task SeedForTenantAsync(Guid tenantId)
      {
          using (CurrentTenant.Change(tenantId))
          {
              // Create default domain aggregates, settings, features, etc.
              // await _myAppService.InitializeDefaultsAsync();
          }
      }
  }
  • Option B: Handle tenant domain events
    • Subscribe to tenant lifecycle events and run your logic without replacing services:
using System.Threading.Tasks;
  using Volo.Abp.DependencyInjection;
  using Volo.Abp.EventBus;
  using Volo.Saas.Tenants;

  public class TenantCreatedHandler :
      ILocalEventHandler<TenantCreatedEto>, ITransientDependency
  {
      public async Task HandleEventAsync(TenantCreatedEto eventData)
      {
          using (CurrentTenant.Change(eventData.Id))
          {
              // Seed default data, features, etc.
          }
      }
  }
  • Change database provisioning behavior

    • If you need to control connection string creation or custom provisioning, intercept after CreateAsync when you have TenantId and set connection strings (Repository/Manager) then run migrations (or queue them) and seed data in the tenant scope.
  • Change the User creation flow (Identity)

    • Option A: Replace the UserAppService
using Volo.Abp.DependencyInjection;
  using Volo.Abp.Identity;

  [Dependency(ReplaceServices = true)]
  [ExposeServices(typeof(IIdentityUserAppService))]
  public class MyIdentityUserAppService : IdentityUserAppService, IIdentityUserAppService
  {
      public MyIdentityUserAppService(IdentityUserManager userManager, IIdentityUserRepository userRepository, IIdentityRoleRepository roleRepository)
          : base(userManager, userRepository, roleRepository)
      {
      }

      public override async Task<IdentityUserDto> CreateAsync(IdentityUserCreateDto input)
      {
          // Custom validation (e.g., enforce password policy, email domain checks)
          // if (!input.Email.EndsWith("@mycorp.com")) throw new BusinessException("InvalidEmailDomain");

          var user = await base.CreateAsync(input);

          // Post actions: assign roles/permissions, create related domain data
          // await UserManager.AddToRoleAsync(await UserManager.GetByIdAsync(user.Id), "MyDefaultRole");

          return user;
      }
  }
  • Option B: Use Identity domain events or override the IdentityUserManager
    • Subscribe to user creation events to avoid replacing the app service:
using Volo.Abp.EventBus;
  using Volo.Abp.Identity;

  public class UserCreatedHandler :
      ILocalEventHandler<IdentityUserCreatedEto>, ITransientDependency
  {
      public async Task HandleEventAsync(IdentityUserCreatedEto eventData)
      {
          using (CurrentTenant.Change(eventData.TenantId))
          {
              // E.g., create a profile aggregate, send welcome mail, assign features based on tenant plan
          }
      }
  }
- Alternatively, derive from IdentityUserManager and override CreateAsync to enforce invariants at the domain layer, then register your manager with ReplaceServices.
  • Validation, permissions, and DTO mapping

    • Apply FluentValidation/ABP validation attributes on your custom input DTOs and let ABP’s validation pipeline run.
    • If you introduce new fields on create, extend the DTOs (using object extension system) so they flow through UI/API, then map in your overridden service.
    • Ensure your custom service keeps ABP permission checks (e.g., IdentityPermissions.Users.Create, SaasPermissions.Tenants.Create) or customize as needed.
  • Multi-tenancy considerations

    • Wrap all tenant-specific post-create logic in using (CurrentTenant.Change(tenantId)).
    • For host-triggered user creation inside a tenant, ensure CurrentTenant is correctly set before calling user APIs.
    • If you remove tenant switch and infer tenant by email/username, resolve tenant early and set CurrentTenant before calling Identity (see examples in the referenced guides).
  1. Sources
  • Multi-Tenancy overview (tenant resolution, scopes):
    • https://abp.io/docs/latest/framework/architecture/multi-tenancy
  • Angular multi-tenancy behavior (tenant header flow):
    • https://abp.io/docs/latest/framework/ui/angular/multi-tenancy
  • Community examples for changing tenant selection and simplifying login (pattern for setting CurrentTenant and overriding UI/service flows):
    • https://abp.io/community/articles/4foaup7p
    • https://abp.io/support/questions/9283

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.

  1. Solution
  • Goal: Use Keycloak (as the OpenID Connect provider) instead of ABP’s built-in Auth Server/OpenIddict in a multi-tenant ABP application.

  • High-level approach:

    • Do not run ABP AuthServer/OpenIddict. Configure your UI and API to use remote authentication against Keycloak via OpenID Connect/OAuth 2.0.
    • Resolve tenants from domain/subdomain (or header/query) and ensure the selected tenant is sent during the login flow so authorization happens in the correct tenant.
    • Map Keycloak claims to ABP’s expected claims and ensure the user exists in ABP’s identity system (ABP still needs local users/roles/permissions).
  • Backend (HttpApi.Host or Gateway)

    1. Disable local auth server and configure JWT validation against Keycloak:
// In YourProjectNameHttpApiHostModule.ConfigureServices
  public override void ConfigureServices(ServiceConfigurationContext context)
  {
      var configuration = context.Services.GetConfiguration();

      context.Services
          .AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
          .AddJwtBearer(options =>
          {
              // Keycloak realm OIDC metadata
              options.Authority = configuration["AuthServer:Authority"]; // e.g. https://keycloak.example.com/realms/myrealm
              options.RequireHttpsMetadata = true;
              options.TokenValidationParameters.ValidateAudience = false; // or set ValidAudience(s) if you validate it
              // Optional: Map Keycloak claim names to ABP’s defaults if needed
              options.TokenValidationParameters.NameClaimType = ClaimTypes.Name;
              options.TokenValidationParameters.RoleClaimType = ClaimTypes.Role;
          });

      // Multi-tenancy tenant resolution (domain-based example)
      Configure<AbpTenantResolveOptions>(opts =>
      {
          // e.g. {0}.myapp.com → tenant from subdomain
          opts.AddDomainTenantResolver("{0}.myapp.com");
          // Optionally also allow header/query resolvers if you prefer those
          // opts.AddHeaderTenantResolver();
          // opts.AddQueryStringTenantResolver();
      });

      // CORS if needed for your SPA domains
      Configure<AbpCorsOptions>(options =>
      {
          options.AddPolicy(DefaultCorsPolicyName, builder =>
          {
              builder
                  .WithOrigins(
                      "https://{0}.myapp.com" // replace with exact SPA origins per tenant if not using wildcard CORS service
                  )
                  .AllowAnyHeader()
                  .AllowAnyMethod()
                  .AllowCredentials();
          });
      });
  }

  // In your module’s OnApplicationInitialization (or Program.cs in minimal hosting)
  public override void OnApplicationInitialization(ApplicationInitializationContext context)
  {
      var app = context.GetApplicationBuilder();
      app.UseCorrelationId();
      app.UseRouting();
      app.UseCors();
      app.UseAuthentication();
      app.UseMultiTenancy();
      app.UseAuthorization();
      app.UseConfiguredEndpoints();
  }
  1. Ensure ABP local users exist:
    • ABP authorizes against its own identity/permission system. Use an external login flow (OpenID Connect) and provision/link users to ABP users on first login, or implement a synchronization job from Keycloak to ABP Identity.
    • If you use MVC/Razor pages: Add an OpenID Connect external login handler that uses Keycloak as the external provider and creates/links local ABP users upon first sign-in.
  • Frontend (Angular/Blazor/MVC)
    • Angular:
      • Configure oAuthConfig to use Keycloak realm as issuer and app’s clientId:
oAuthConfig: {
      issuer: 'https://keycloak.example.com/realms/myrealm',
      redirectUri: 'https://{0}.myapp.com', // domain-based tenant
      clientId: 'my-angular-client',
      responseType: 'code',
      scope: 'openid profile email offline_access',
      requireHttps: true
    }
- Use domain-based tenant resolution on the UI by setting baseUrl/Authority with {0} placeholder and configure AbpTenantResolveOptions on the backend to match. This prevents tenant switching and ensures the __tenant value aligns with the subdomain.
- When navigating to login, optionally pass __tenant if you rely on header/query resolvers:
import { AuthService, SessionStateService } from '@abp/ng.core';

    // ...
    const tenantId = this.sessionState.getTenant()?.id;
    this.authService.navigateToLogin(tenantId ? { __tenant: `${tenantId}` } : undefined);
  • Blazor WebAssembly:

    • Set AuthServer:Authority and RemoteServices:Default in appsettings to Keycloak realm and your API base. If you use subdomain tenancy, place {0} and replace it at startup from the current host before configuring OIDC and remote services.
  • Keycloak configuration

    • Create a realm (or one realm per tenant if you intend tenant-per-realm). Most ABP setups use one realm and resolve tenants by domain; your ABP-side tenant resolver remains the source of truth.
    • Create a confidential/public client for your SPA/API flows:
      • Allowed redirect URIs: add each tenant app URL, e.g. https://tenant1.myapp.com, https://tenant2.myapp.com (avoid wildcards for redirect URIs; list exact URLs).
      • Web origins/CORS: add SPA origins accordingly.
    • Expose roles/claims:
      • Map realm or client roles to the role claim Keycloak emits (ABP expects role in the standard role claim). If the claim types differ, set RoleClaimType/NameClaimType in TokenValidationParameters accordingly on the backend.
  • Multi-tenancy specifics

    • Prefer domain/subdomain tenant resolver on backend to prevent switching. Example:
Configure<AbpTenantResolveOptions>(options =>
    {
        options.AddDomainTenantResolver("{0}.myapp.com");
    });
  • Ensure headers with underscores (__tenant) are forwarded if you use header/query resolvers behind proxies like NGINX (set underscores_in_headers on). With pure domain resolver, this is not needed.

  • Place UseAuthentication() before UseMultiTenancy() in the middleware order so tenant can be resolved from token/headers correctly where applicable.

  • Common pitfalls

    • Mismatch between Angular issuer and backend Authority: issuer must point to the Keycloak realm URL. Backend JWT validation Authority must match too.
    • Missing redirect URIs per tenant domain in Keycloak client → causes login loops.
    • UI or gateway blocks the __tenant header (if you rely on it) → tenant falls back to Host.
    • Not provisioning ABP users → tokens validate but ABP authorization fails because local user/permissions are missing.
  1. Sources
  • Domain-based tenant resolution and wildcard/tenant formats (community articles validated):
    • https://abp.io/community/articles/v9y8da7v
    • https://abp.io/community/articles/c1x4un8x
  • Passing __tenant during Angular login (support thread):
    • https://abp.io/support/questions/9661
  • Multi-tenancy middleware order (support thread):
    • https://abp.io/support/questions/9577

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.

  1. Solution
  • Use Centralized Authentication (one Auth Server) and OIDC sign-in, not password-based token requests:

    • Treat Solution 1 as the single OpenIddict Authorization Server (AuthServer).
    • Register each frontend/back‑end of Solution 2 as OpenIddict client applications on Solution 1.
    • In Solution 2, authenticate users via standard OpenID Connect browser flow (Authorization Code, with PKCE for SPAs if any). Never call the token endpoint with username/password when users come from SSO.
  • Automatic user provisioning on first external login (recommended):

    • ABP’s Account module supports external/third‑party logins and can create a local user automatically on first login when external provider returns required claims (e.g., email). This allows Solution 2 users to appear in Solution 1’s identity store without knowing their password.
    • Ensure your OIDC configuration requests the email scope and maps sub -> NameIdentifier so ABP can provision users.
    • If you must restrict auto‑provisioning, handle the external login callback to apply custom rules or block creation.
  • Don’t reuse the same confidential client credentials across different sites arbitrarily:

    • Create a dedicated client for each app of Solution 2 in Solution 1’s OpenIddict (distinct client_id, secret if confidential, redirect URIs).
    • Register exact redirect URIs and post-logout redirect URIs used by Solution 2.
    • Grant the correct flows: for MVC server-side apps use authorization_code (+ refresh_token); for SPAs use authorization_code with PKCE; avoid resource owner password flow for SSO scenarios.
  • When only email is available (no password):

    • Use OIDC Authorization Code flow. The user authenticates at Solution 1 (AuthServer) using whatever SSO/external IdP it trusts (Azure AD, Google, etc.). Your Solution 2 does not and should not need the user’s password.
    • Ensure Solution 1 returns email and profile claims. In ABP, add options.Scope.Add("email") (and profile) in your OpenIdConnect configuration so the Account module can create/link the user on first login.
  • Practical steps

    1. In Solution 1 (AuthServer):
      • Define API scopes/resources needed by Solution 2.
      • Seed OpenIddict client applications for Solution 2 with:
        • client_id and client_secret (if confidential),
        • allowed grant types: authorization_code (+ refresh_token), optionally PKCE,
        • redirectUris/postLogoutRedirectUris matching Solution 2’s public URLs,
        • scopes: openid, profile, email, offline_access (+ your API scopes).
    2. In Solution 2 (MVC):
      • Add OpenID Connect authentication pointing to Solution 1’s authority:
services.AddAuthentication()
         .AddCookie()
         .AddOpenIdConnect("oidc", "AuthServer", options =>
         {
             options.Authority = configuration["AuthServer:Authority"];
             options.RequireHttpsMetadata = true;
             options.ClientId = configuration["AuthServer:ClientId"];
             options.ClientSecret = configuration["AuthServer:ClientSecret"];
             options.ResponseType = "code";
             options.UsePkce = true;
             options.SaveTokens = true;

             options.Scope.Clear();
             options.Scope.Add("openid");
             options.Scope.Add("profile");
             options.Scope.Add("email");
             options.Scope.Add("offline_access");
             options.Scope.Add("MySolution2Api"); // your API scope(s)

             // Important for ABP external login handling:
             options.ClaimActions.MapJsonKey(System.Security.Claims.ClaimTypes.NameIdentifier, "sub");
             options.GetClaimsFromUserInfoEndpoint = true; // or false if you rely on id_token only
         });
 - Ensure cookies are configured and the app uses the OIDC challenge for sign-in.
  1. Enable ABP external login auto‑provisioning behavior:
    • With email scope present, ABP’s Account module can create the user locally on first login. If you’ve customized login, keep the mapping for sub → NameIdentifier and ensure email is present.
  2. If you run behind reverse proxies/ingress:
    • Register HTTPS redirect URIs that exactly match the browser-facing URLs.
    • Enable forwarded headers so ASP.NET Core and OIDC handlers generate correct https URLs.
  • Summary answers to your questions
    • Recommended approach: Centralized authentication via Solution 1 as AuthServer; Solution 2 signs in via OIDC Authorization Code flow. No password needed.
    • Auto-provision users in Solution 1? Yes, recommended. ABP supports creating the user on first external login if required claims are provided.
    • Specific flow when only email is available? Authorization Code flow (with PKCE for SPA). Request email scope so ABP can provision/link the user.
    • Handling authentication without credentials due to SSO: Never use ROPC or call token endpoint with username/password. Always use browser-based OIDC flow against the central AuthServer; rely on claims and ABP’s external login user provisioning.
  1. Sources
  • Centralized authentication and external IdPs with ABP (validated):
    • https://documentation.openiddict.com/
    • https://github.com/abpframework/abp
  • Community guides showing ABP OIDC external login, scopes and mapping:
    • https://learn.microsoft.com/en-us/aspnet/core/security/authentication/social/?view=aspnetcore-8.0

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.

Answer
  1. Solution
  • LeptonX MVC layouts (including the top menu layout) are Razor files in the LeptonX theme package. To customize the top menu, override the corresponding .cshtml files in your Web project by copying them with the exact same folder structure. ABP will automatically prefer your local files over the theme’s embedded ones.

  • Steps:

    1. Get the LeptonX theme source (optional but helpful to see the original files):

      • If you have access to the theme sources, use ABP CLI: abp get-source Volo.Abp.LeptonXTheme
      • In the downloaded repo, find: aspnet-core/volo/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.LeptonX/Themes/LeptonX/Layouts/Application/ Key files:
        • TopMenuLayout.cshtml -> top menu layout
        • SideMenuLayout.cshtml -> side (left) menu layout Additionally, reusable pieces (header, navbar, user menu, etc.) are under: Themes/LeptonX/Components/...
    2. Override the top menu layout in your MVC app:

      • Create the same folder structure in your Web project: YourProject.Web/Views/ Then add the following (exact path and names are critical): YourProject.Web/Themes/LeptonX/Layouts/Application/TopMenuLayout.cshtml
      • Copy the original TopMenuLayout.cshtml into that path.
      • Modify it as needed (e.g., rearrange toolbar/menu, add extra sections/partials).
      • If you need to customize the menu rendering itself or header elements, also override the related theme components found under: Themes/LeptonX/Components/<ComponentName>/Default.cshtml Create matching folders and copy/edit the component views you want to change.
    3. Switch your application to use the top menu layout:

      • Ensure your application layout selection (usually configured by the theme/layout provider or the selected layout in your pages) uses the TopMenuLayout.cshtml. In ABP MVC, LeptonX decides the layout file per page. If a page doesn’t pick the top layout by default, you can force it per-page: @{ Layout = "/Themes/LeptonX/Layouts/Application/TopMenuLayout.cshtml"; }
      • Alternatively, if your project already uses the Top Menu layout globally, just overriding the file is enough; your customized version will take effect.
    4. Notes/tips:

      • Keep the path casing and hierarchy exactly the same as the theme’s files to override correctly.
      • You do not need to embed resources or change bundling for Razor overrides.
      • If you only want to change CSS/spacing, prefer CSS overrides first. For structural changes (moving items, adding containers), edit the Razor files.
      • For menu items themselves (entries), use your MenuContributor; for the layout and placement (top vs. side), change the layout .cshtml.
  1. Sources
  • ABP Basic Theme sample (shows how overriding theme Razor files by matching folder structure works):
    • https://github.com/abpframework/abp/blob/dev/modules/basic-theme/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Components/Menu/Default.cshtml

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.

  1. Solution
  • What changed in v10.0.1

    • ABP Framework modules switched their default object mapper from AutoMapper to Mapperly in v10. You can still use AutoMapper in your own application layers, but new ABP templates and modules favor Mapperly. This is why you see guidance “use Mapperly,” especially in UI layers like Blazor.
  • Why it “works in Application layer but not in Blazor”

    • Your Application layer likely still references and configures Volo.Abp.AutoMapper and your AutoMapper profiles. However, your Blazor project was created/updated to v10 where the UI integration uses Mapperly by default. If the Blazor project no longer references Volo.Abp.AutoMapper or doesn’t register your AutoMapper profiles, AutoMapper-based mappings won’t be available there.
  • Two supported ways to resolve

    Option A — Keep using AutoMapper everywhere (quickest to keep existing profiles)

    1. Add the AutoMapper integration in all layers that need it (including Blazor):
      • Add package references to the Blazor project:
        • Volo.Abp.AutoMapper
        • AutoMapper
        • AutoMapper.Extensions.Microsoft.DependencyInjection
    2. Ensure your Blazor module depends on AbpAutoMapperModule and registers your profiles:
using Volo.Abp;
     using Volo.Abp.AutoMapper;
     using Volo.Abp.Modularity;

     [DependsOn(
         typeof(AbpAutoMapperModule)
     )]
     public class MyProjectBlazorModule : AbpModule
     {
         public override void ConfigureServices(ServiceConfigurationContext context)
         {
             context.Services.AddAutoMapperObjectMapper<MyProjectBlazorModule>();

             Configure<AbpAutoMapperOptions>(options =>
             {
                 // Add your AutoMapper profiles that contain CreateMap<,>() definitions
                 options.AddMaps<MyProjectApplicationModule>(validate: true);
                 options.AddMaps<MyProjectBlazorModule>(validate: true);
             });
         }
     }
  1. Make sure your AutoMapper Profile classes are in assemblies scanned above (e.g., Application and Blazor projects if you keep profiles there).
  2. If you previously removed Volo.Abp.AutoMapper due to the upgrade, restore it in the .csproj and module DependsOn.

Option B — Migrate to Mapperly (recommended for ABP v10+)

  1. Replace AutoMapper integration with Mapperly across the solution:
    • In all projects, replace Volo.Abp.AutoMapper with Volo.Abp.Mapperly (csproj).
    • In code, replace:
      • using Volo.Abp.AutoMapper; → using Volo.Abp.Mapperly;
      • AbpAutoMapperModule → AbpMapperlyModule
      • AddAutoMapperObjectMapper → AddMapperlyObjectMapper
      • Remove Configure<AbpAutoMapperOptions> blocks.
  2. Convert your AutoMapper Profile mappings to Mapperly mappers using partial classes with [Mapper] and Map/ReverseMap methods. Example:
using Riok.Mapperly.Abstractions;
     using Volo.Abp.Mapperly;

     [Mapper(RequiredMappingStrategy = RequiredMappingStrategy.Target)]
     public partial class MyEntityToDtoMapper : MapperBase<MyEntity, MyEntityDto>
     {
         public override partial MyEntityDto Map(MyEntity source);
         public override partial void Map(MyEntity source, MyEntityDto destination);
     }
  1. Register Mapperly in each layer (including Blazor) that needs mappings:
using Volo.Abp;
     using Volo.Abp.Mapperly;
     using Volo.Abp.Modularity;

     [DependsOn(typeof(AbpMapperlyModule))]
     public class MyProjectBlazorModule : AbpModule
     {
         public override void ConfigureServices(ServiceConfigurationContext context)
         {
             context.Services.AddMapperlyObjectMapper<MyProjectBlazorModule>();
         }
     }
  1. Build to let source generators produce mapping implementations. Fix any missing attributes (e.g., MapProperty, MapperIgnoreTarget) and AfterMap custom logic.
  • Practical recommendation
    • If you need a fast fix to keep your existing profiles: follow Option A and re-enable AutoMapper in the Blazor project.
    • If you can invest a bit of time for the future-proof path: follow Option B and migrate to Mapperly using the guide below.
  1. Sources
  • ABP 10 migration and Mapperly adoption:
    • https://abp.io/docs/10.0/release-info/migration-guides/abp-10-0
    • https://abp.io/docs/10.0/release-info/migration-guides/AutoMapper-To-Mapperly
  • Background and examples (community/ABP context):
    • https://abp.io/community/articles/best-free-alternatives-to-automapper-in-net-l9f5ii8s

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.

  1. Solution
  • Why it happens

    • EF Core does not enable lazy loading by default in ABP. The default repository GetAsync(id) loads only the aggregate root and not its navigation collections. Therefore, when the ObjectMapper maps ProductItem to ProductItemDto, the collections are not loaded and end up empty.
  • What to change

    • Load related collections explicitly, then map. Use ABP’s repository WithDetails/WithDetailsAsync (or GetAsync(id, includeDetails: true) if your repository exposes it) to eagerly include navigation properties.
  • Option A: Use WithDetailsAsync in your AppService

public virtual async Task<ProductItemDto> GetAsync(Guid id)
  {
      var queryable = await _productItemRepository.WithDetailsAsync(
          x => x.ProductItemCultures,
          x => x.ProductItemAttributeListValues
      );

      var entity = await AsyncExecuter.FirstOrDefaultAsync(queryable.Where(x => x.Id == id));
      if (entity == null)
      {
          throw new EntityNotFoundException(typeof(ProductItem), id);
      }

      return ObjectMapper.Map<ProductItem, ProductItemDto>(entity);
  }
  • Option B: Expose includeDetails on repository Get/Find
    • If your repository inherits from EfCoreRepository, you can call:
public virtual async Task<ProductItemDto> GetAsync(Guid id)
  {
      var entity = await _productItemRepository.GetAsync(id, includeDetails: true);
      return ObjectMapper.Map<ProductItem, ProductItemDto>(entity);
  }
  • Then configure default details in a custom repository if needed:
public class ProductItemRepository 
      : EfCoreRepository<MyDbContext, ProductItem, Guid>, IProductItemRepository
  {
      public ProductItemRepository(IDbContextProvider<MyDbContext> dbContextProvider)
          : base(dbContextProvider)
      {
      }

      public override async Task<IQueryable<ProductItem>> WithDetailsAsync()
      {
          return (await GetQueryableAsync())
              .Include(x => x.ProductItemCultures)
              .Include(x => x.ProductItemAttributeListValues);
      }
  }
  • With this, any GetAsync(id, includeDetails: true) will include those navigations by default.

  • Notes for multi-tenancy and filters

    • Ensure the requested ProductItem and its children belong to the same Tenant (entities implement IMultiTenant). ABP’s data filters automatically apply tenant filtering; mismatched TenantId will result in not-found or empty collections.
    • If any child entities are soft-deleted (ISoftDelete), ABP filters them out. Disable filters only if you need to see deleted children:
using (DataFilter.Disable<ISoftDelete>())
    {
        // load with details here
    }
  • Summary
    • Replace the generated GetAsync(id) call with an eager-loading variant (WithDetails/Include) so EF Core actually retrieves ProductItemCultures and ProductItemAttributeListValues before mapping. Swagger shows the DTO shape, not the loading behavior; you must include related data explicitly.
  1. Sources
  • Validated reference (ABP EF Core, Loading Related Entities – WithDetails/WithDetailsAsync):
    • https://abp.io/docs/latest/framework/data/entity-framework-core/

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.

  1. Solution
  • Short answer: Yes, you can ship a pure-OSS HttpApi.Client package (and underlying contracts) by removing all ABP Commercial (Pro) dependencies from Domain.Shared, Application.Contracts and HttpApi.Client, while still using Commercial packages in your server-side layers. However:

    • You must not reference any Pro package from the client-distributed NuGet (no transitive Pro deps).
    • Any features/types that come from Pro packages cannot be exposed in your contracts or client proxies.
    • This is supported from a technical standpoint, but you must ensure your contracts and controllers don’t use types, permissions, features, or APIs defined by Pro modules.
  • Practical steps:

    1. Domain.Shared
      • Remove Pro dependencies like:
        • Volo.Abp.Identity.Pro.Domain.Shared
        • Volo.Abp.OpenIddict.Pro.Domain.Shared
      • Replace with OSS equivalents where needed (e.g., Volo.Abp.Identity.Domain.Shared, Volo.Abp.OpenIddict.Domain.Shared) and refactor constants/enums/options so you don’t rely on Pro-only members.
    2. Application.Contracts
      • Remove Pro contracts like:
        • Volo.Abp.Identity.Pro.Application.Contracts
        • Volo.Abp.OpenIddict.Pro.Application.Contracts
        • Volo.Abp.Account.Pro.*.Application.Contracts
      • Reference only OSS contracts (e.g., Volo.Abp.Identity.Application.Contracts) or eliminate the dependency entirely by defining your own DTOs and interfaces that don’t use Pro types.
      • Ensure no DTO, interface, attribute, permission name, or feature flag from Pro packages remains.
    3. HttpApi.Client
      • Remove all Pro HttpApi.Client packages:
        • Volo.Abp.Identity.Pro.HttpApi.Client
        • Volo.Abp.Account.Pro.*.HttpApi.Client
        • Volo.Abp.OpenIddict.Pro.HttpApi.Client
      • Generate or reference only the client proxies for your own controllers and OSS-based modules.
      • If you previously used dynamic client proxies, you can switch to Static C# API clients to fully control what gets compiled into the client package.
    4. Server-side (Application, Domain, HttpApi, Host)
      • You can continue using Pro packages here.
      • Keep all Pro usage out of the shared contract surface. If a controller action returns or accepts a Pro type, that action will force a Pro dependency into your client. Refactor these endpoints to return your own DTOs or OSS types for the public API.
    5. Packaging for external users
      • Create a separate HttpApi.Client-only NuGet (and optionally a separate Application.Contracts NuGet) that references only nuget.org OSS packages.
      • Ensure nuspec/props do not include any private feeds or Pro package references.
      • Consumers only need nuget.org; they won’t need ABP Commercial feeds or licenses to restore or use your client.
  • Licensing implications:

    • ABP Framework (OSS) is MIT and can be redistributed freely.
    • ABP Commercial (Pro) packages must not be redistributed to third parties (no transitive or direct dependencies in what you publish).
    • If you keep Pro packages only on the server and fully exclude them from your client/contract packages, your distributed packages remain OSS-only and do not require your clients to have an ABP Commercial license.
  • Technical limitations to watch:

    • Transitive leakage: even a single Pro-defined permission name, feature, interface, DTO, or attribute referenced in Application.Contracts will pull in Pro packages or cause compile/runtime mismatches. Replace these with your own abstractions or OSS equivalents.
    • Identity/OpenIddict differences: Pro variants add extra endpoints/features. If your public API relies on those extras, you must either:
      • Project them to your own DTOs and surface only those in contracts, or
      • Provide alternative endpoints that don’t require Pro features.
    • Angular/Blazor/Client tooling: If you generate client proxies automatically, configure generation to target only your public controllers (exclude Pro module endpoints). Static C# client generation helps avoid unwanted dependencies.
  1. Sources
  • 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.

  1. Solution To use Dynamic Claims in a tiered ABP 8.3.4 solution (Blazor Server UI + separated Auth Server), configure it as follows:
  • Enable Dynamic Claims on the authentication server (AuthServer/IdentityHost):
using Volo.Abp.Security.Claims;

  public override void ConfigureServices(ServiceConfigurationContext context)
  {
      context.Services.Configure<AbpClaimsPrincipalFactoryOptions>(options =>
      {
          options.IsDynamicClaimsEnabled = true; // enable dynamic claims
      });
  }
  • Enable Dynamic Claims on the UI host (Blazor Server) and set RemoteRefreshUrl to the Auth Server:
using Volo.Abp.Security.Claims;

  public override void ConfigureServices(ServiceConfigurationContext context)
  {
      var configuration = context.Services.GetConfiguration();

      context.Services.Configure<AbpClaimsPrincipalFactoryOptions>(options =>
      {
          options.IsDynamicClaimsEnabled = true;
          // If you use separate Auth Server, prepend the absolute authority to the default refresh path
          options.RemoteRefreshUrl = configuration["AuthServer:Authority"]?.TrimEnd('/') + options.RemoteRefreshUrl;
      });
  }

Notes:

  • If you use Abp’s AddAbpOpenIdConnect/AddAbpJwtBearer, the RemoteRefreshUrl is set automatically. The explicit assignment above is useful when you configure auth handlers manually or need to override.

  • Ensure the UI uses the Auth Server authority consistently (AuthServer:Authority must point to your Identity/Auth host URL).

  • Implement and register your dynamic claims contributor (on the Auth Server; in tiered setups, this must run where the principal is created/refreshed):

using System.Security.Claims;
  using Volo.Abp.DependencyInjection;
  using Volo.Abp.Security.Claims;

  public class MyDynamicClaimsContributor :
      IDynamicClaimsPrincipalContributor, ITransientDependency
  {
      public async Task ContributeAsync(DynamicClaimsPrincipalContributionContext context)
      {
          // Example: add/update a claim dynamically, e.g. a custom tenant role/flag
          // Use context.Principal to read current claims and context.TenantId, context.UserId, etc.
          var identity = context.Principal.Identity as ClaimsIdentity;
          if (identity == null) return;

          // remove existing value if present
          identity.RemoveClaim(identity.FindFirst("my-dynamic-flag"));
          // add new dynamic claim
          identity.AddClaim(new Claim("my-dynamic-flag", "true"));

          await Task.CompletedTask;
      }
  }

Then register it (Auth Server):

public override void ConfigureServices(ServiceConfigurationContext context)
  {
      Configure<AbpDynamicClaimsPrincipalOptions>(options =>
      {
          options.Contributors.Add<MyDynamicClaimsContributor>();
      });
  }
  • RemoteServices configuration: which remote service is used?

    • Dynamic Claims refresh is handled by ABP’s Dynamic Claims endpoint on the Auth Server; you do not call AbpIdentity or AbpAccountPublic endpoints yourself for the refresh. The framework posts to RemoteRefreshUrl to get an updated principal.
    • Therefore, set RemoteRefreshUrl to the AuthServer’s base URL + the built-in path (done automatically by AddAbpOpenIdConnect/AddAbpJwtBearer). You do NOT need to configure a RemoteServices: section specifically for “AbpAccountPublic” or “AbpIdentity” for Dynamic Claims to work.
    • Keep your standard RemoteServices configuration for your own HTTP client proxies as usual; Dynamic Claims does not require an extra named remote service.
  • Authentication middleware/handlers:

    • UI (Blazor Server): use the OIDC/JWT integration that authenticates against the Auth Server and let ABP wire the RemoteRefreshUrl. For example, with OIDC:
context.Services.AddAuthentication()
        .AddAbpOpenIdConnect("oidc", options =>
        {
            options.Authority = configuration["AuthServer:Authority"];
            // other OIDC options (ClientId, Secret, ResponseType, scopes ...)
            // RemoteRefreshUrl is set by AddAbpOpenIdConnect internally.
        });
  • Runtime checks:
    • After user signs in, ABP will refresh claims dynamically on-demand or on interval. You can force-refresh by calling the dynamic claims refresh endpoint (framework does it) or sign-out/in to observe changes.
    • Ensure date/time skew between UI and AuthServer isn’t extreme; HTTPS must be used in production.
  1. Sources
  • Dynamic Claims fundamentals and configuration:
    • No verified source available for this specific query within the accessible documentation links. However, the provided configuration aligns with ABP’s Dynamic Claims options (AbpClaimsPrincipalFactoryOptions, AbpDynamicClaimsPrincipalOptions) and the guidance that RemoteRefreshUrl is set by AddAbpOpenIdConnect/AddAbpJwtBearer.
  • Related community context indicating RemoteRefreshUrl usage and enabling Dynamic Claims:
    • https://abp.io/support/questions/560
    • https://abp.io/support/questions/10213

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.

  1. Solution
  • Root cause: In ABP v10 projects generated by ABP Suite, Mapperly generates a mapper class like CompanyWithNavigationPropertiesToCompanyWithNavigationPropertiesDtoMapper.g.cs. The NullReferenceException typically occurs when a navigation collection or reference (e.g., Sectors, Child collection, or a WithNavigationProperties reference) is null and the generated MapToListOf... or MapTo... method doesn’t guard against null. This happens if the repository/service returns entities without including related data or returns null collections, and the generated mapper directly enumerates them.

  • Fix it at the query layer (recommended): Ensure navigation properties and collections are loaded (Include / ThenInclude) before mapping. In the repository GetListWithNavigationPropertiesAsync (or the query used by your AppService), include the related entities/collections and coalesce collections to empty lists where needed.

    Example:

public async Task<List<CompanyWithNavigationProperties>> GetListWithNavigationPropertiesAsync(
      string filterText, string sorting, int maxResultCount, int skipCount, CancellationToken cancellationToken = default)
  {
      var query = (await GetQueryableAsync())
          .Include(x => x.Organisation)              // reference
          .Include(x => x.Sectors)                   // collection
          .AsSplitQuery();                           // optional for safety

      // apply filters, sorting, paging...

      var items = await query
          .OrderBy(string.IsNullOrWhiteSpace(sorting) ? nameof(Company.Name) : sorting)
          .Skip(skipCount)
          .Take(maxResultCount)
          .ToListAsync(GetCancellationToken(cancellationToken));

      // Optional: defensive normalization so Mapperly doesn't hit null collections
      foreach (var item in items)
      {
          item.Sectors ??= new List<Sector>();
      }

      return items.Select(e => new CompanyWithNavigationProperties
      {
          Company = e,
          Organisation = e.Organisation,
          Sectors = e.Sectors
      }).ToList();
  }
  • If you have a custom WithNavigationProperties type (e.g., CompanyWithNavigationProperties wrapping master + refs): Make sure that when constructing this wrapper, you never pass null collections. Always set empty lists when data is missing:
return new CompanyWithNavigationProperties
  {
      Company = company,
      Organisation = company.Organisation,
      Sectors = company.Sectors ?? new List<Sector>()
  };
  • Optionally harden the generated partial mapper (non-breaking approach): Mapperly allows partial methods. Create a partial class with the same name and add safe wrappers to coalesce collections before mapping.
// In Application project (not in obj folder)
  public partial class CompanyWithNavigationPropertiesToCompanyWithNavigationPropertiesDtoMapper
  {
      private ICollection<Sector> Safe(ICollection<Sector>? source) => source ?? Array.Empty<Sector>();

      // If your generated method is MapToListOfSectorDto, add a partial wrapper where you call it with Safe(...)
      public CompanyWithNavigationPropertiesDto MapSafe(CompanyWithNavigationProperties source)
      {
          // normalize before calling generated Map(...)
          source.Sectors ??= new List<Sector>();
          return Map(source); // calls the generated method
      }
  }

Then use ObjectMapper.Map with the safe entry point (or normalize source prior to mapping). Prefer fixing data loading as shown above.

  • Ensure object-to-object mapping integration is properly configured for v10 (Mapperly): ABP v10 uses Mapperly by default. Make sure:
    • Volo.Abp.Mapperly package is referenced by your Application layer.
    • Your module depends on AbpMapperlyModule:
[DependsOn(typeof(AbpDddApplicationModule), typeof(AbpAutoMapperModule), typeof(AbpMapperlyModule))]
    public class YourApplicationModule : AbpModule
    {
    }

This guarantees ABP’s MapperlyAutoObjectMappingProvider is active.

  • Verify AppService mapping usage: In your AppService GetListAsync, continue using ObjectMapper.Map on the already-normalized source:
public override async Task<PagedResultDto<CompanyWithNavigationPropertiesDto>> GetListAsync(GetCompaniesInput input)
  {
      var items = await _companyRepository.GetListWithNavigationPropertiesAsync(
          input.FilterText, input.Sorting, input.MaxResultCount, input.SkipCount);

      // items must have non-null navigation collections
      var dtos = ObjectMapper.Map<List<CompanyWithNavigationProperties>, List<CompanyWithNavigationPropertiesDto>>(items);

      var totalCount = await _companyRepository.GetCountAsync(input.FilterText /* ... */);

      return new PagedResultDto<CompanyWithNavigationPropertiesDto>(totalCount, dtos);
  }
  • Why this works: Mapperly’s generated methods typically enumerate the source collections. If EF Core did not load the navigation properties or they’re null, enumerating causes NullReferenceException. Loading relations via Include and normalizing to empty collections prevents that.
  1. Sources
  • Object-to-Object Mapping in ABP: https://docs.abp.io/en/abp/latest/Object-To-Object-Mapping
  • ABP Module system (for module dependencies like AbpMapperlyModule): https://abp.io/docs/latest/framework/architecture/modularity/basics
  • ABP Suite master–detail generation overview: https://abp.io/docs/latest/suite/creating-master-detail-relationship

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.

  1. Solution Based on your stack trace, the failure happens while rendering the LeptonX theme’s language switch on the AuthServer login page: Volo.Abp.Localization.LanguageInfo.ChangeCultureInternal(String cultureName, String uiCultureName, String displayName) LeptonX.Components.SideMenu.Toolbar.LanguageSwitch.ThemeLanguageInfoProvider.GetLanguageSwitchViewComponentModel()

This means the configured languages contain an invalid LanguageInfo (missing CultureName/UiCultureName/DisplayName) at runtime. In containers, this typically happens because environment variables are not bound correctly to options (wrong keys, wrong indices, or YAML parsing removing quotes).

Do the following:

  • Ensure RequestLocalization is consistent

    • Add both cultures your app uses:
      • vi (default)
      • en
    • Example (appsettings of AuthServer): { "Localization": { "DefaultLanguage": "vi", "Languages": [ { "CultureName": "vi", "UiCultureName": "vi", "DisplayName": "Vietnamese", "IsDefault": true }, { "CultureName": "en", "UiCultureName": "en", "DisplayName": "English" } ] } } { "RequestLocalization": { "DefaultRequestCulture": "vi", "SupportedCultures": [ "vi", "en" ], "SupportedUICultures": [ "vi", "en" ] } }
  • Map environment variables correctly in docker-compose

    • For hierarchical keys, use double underscores __ and integer indices, and keep values quoted in YAML.
    • Example for your authserver service (environment section):
      • Localization__DefaultLanguage="vi"
      • Localization__Languages__0__CultureName="vi"
      • Localization__Languages__0__UiCultureName="vi"
      • Localization__Languages__0__DisplayName="Vietnamese"
      • Localization__Languages__0__IsDefault="true"
      • Localization__Languages__1__CultureName="en"
      • Localization__Languages__1__UiCultureName="en"
      • Localization__Languages__1__DisplayName="English"
      • RequestLocalization__DefaultRequestCulture="vi"
      • RequestLocalization__SupportedCultures="vi,en"
      • RequestLocalization__SupportedUICultures="vi,en"
    • Important:
      • Keep the indices continuous (0, 1, 2, …).
      • Quote all values so YAML doesn’t treat vi,en as a sequence or strip values.
      • Do NOT try to pass a JSON array to a single env var (e.g., Localization__Languages=[{...}]); that won’t bind to the options object.
  • Remove or fix commented examples in docker-compose

    • Commented lines do nothing; but ensure you’re not mixing the single-value shortcuts. Prefer the explicit per-language indexing shown above.
  • Verify runtime configuration

    • Inside the running container, dump effective configuration at startup to confirm binding: public override void OnApplicationInitialization(...) { var opts = context.ServiceProvider.GetRequiredService<IOptions<AbpLocalizationOptions>>().Value; var req = context.ServiceProvider.GetRequiredService<IOptions<RequestLocalizationOptions>>().Value; // log opts.Languages and req.SupportedCultures for inspection }
    • Alternatively, add a quick health endpoint that returns AbpLocalizationOptions.Languages to ensure CultureName/UiCultureName/DisplayName are non-empty.
  • Seed Language Management (if Dynamic Localization enabled)

    • Since you enabled Dynamic Localization, ABP reads languages/resources from DB too. Ensure AbpLanguages table has at least vi and en with CultureName and UiCultureName set, and that nothing writes an empty language row.
    • If any row has null/empty CultureName or UiCultureName, delete/fix it.
  • Clear caches and restart containers

    • Stop containers, clear volumes if you changed env var names, and start again to ensure fresh options binding.

Why this fixes the error LanguageInfo.ChangeCultureInternal throws when any LanguageInfo has null/empty CultureName. In your log, the crash happens while building the language switch model. When configuration binding yields a LanguageInfo with missing CultureName (often due to malformed environment variable keys or YAML parsing), the theme tries to use it and the exception is thrown, returning HTTP 500 on /Account/Login.

  1. Sources
  • ABP Module System:
    • https://abp.io/docs/latest/framework/architecture/modularity/basics
  • Application Localization API (shows required cultureName and how localization is driven by culture names):
    • https://abp.io/docs/latest/framework/api-development/standard-apis/localization

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.

Showing 631 to 640 of 1374 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 21, 2026, 11:09
1
ABP Assistant
🔐 You need to be logged in to use the chatbot. Please log in first.