Setup steps (MVC):
private void ConfigureBlobStoring(IConfiguration configuration)
{
Configure<AbpBlobStoringOptions>(options =>
{
options.Containers.Configure<FileManagementContainer>(c =>
{
c.UseFileSystem(fs =>
{
fs.BasePath = configuration["FileManagement:BasePath"]; // e.g. "D:\\abp-files"
});
// You can alternatively use Database or Azure providers.
});
});
}
[DependsOn(
typeof(FileManagementWebModule) // plus other module deps
)]
public class YourProjectWebModule : AbpModule
{ }
Key points:
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.
Fix/checklist:
using Volo.Abp.AspNetCore.Mvc;
using Volo.Abp.Modularity;
[DependsOn(
typeof(AbpAspNetCoreMvcModule),
typeof(AmlReportServiceApplicationModule) // your app module
)]
public class AmlReportServiceHttpApiModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<AbpAspNetCoreMvcOptions>(options =>
{
options.ConventionalControllers.Create(typeof(AmlReportServiceApplicationModule).Assembly);
});
}
}
If CustomStaticDataTypeAppService resides in a different assembly, pass that assembly instead.
[RemoteService(true)]
public class CustomStaticDataTypeAppService : StaticDataTypeAppService, ICustomStaticDataTypeAppService
{
// ...
}
Verify the remote service name/group for your controller so the JS proxy endpoint includes it. If you changed the controller name with [RemoteService(Name = "AmlReportService")] or ApiExplorerGroupName, ensure you request the correct group from the ServiceProxyScript if you are filtering by module. For example:
If you are in a tiered setup, ensure the MVC/UI application points to and loads the ServiceProxyScript from the AMLReportService HttpApi host that actually exposes the controllers. Load and inspect:
Use only async methods in app services. JS proxies are generated for the HTTP endpoints created from async methods (Task/Task<T>) on IApplicationService. Your signatures already look async; keep that.
Clear caches/restart and re-check: since ServiceProxyScript is generated on request, restart the HttpApi host after code changes and hard-refresh the browser to avoid caching.
Quick verification flow:
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 YourProjectNameEfCoreEntityExtensionMappings.Configure()
ObjectExtensionManager.Instance
.MapEfCoreProperty<IdentityUser, string>(
MyConsts.MyPropertyName,
(entityBuilder, propertyBuilder) =>
{
propertyBuilder.HasMaxLength(128);
// optional: propertyBuilder.HasDefaultValue("");
// optional: add index/unique/etc.
}
);
- Ensure this mapping executes before DbContext model building (use the generated EfCoreEntityExtensionMappings class).
- Add a migration and update the database.
ObjectExtensionManager.Instance.Modules().ConfigureIdentity(identity =>
{
identity.ConfigureUser(user =>
{
user.AddOrUpdateProperty<Guid?>(
MyConsts.MyPropertyName,
options =>
{
options.DefaultValue = null;
options.UI.OnTable.IsVisible = true;
options.UI.OnCreateForm.IsVisible = false;
// optionally:
// options.UI.OnEditForm.IsVisible = true;
}
);
});
});
Notes:
End-to-end checklist for your case (ABP 9.1.3, Blazor Server, SQL Server)
// Domain.EfCore project
public static class YourProjectEfCoreEntityExtensionMappings
{
private static readonly OneTimeRunner OneTimeRunner = new OneTimeRunner();
public static void Configure()
{
OneTimeRunner.Run(() =>
{
ObjectExtensionManager.Instance
.MapEfCoreProperty<IdentityUser, string>(
MyConsts.MyPropertyName,
(_, p) =>
{
p.HasMaxLength(128);
}
);
});
}
}
- Call YourProjectEfCoreEntityExtensionMappings.Configure() during pre-DbContext configuration (usually in your EntityFrameworkCore module’s ConfigureServices).
- Run Add-Migration and Update-Database.
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
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.