Root cause
What to do
public class SaasFeaturesDefinitionProvider : FeatureDefinitionProvider
{
public override void Define(IFeatureDefinitionContext context)
{
var group = context.GetGroupOrNull(ModulesFeatures.Main)
?? context.AddGroup(ModulesFeatures.Main, L("MainFeatures"));
group.AddFeature(
SaasFeatures.Saas,
defaultValue: "true",
displayName: L(SaasFeatures.Saas),
isVisibleToClients: true // <<< important
);
}
private static LocalizableString L(string name)
=> LocalizableString.Create<PlatformResource>("Features:" + name);
}
[DependsOn(
typeof(AbpFeaturesModule)
// + your package/module that contains the FeatureDefinitionProvider
)]
public class PlatformDomainModule : AbpModule
{
}
var saas = context.Menu.GetMenuItem(SaasHostMenus.GroupName);
saas.RequireFeatures(SaasFeatures.Saas);
defaultValue: "true" is used only if there is no stored value. If you manage the feature in UI, the database value takes precedence.
Ensure the tenant/host you are testing has the feature enabled in the Features modal.
Quick checklist
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.
Follow these steps:
Determine your Auth Server base URL
Create or confirm a client that allows the password flow
Postman request to get the token (password grant)
Important multi-tenancy notes
Use the token in JMeter
Example curl (equivalent to Postman) curl -X POST "https://localhost:44322/connect/token" ^ -H "Content-Type: application/x-www-form-urlencoded" ^ -H "Abp-TenantId: 11111111-1111-1111-1111-111111111111" ^ --data-urlencode "grant_type=password" ^ --data-urlencode "username=admin@mytenant.com" ^ --data-urlencode "password=1q2w3E*" ^ --data-urlencode "client_id=MyAngular_App" ^ --data-urlencode "client_secret=1q2w3e*" ^ --data-urlencode "scope=openid profile email roles offline_access MyProjectName"
If you prefer client credentials (app-to-app, no user), use:
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.
Please contact us at 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.
[maliming] said: hi
We plan to release 9.3.7 this week.
Thanks.
Solution
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.
Recommended approach: Resolve the tenant using a standard tenant resolver (domain/subdomain, route, header, or query string) instead of deriving it from client_id. ABP’s multi-tenancy pipeline is designed to determine the current tenant before MVC/OpenIddict endpoints run. Tying tenant selection to client_id is brittle (one client may serve multiple tenants; client names can change; it bypasses ABP’s resolvers). Prefer:
If you still want to implement a client_id-to-tenant mapping: add a custom ITenantResolveContributor that checks the request when OpenIddict’s authorize/login/register endpoints are hit and sets the tenant from a mapping dictionary. Ensure you register it very early in the resolver pipeline and only apply it for the authorize/login/register paths, so you don’t affect other requests.
Code: Domain/route (recommended)
// In YourProjectNameHttpApiHostModule (or AuthServer module if separated)
using Volo.Abp.MultiTenancy;
public override void ConfigureServices(ServiceConfigurationContext context)
{
var configuration = context.Services.GetConfiguration();
Configure<AbpTenantResolveOptions>(options =>
{
// Example: https://{0}.yourdomain.com
options.AddDomainTenantResolver("{0}.yourdomain.com");
});
}
Code: Custom contributor mapping client_id -> tenant (if you must)
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Primitives;
using Volo.Abp.DependencyInjection;
using Volo.Abp.MultiTenancy;
public class ClientIdTenantResolveContributor : HttpTenantResolveContributorBase, ITransientDependency
{
public const string ContributorName = "ClientIdTenant";
public override string Name => ContributorName;
protected override Task<string?> GetTenantIdOrNameFromHttpContextOrNullAsync(
ITenantResolveContext context, HttpContext httpContext)
{
// Only act on OIDC endpoints to avoid side effects
var path = httpContext.Request.Path.Value ?? "";
if (!path.StartsWith("/connect/authorize") &&
!path.StartsWith("/Account/Login") &&
!path.StartsWith("/Account/Register"))
{
return Task.FromResult<string?>(null);
}
// Read client_id from query (OpenIddict authorize request)
if (httpContext.Request.Query.TryGetValue("client_id", out StringValues clientIds))
{
var clientId = clientIds.ToString();
// Your mapping: client_id => tenant name
// Example: Next_app => "NextJs"
var tenantName = MapClientIdToTenantName(clientId);
if (!tenantName.IsNullOrWhiteSpace())
{
return Task.FromResult<string?>(tenantName);
}
}
return Task.FromResult<string?>(null);
}
private static string? MapClientIdToTenantName(string clientId)
{
return clientId switch
{
"Next_app" => "NextJs",
// add other mappings here
_ => null
};
}
}
Register it early in the pipeline (before others that you want it to override):
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<AbpTenantResolveOptions>(options =>
{
// Put it at the beginning if you want it to take precedence
options.TenantResolvers.Insert(0, new ClientIdTenantResolveContributor());
});
}
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.
Workarounds you can apply today:
Suppress prerelease warning noise and stay on the transitive version (recommended until ABP bumps):
Force the stable provider version when it becomes available:
<ItemGroup>
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.0" />
</ItemGroup>
This overrides the transitive rc1 from Volo.Abp.EntityFrameworkCore.PostgreSql.
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.
Default choice and design reason
Downsides or caveats of using sequential GUIDs globally
How to replace the default with SequentialGuidGenerator via DI (ABP-idiomatic)
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp;
using Volo.Abp.Guids;
public class MyModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.AddSingleton<IGuidGenerator, SequentialGuidGenerator>();
}
}
This forces the sequential implementation globally. In an ABP app with standard EF Core provider packages, this is normally unnecessary because ABP already wires SequentialGuidGenerator as the default.
Is ABP’s SequentialGuidGenerator recommended for production with large datasets and GUID PKs?
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.
Implement a custom provider by inheriting the built-in LeptonXStyleProvider and override only what you need (SelectedStyle, Side menu, etc.). Then register it as the ILeptonXStyleProvider.
Example (MVC, ABP v9.x):
using System.Threading.Tasks;
using Microsoft.Extensions.Options;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Ui.LeptonX;
using Volo.Abp.Ui.LeptonX.Theming;
namespace Tapp.Web;
// Inherit the base provider to avoid re-implementing unrelated members
public class TappLeptonXStyleProvider : LeptonXStyleProvider, ITransientDependency
{
public TappLeptonXStyleProvider(IOptions<LeptonXThemeOptions> options)
: base(options)
{
}
// Force Light theme always
public override Task<string> GetSelectedStyleAsync()
{
return Task.FromResult(LeptonXStyleNames.Light);
}
// Optional: keep menu expanded (example)
public override Task<LeptonXSideMenuState> GetSideMenuStateAsync()
{
return Task.FromResult(LeptonXSideMenuState.Expanded);
}
}
Register it in your WebModule so it replaces the default provider:
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Modularity;
using Volo.Abp.Ui.LeptonX.Theming;
[DependsOn(typeof(AbpAspNetCoreMvcUiLeptonXThemeModule))] // ensure LeptonX MVC module is depended
public class TappWebModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.AddTransient<ILeptonXStyleProvider, TappLeptonXStyleProvider>();
}
}
Notes:
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.Ui.Branding;
using Volo.Abp.AspNetCore.Mvc.UI.Theme.LeptonXLite; // or LeptonX, depending on your package
using Volo.Abp.AspNetCore.Mvc.UI.Theme.LeptonXLite.Bundling; // if using LeptonX Lite bundles
using Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared;
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<LeptonXThemeOptions>(options =>
{
// Forces default style to Light
options.DefaultStyle = LeptonXThemeStyle.Light;
});
// Optional: if you have per-user style/cookie preferences lingering from development,
// you can disable reading from user settings to avoid OS/system overrides:
Configure<LeptonXThemeUiOptions>(ui =>
{
ui.AllowStyleSelectionFromClient = false;
});
}
Remove or hide the client-side theme switcher
Clear persisted client preference so testing doesn’t revert to Dark
Prevent system/OS dark preference from applying
If you still see dark colors, verify CSS order
Code-only minimal variant:
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<LeptonXThemeOptions>(options =>
{
options.DefaultStyle = LeptonXThemeStyle.Light;
});
Configure<LeptonXThemeUiOptions>(ui =>
{
ui.AllowStyleSelectionFromClient = false;
});
}
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.