Use Centralized Authentication (one Auth Server) and OIDC sign-in, not password-based token requests:
Automatic user provisioning on first external login (recommended):
Don’t reuse the same confidential client credentials across different sites arbitrarily:
When only email is available (no password):
Practical steps
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.
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.
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:
Get the LeptonX theme source (optional but helpful to see the original files):
Override the top menu layout in your MVC app:
Switch your application to use the top menu layout:
Notes/tips:
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.
What changed in v10.0.1
Why it “works in Application layer but not in Blazor”
Two supported ways to resolve
Option A — Keep using AutoMapper everywhere (quickest to keep existing 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);
});
}
}
Option B — Migrate to Mapperly (recommended for ABP v10+)
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);
}
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>();
}
}
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.
Why it happens
What to change
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);
}
public virtual async Task<ProductItemDto> GetAsync(Guid id)
{
var entity = await _productItemRepository.GetAsync(id, includeDetails: true);
return ObjectMapper.Map<ProductItem, ProductItemDto>(entity);
}
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
using (DataFilter.Disable<ISoftDelete>())
{
// load with details here
}
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.
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:
Practical steps:
Licensing implications:
Technical limitations to watch:
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.
using Volo.Abp.Security.Claims;
public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.Configure<AbpClaimsPrincipalFactoryOptions>(options =>
{
options.IsDynamicClaimsEnabled = true; // enable dynamic claims
});
}
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?
Authentication middleware/handlers:
context.Services.AddAuthentication()
.AddAbpOpenIdConnect("oidc", options =>
{
options.Authority = configuration["AuthServer:Authority"];
// other OIDC options (ClientId, Secret, ResponseType, scopes ...)
// RemoteRefreshUrl is set by AddAbpOpenIdConnect internally.
});
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.
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();
}
return new CompanyWithNavigationProperties
{
Company = company,
Organisation = company.Organisation,
Sectors = company.Sectors ?? new List<Sector>()
};
// 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.
[DependsOn(typeof(AbpDddApplicationModule), typeof(AbpAutoMapperModule), typeof(AbpMapperlyModule))]
public class YourApplicationModule : AbpModule
{
}
This guarantees ABP’s MapperlyAutoObjectMappingProvider is active.
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);
}
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 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
Map environment variables correctly in docker-compose
Remove or fix commented examples in docker-compose
Verify runtime configuration
Seed Language Management (if Dynamic Localization enabled)
Clear caches and restart containers
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.
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.
using Medallion.Threading;
using Medallion.Threading.Redis;
using StackExchange.Redis;
context.Services.AddSingleton<IDistributedLockProvider>(sp =>
{
var configuration = context.Services.GetConfiguration();
var mux = ConnectionMultiplexer.Connect(configuration["Redis:Configuration"]); // e.g., "127.0.0.1"
return new RedisDistributedSynchronizationProvider(mux.GetDatabase());
});
- Ensure all instances share the same distributed lock backend.
Configure<AbpDistributedEventBusOptions>(options =>
{
options.Outboxes.Configure(config => { config.UseDbContext<MyDbContext>(); });
options.Inboxes.Configure(config => { config.UseDbContext<MyDbContext>(); });
});
- Outbox/Inbox tables must live in the same database as your business data to execute in a single transaction.
On duplicates and ordering
If you need higher parallelism
Not supported/out-of-scope
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.
Prerequisites
Command abp suite generate -e <entity-json-path> -s <solution-sln-path> [options]
Required parameters
Common options
Typical usage examples
Basic generation (single module, single UI) abp suite generate -e C:\Users<you>.suite\entities\Country.json -s C:\dev\MyApp\MyApp.sln
Generate only for Angular UI abp suite generate -e ..suite\entities\Product.json -s .\MyApp.sln --ui angular
Overwrite previously generated files (refresh after entity changes) abp suite generate -e ..suite\entities\Order.json -s .\MyApp.sln --overwrite true
Dry run to preview changes abp suite generate -e ..suite\entities\Customer.json -s .\MyApp.sln --dry-run true
Generate into a specific module in a modular solution abp suite generate -e ..suite\entities\Invoice.json -s .\MyMicroserviceApp.sln --module AccountingService
What the command does
Authoring the entity JSON A minimal example to get started (Country.json): { "name": "Country", "namespace": "MyCompany.MyApp", "tableName": "AppCountries", "primaryKey": "Guid", "aggregateRoot": true, "audited": true, "fields": [ { "name": "Name", "type": "string", "maxLength": 128, "required": true }, { "name": "Code", "type": "string", "maxLength": 8, "required": true, "unique": true } ], "ui": { "create": { "visible": true }, "edit": { "visible": true }, "delete": { "visible": true }, "list": { "columns": [ { "name": "Name", "width": 200 }, { "name": "Code", "width": 120 } ] } }, "permissions": { "groupName": "Country", "default": "MyApp.Countries", "create": "MyApp.Countries.Create", "update": "MyApp.Countries.Update", "delete": "MyApp.Countries.Delete" } }
Recommended workflow
Troubleshooting tips
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.