abp-dynamic-layout still works, but it depends on ABP’s layout system being properly provided/bootstrapped and on routes being compatible with the post-v9 Angular routing approach (standalone + updated route factories).Do the following (these are the verified migration areas between v9 and v10 that affect Angular routing/layout behavior):
forLazy()), migrate them to the new pattern used by ABP packages, i.e. createRoutes(...).Example (conceptual):
{
path: 'identity',
loadChildren: () => import('@volo/abp.ng.identity').then(m => m.createRoutes()),
}
If your own feature modules still use “old style” lazy loading, ABP layout resolution can silently fail to match the route tree as expected in newer Angular/ABP setups.
ApplicationConfig providers (e.g. app.config.ts). Make sure you didn’t miss the ABP providers responsible for theme/layout.Practically:
This is especially important if you upgraded “in place” instead of regenerating a v10 Angular UI and diffing the bootstrap/config.
If you want a concrete checklist for what changed in ABP Angular around routing/standalone, use these (verified) references and compare your project with the template produced by ABP 10.x:
Sources (validated)
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.
Volo.Abp.OperationRateLimiting is a Pro module and it requires ABP Team (or higher) to use/download. This is explicitly stated in ABP’s Operation Rate Limiting documentation, and it also notes that the module is used by Account (Pro) modules and can come pre-installed in the latest startup templates.So for your two concrete questions:
Does your current license tier include Volo.Abp.OperationRateLimiting?
I can’t see your license tier from here, but the rule is definitive: only Team (or higher) includes it. If you’re on a lower tier, you will get 403 exactly like this when restoring Volo.Abp.OperationRateLimiting.Domain (10.3.0 included).
Why does the default app template pull it?
Because in v10.3 the module is used internally by Account (Pro) modules and is also described as pre-installed in the latest startup templates. If your template selection (e.g., Social Login / OpenIddictAdmin / other Pro account-related features) caused Account Pro packages/modules to be included, they can bring OperationRateLimiting as a dependency—then restore fails on tiers that don’t include it. Practically, this means the generated solution can become “unbuildable out of the box” unless the generator filters Pro-only modules based on license, which appears to be the gap you’re hitting.
Workarounds you can apply immediately (to make the solution build without upgrading):
OperationRateLimiting:
*.csproj files, remove any PackageReference to Volo.Abp.OperationRateLimiting.* (if present).DependsOn(...) chain in your POC.ApiSports*.Module classes and remove any module dependency related to OperationRateLimiting (directly) and/or the specific Pro module that introduced it (often via Account Pro features).If you want this treated as a template/generation issue (so ABP Studio/CLI warns or excludes it based on license), please report it through the official channel so the team can reproduce it against your exact template options and license entitlement:
(Links validated.)
Sources:
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.
Volo.Abp.OperationRateLimiting.Domain is a commercial (Pro) module, and it is not available for every commercial license tier. If your license doesn’t include that module, the ABP private feed returns 403 (Forbidden) for that specific package/version, while other Volo.Abp.* packages may still restore successfully (because they are included in your tier).What to do (pick one):
If you want to use Operation Rate Limiting
If you don’t need Operation Rate Limiting in this POC
Volo.Abp.OperationRateLimiting.*DependsOn(...) chain in your *.WebModule / *.HttpApiHostModule (where it gets brought in).If you believe you should have access but still get 403
abp logout
abp login
dotnet nuget locals all --clear
dotnet restore
Sources:
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.
ITypeConverter<TSource, TDestination> is to add a custom mapping method (a normal C# method) that Mapperly will use when it needs to map between those types.Create a Mapperly mapper (ABP style) and add a method which maps double? → CurrencyObject[]?:
using Riok.Mapperly.Abstractions;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Mapperly;
public class CurrencyObject
{
public double CurrencyValue { get; set; }
public string? Currency { get; set; }
}
[Mapper]
public partial class SurveyCurrencyMapper
: MapperBase<double?, CurrencyObject[]?>, ITransientDependency
{
// ABP expects these overrides for ObjectMapper.Map<double?, CurrencyObject[]?>()
public override partial CurrencyObject[]? Map(double? source);
public override partial void Map(double? source, CurrencyObject[]? destination);
// Mapperly custom conversion (this replaces AutoMapper ITypeConverter)
private static CurrencyObject[]? MapToCurrencyObjectArray(double? source)
=> source.HasValue
? new[]
{
new CurrencyObject
{
CurrencyValue = source.Value,
Currency = "USD"
}
}
: null;
}
How it works:
double? to CurrencyObject[]?, it can use your custom method.MapperBase<TSource, TDestination> + ITransientDependency allows ObjectMapper.Map<,> to resolve it via the Mapperly provider.If you don’t need ObjectMapper.Map<double?, CurrencyObject[]?>() directly and only need it as part of a larger mapping (e.g., UploadedSurveyData -> SurveyData), you can also just put the MapToCurrencyObjectArray(double?) method inside that mapper class; Mapperly will reuse it for that conversion.
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.
Mapperly (and ABP’s Mapperly integration) is compile-time / static mapping. Your AutoMapper extension is runtime / reflection-based (“ignore everything not in this string list”). So if you truly need a List<string> decided at runtime, you will not get an equivalent without either:
If you have 100+ properties and this pattern is used in many mappings, the two scalable options are:
This keeps your “only map these fields” behavior without hardcoding 100 property names and works for all DTO/entity pairs.
using System.Reflection;
public static class PatchMappingExtensions
{
/// <summary>
/// Copies values from <paramref name="source"/> to <paramref name="destination"/>
/// only for destination properties whose names are in <paramref name="requiredProperties"/>.
/// </summary>
public static void PatchByPropertyList<TSource, TDestination>(
TSource source,
TDestination destination,
IReadOnlyCollection<string> requiredProperties)
where TDestination : class
{
if (requiredProperties == null || requiredProperties.Count == 0)
{
return; // same semantics as your current extension
}
var srcType = typeof(TSource);
var destType = typeof(TDestination);
// Build lookup for source properties by name (public instance)
var srcProps = srcType
.GetProperties(BindingFlags.Instance | BindingFlags.Public)
.Where(p => p.CanRead)
.ToDictionary(p => p.Name, p => p);
foreach (var destProp in destType.GetProperties(BindingFlags.Instance | BindingFlags.Public))
{
if (!destProp.CanWrite)
{
continue;
}
if (!requiredProperties.Contains(destProp.Name))
{
continue;
}
if (!srcProps.TryGetValue(destProp.Name, out var srcProp))
{
continue;
}
// Only assign when compatible (basic safety)
if (!destProp.PropertyType.IsAssignableFrom(srcProp.PropertyType))
{
continue;
}
var value = srcProp.GetValue(source);
destProp.SetValue(destination, value);
}
}
}
Usage with Mapperly in ABP:
PatchByPropertyList for this special dynamic-field-list case:// Full map (generated, fast)
mapper.Map(uploaded, surveyData);
// Dynamic partial update (runtime)
PatchMappingExtensions.PatchByPropertyList(uploaded, surveyData, requiredProperties);
This is essentially what AutoMapper was doing for you (runtime reflection), but now it’s explicit, reusable, and you write it once.
This is the most Mapperly-friendly approach: define a DTO that contains only the fields that can be updated, and map that DTO to the entity. No reflection, no list of strings, no 100-case switch.
Example idea:
UploadedSurveyDataPatchDto includes only patchable properties (maybe nullable)This approach aligns with Mapperly’s compile-time nature and is typically cleaner long-term, but it requires changing your API shape.
Validated references:
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.
You can’t migrate this IgnoreNotInListMembers(List<string>) concept 1:1 to Mapperly.
AutoMapper can decide at runtime which destination members to ignore using reflection. Mapperly generates mapping code at compile time, so “ignore members by a runtime string list” is not something it can express via attributes.
Recommended migration approach (Mapperly + manual “patch” mapping):
Treat this as a partial update (“patch”) use-case and apply the requiredProperties list yourself after (or instead of) mapping.
Example:
using Riok.Mapperly.Abstractions;
public class UploadedSurveyData
{
// source props...
}
public class SurveyData
{
// destination props...
public string? Q1 { get; set; }
public string? Q2 { get; set; }
public string? Q3 { get; set; }
}
[Mapper]
public partial class SurveyDataMapper
{
// Normal full mapping (Mapperly generates this)
public partial SurveyData Map(UploadedSurveyData source);
// Update existing destination (Mapperly generates this)
public partial void Map(UploadedSurveyData source, SurveyData destination);
}
public static class SurveyDataPatchMapping
{
// Runtime selective update (replacement for IgnoreNotInListMembers)
public static void MapOnly(
this SurveyDataMapper mapper,
UploadedSurveyData source,
SurveyData destination,
List<string> requiredProperties)
{
// If requiredProperties is empty => do nothing (same behavior as your extension)
if (requiredProperties == null || requiredProperties.Count == 0)
{
return;
}
// Update only the fields you allow.
// (This is explicit; you can generate this switch via a source generator/T4 if the list is large.)
foreach (var p in requiredProperties)
{
switch (p)
{
case nameof(SurveyData.Q1):
destination.Q1 = mapper.Map(source).Q1; // or map from source directly
break;
case nameof(SurveyData.Q2):
destination.Q2 = mapper.Map(source).Q2;
break;
case nameof(SurveyData.Q3):
destination.Q3 = mapper.Map(source).Q3;
break;
}
}
}
}
Notes:
If you want to avoid calling mapper.Map(source) repeatedly, map once into a temp object and copy selected props.
If you have many properties, the most maintainable pattern is to model the patch DTO to contain only patchable fields (preferred) or generate the switch/copy code.
ABP-specific note: Mapperly integration is attribute-based; “ignore” behavior is typically expressed via Mapperly attributes (static configuration), not runtime reflection lists.
Links (validated):
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.
This error is coming from ABP Commercial licensing. It typically happens in CI/CD, containers, or Azure Functions/App Services when the deployed environment doesn’t have an ABP CLI login/license session available, so runtime license validation fails and it prints “you should login to abp.io”.
Do not create a new project to fix it. The fix is to ensure the environment that builds/publishes (or runs, depending on your setup) has a valid ABP commercial license context.
Confirm the exact license error code in logs
ABP-LIC-0008 - License check failed for '...' You need to log in using the command abp login <username>.ABP-LIC-0018 - ... Your trial period has ended!Re-login on the build/deploy machine (or pipeline agent) using ABP CLI
abp login <your-abp-io-email>
If this started after “AI Management”
If the issue is actually “trial ended” but you have a production license
Account / product key not visible
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.
In ABP 10.0.1, DELETE /api/identity/organization-units/{id} deletes the OrganizationUnit aggregate itself. The framework does not automatically block deletion when there are still rows in the link tables (OU–User and OU–Role relations). Those relations are not modeled as required FK constraints to the OU entity in a way that would cause a database “restrict delete” error, so deletion can succeed and the relation rows may become orphaned depending on your DB configuration.
The UI typically prevents/avoids this scenario by guiding you to remove members/roles first, but calling the endpoint directly from Swagger bypasses any UI guardrails—so what you observe is expected with the default backend behavior.
To enforce “cannot delete if it has users/roles”, you should implement an application-layer validation before delete, then throw a user-friendly/business exception.
A safe, ABP-style implementation (override the app service):
using System;
using System.Threading.Tasks;
using Volo.Abp;
using Volo.Abp.Application.Services;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.Identity;
public class MyOrganizationUnitAppService : OrganizationUnitAppService
{
private readonly IRepository<IdentityUserOrganizationUnit> _userOuRepo;
private readonly IRepository<IdentityRoleOrganizationUnit> _roleOuRepo;
public MyOrganizationUnitAppService(
OrganizationUnitManager organizationUnitManager,
IOrganizationUnitRepository organizationUnitRepository,
IdentityUserManager userManager,
IdentityRoleManager roleManager,
IRepository<IdentityUserOrganizationUnit> userOuRepo,
IRepository<IdentityRoleOrganizationUnit> roleOuRepo)
: base(organizationUnitManager, organizationUnitRepository, userManager, roleManager)
{
_userOuRepo = userOuRepo;
_roleOuRepo = roleOuRepo;
}
public override async Task DeleteAsync(Guid id)
{
// Users?
var hasUsers = await _userOuRepo.AnyAsync(x => x.OrganizationUnitId == id);
// Roles?
var hasRoles = await _roleOuRepo.AnyAsync(x => x.OrganizationUnitId == id);
if (hasUsers || hasRoles)
{
throw new UserFriendlyException(
"Cannot delete organization unit because it has assigned users and/or roles.");
}
await base.DeleteAsync(id);
}
}
Wire it up by replacing the default service:
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Identity;
public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.Replace(
ServiceDescriptor.Transient<IOrganizationUnitAppService, MyOrganizationUnitAppService>()
);
}
Alternative (if you prefer automatic cleanup instead of blocking)
base.DeleteAsync(id), explicitly remove OU memberships and OU role links (using the relevant managers/repositories), then delete the OU. This avoids orphan links but changes behavior, so only do it if that matches your business rules.Sources (validated):
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.
This behavior can happen if the Organization Unit repository/queries are not protected by the current tenant filter at the time the parentId is loaded/validated, so the service can “see” an OU from another tenant and accept it as a parent. In a correctly isolated setup, the parentId lookup should only be able to resolve OUs in the current tenant (or fail).
Immediate mitigation (recommended custom validation): Override/extend the OU create endpoint in your solution and validate that the parentId belongs to the current tenant before creating the OU. The simplest safe rule is: if input.ParentId != null, then parent.TenantId must equal CurrentTenant.Id (and also handle host side as null tenant).
Example (custom application service wrapper) pattern:
using System;
using System.Threading.Tasks;
using Volo.Abp;
using Volo.Abp.Application.Services;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Identity;
public class MyOrganizationUnitAppService : ApplicationService
{
private readonly IOrganizationUnitRepository _organizationUnitRepository;
private readonly OrganizationUnitManager _organizationUnitManager;
private readonly ICurrentTenant _currentTenant;
public MyOrganizationUnitAppService(
IOrganizationUnitRepository organizationUnitRepository,
OrganizationUnitManager organizationUnitManager,
ICurrentTenant currentTenant)
{
_organizationUnitRepository = organizationUnitRepository;
_organizationUnitManager = organizationUnitManager;
_currentTenant = currentTenant;
}
public async Task<OrganizationUnitDto> CreateAsync(OrganizationUnitCreateDto input)
{
if (input.ParentId.HasValue)
{
var parent = await _organizationUnitRepository.GetAsync(input.ParentId.Value);
// Tenant boundary check
if (parent.TenantId != _currentTenant.Id)
{
throw new BusinessException("AbpIdentity:CrossTenantOuParentNotAllowed")
.WithData("ParentId", input.ParentId)
.WithData("CurrentTenantId", _currentTenant.Id)
.WithData("ParentTenantId", parent.TenantId);
}
}
var ou = await _organizationUnitManager.CreateAsync(input.DisplayName, input.ParentId);
return ObjectMapper.Map<OrganizationUnit, OrganizationUnitDto>(ou);
}
}
This guarantees the API cannot link to a parent OU from another tenant even if some internal query accidentally bypasses tenant filtering.
Also verify your multi-tenancy data filter is not being disabled for this call path (globally or via a custom unit-of-work/action filter). If you have any code like IDataFilter.Disable<IMultiTenant>() around identity/OUs, remove it; that would directly allow cross-tenant parent resolution.
If you believe this occurs in a clean ABP 10.0.1 Pro template without customization, please report it to Volosoft support (include your minimal repro).
https://abp.io/contact
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.
Yes — when migrating the Microservice template gateway from Ocelot to YARP, you should align your gateway routes with the current ABP template conventions, i.e. use:
"/api/account-admin/{**catch-all}" (YARP catch-all syntax)Your Ocelot route:
"/api/account-admin/{everything}"
is functionally the same intent (a catch-all), just using Ocelot’s placeholder style. In YARP the equivalent is {**catch-all}.No AuthServer backend code change is required just because of this route change, as long as:
/api/account-admin/...), andWhat you need to do is only gateway configuration (YARP) consistency:
/api/account-admin/...ClusterId: "AuthServer")Example YARP route (as template):
"AccountAdmin": {
"ClusterId": "AuthServer",
"Match": {
"Path": "/api/account-admin/{**catch-all}"
}
}
/api/account-admin/..., then switching the gateway from Ocelot to YARP with the route above will be transparent.Match.Path + Transforms (but in what you shared, upstream == downstream, so no transform is needed).Sources:
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.