Root cause: In the MVC Book Store sample, the New Book button uses ABP’s ModalManager and generated JavaScript proxies. If the global proxy namespace isn’t initialized on the page, clicking “New book” won’t do anything. On some setups (often Linux or when script order/bundling differs), the proxy object path can be undefined, so the button appears but the click handler silently fails.
Quick fix in Pages/Books/Index.js:
Ensure the page includes the generated JS proxies before Index.js:
Verify bundling isn’t stripping or reordering required scripts:
Double-check the selector and click handler wiring:
If the modal still doesn’t open:
Note on OS differences (Linux vs Windows):
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 the markup looks different
Get the correct, up-to-date view for Account Pro
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI
@addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI.Bootstrap
@addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI.Bundling
6) Keep the original model type from the Pro page (don’t switch to the free module’s LoginModel). If you need to extend behavior, subclass the correct Pro LoginModel type and only change the @model accordingly.
7) Now, minimally customize only the external login section:
- Find the external login block:
- The form with asp-page="./Login" asp-page-handler="ExternalLogin" (or a dedicated partial used for external providers).
- Insert your small changes (e.g., button text, icon, order), leaving all other markup intact to preserve styling.
If you only want to inject buttons without copying the whole page
LeptonX/LeptonXLite account layout note
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.
Apply the following steps to make your integration tests deterministic and fix the failure:
public class SiteManagementDataSeedContributor : IDataSeedContributor, ITransientDependency
{
private readonly IUnitOfWorkManager _uowManager;
private readonly IRepository<Contact, Guid> _contactRepository; // example
private readonly ICurrentTenant _currentTenant;
public SiteManagementDataSeedContributor(
IUnitOfWorkManager uowManager,
IRepository<Contact, Guid> contactRepository,
ICurrentTenant currentTenant)
{
_uowManager = uowManager;
_contactRepository = contactRepository;
_currentTenant = currentTenant;
}
public async Task SeedAsync(DataSeedContext context)
{
// Avoid host-only seeding when you expect tenant-only data
// and/or ensure you seed for a specific tenant
using (_currentTenant.Change(context?.TenantId))
{
using (var uow = _uowManager.Begin(requiresNew: true))
{
// Defensive checks to prevent NREs
if (_contactRepository == null)
{
await uow.CompleteAsync();
return;
}
// Example: skip if already seeded
var count = await _contactRepository.GetCountAsync();
if (count == 0)
{
await _contactRepository.InsertAsync(new Contact(/* init fields */), autoSave: true);
// ... add more seed data
}
await uow.CompleteAsync();
}
}
}
}
public class SiteManagementTestBaseModule : AbpModule
{
public override void OnApplicationInitialization(ApplicationInitializationContext context)
{
SeedTestData(context);
}
private static void SeedTestData(ApplicationInitializationContext context)
{
AsyncHelper.RunSync(async () =>
{
using var scope = context.ServiceProvider.CreateScope();
var dataSeeder = scope.ServiceProvider.GetRequiredService<IDataSeeder>();
// Optionally seed for host or a specific test tenant
await dataSeeder.SeedAsync(new DataSeedContext(/* tenantId: null or a tenant Guid */));
});
}
}
public class Contact : AggregateRoot<Guid>, IMultiTenant
{
public Guid? TenantId { get; set; }
// other properties...
}
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<AbpUnitOfWorkDefaultOptions>(options =>
{
options.TransactionBehavior = UnitOfWorkTransactionBehavior.Disabled;
});
context.Services.AddAlwaysDisableUnitOfWorkTransaction();
Configure<FeatureManagementOptions>(options =>
{
options.SaveStaticFeaturesToDatabase = false;
options.IsDynamicFeatureStoreEnabled = false;
});
Configure<PermissionManagementOptions>(options =>
{
options.SaveStaticPermissionsToDatabase = false;
options.IsDynamicPermissionStoreEnabled = false;
});
}
Keep test seeding minimal and deterministic
If you need tenant data during test, explicitly seed for that tenant
var tenantId = /* known test tenant Guid */;
await dataSeeder.SeedAsync(new DataSeedContext(tenantId));
These practices address:
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.
Keep your tenant resolvers (e.g., Cookie, Header, etc.) as-is. The tenant switcher UI is rendered by the theme’s Account layout. Override that layout and remove the switcher block so nothing flashes on first paint.
For LeptonX theme (most common in recent ABP versions), create the same-path view in your Web project and remove the tenant switcher section:
Create file: Themes/LeptonX/Layouts/Account/Default.cshtml Copy the original content from the theme (you can scaffold it from the package or use the structure below) and delete the conditional that shows the tenant switch:
@* Remove or comment out this entire block *@
@*
@if (MultiTenancyOptions.Value.IsEnabled &&
(TenantResolveResultAccessor.Result?.AppliedResolvers?.Contains(CookieTenantResolveContributor.ContributorName) == true ||
TenantResolveResultAccessor.Result?.AppliedResolvers?.Contains(QueryStringTenantResolveContributor.ContributorName) == true))
{
<div>
<div class="row">
<div class="col">
<span style="font-size: .8em;" class="text-uppercase text-muted">@MultiTenancyStringLocalizer["Tenant"]</span><br />
<h6 class="m-0 d-inline-block">
@if (CurrentTenant.Id == null)
{
<span>@MultiTenancyStringLocalizer["NotSelected"]</span>
}
else
{
<strong>@(CurrentTenant.Name ?? CurrentTenant.Id.Value.ToString())</strong>
}
</h6>
</div>
<div class="col-auto">
<a id="AbpTenantSwitchLink" href="javascript:;" class="btn btn-sm btn-outline-primary">@MultiTenancyStringLocalizer["Switch"]</a>
</div>
</div>
</div>
<hr />
}
*@
Leave the rest of the layout unchanged. This removes the tenant switcher UI entirely, so it won’t briefly appear, while your server-side tenant resolution (cookie resolver and any others) remains intact.
If you use the Basic theme or another theme, override its account layout in the equivalent path and remove the same tenant switcher section (it’s the same conditional block that checks MultiTenancyOptions and AppliedResolvers).
Do not clear AbpTenantResolveOptions unless you intend to replace all resolvers. Keep your cookie (and any other needed) resolvers registered so your custom logic that sets tenant (based on username/email) continues to work.
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 v10.x, the “SaaS” module and Multi-tenancy are controlled by two different things:
What happened in your case:
How to create a solution with both SaaS and Multi-tenancy off (no SaaS tables, no multi-tenant behavior):
Notes for v10.1 MVC template:
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.
A) AuthServer discovery shows HTTP endpoints (Angular rejects them) B) ABP Studio interception cannot reach cluster services (timeouts to Postgres/Redis via WireGuard)
Fix A – make discovery document fully HTTPS behind ingress/reverse proxy
public override void PreConfigureServices(ServiceConfigurationContext context)
{
var configuration = context.Services.GetConfiguration();
PreConfigure<OpenIddictServerBuilder>(builder =>
{
builder.SetIssuer(new Uri(configuration["AuthServer:Authority"]!));
});
}
public override void ConfigureServices(ServiceConfigurationContext context)
{
var env = context.Services.GetHostingEnvironment();
var configuration = context.Services.GetConfiguration();
if (!env.IsDevelopment())
{
// Honor X-Forwarded-* to build correct HTTPS URLs and cookies
context.Services.Configure<ForwardedHeadersOptions>(o =>
{
o.ForwardedHeaders = ForwardedHeaders.XForwardedFor
| ForwardedHeaders.XForwardedProto
| ForwardedHeaders.XForwardedHost;
o.KnownNetworks.Clear();
o.KnownProxies.Clear();
});
}
// If you cannot keep RequireHttpsMetadata=true, add this to avoid OpenIddict rejecting HTTP (not recommended for production):
if (!configuration.GetValue<bool>("AuthServer:RequireHttpsMetadata"))
{
Configure<OpenIddictServerAspNetCoreOptions>(options =>
{
options.DisableTransportSecurityRequirement = true;
});
}
}
public override void OnApplicationInitialization(ApplicationInitializationContext context)
{
var app = context.GetApplicationBuilder();
var env = context.GetEnvironment();
if (!env.IsDevelopment())
{
app.UseForwardedHeaders();
}
// Optional hard override if your proxy doesn’t forward scheme correctly:
// app.Use((ctx, next) => { ctx.Request.Scheme = "https"; return next(); });
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseConfiguredEndpoints();
}
Fix B – ABP Studio interception timeouts to in-cluster services
Notes specific to Angular and GW
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
Fix: publish and copy the correct output into the runtime image
# Build & publish
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
WORKDIR /src
COPY . .
RUN dotnet restore
RUN dotnet publish ./src/YourCompany.YourApp.Blazor/YourCompany.YourApp.Blazor.csproj -c Release -o /app/publish
# Runtime
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS final
WORKDIR /app
COPY --from=build /app/publish .
ENTRYPOINT ["dotnet", "YourCompany.YourApp.Blazor.dll"]
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS final
WORKDIR /app
COPY ./src/YourCompany.YourApp.Blazor/bin/Release/net10.0/publish/ .
ENTRYPOINT ["dotnet", "YourCompany.YourApp.Blazor.dll"]
Verify that the path exists locally and contains the “_framework” under wwwroot/_framework or in static web assets folders included by the publish output.
Additional checks
ABP-specific note
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’s happening:
End-to-end steps to call protected APIs from your Next.js app using the SSO token:
${process.env.NEXT_PUBLIC_API_URL}/api/identity/my-profile, {
headers: {
Authorization: Bearer ${accessToken},
'Accept-Language': 'en', // optional
'Abp-TenantId': tenantId ?? '' // if you use multi-tenancy and need to target a tenant
}
});
if (res.status === 401) {
// handle re-auth or token refresh
}Minimal code example (server-side Next.js API route proxying to ABP API):
// /pages/api/me.ts
import type { NextApiRequest, NextApiResponse } from 'next';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const accessToken = req.headers['authorization']?.toString().replace(/^Bearer\s+/i, '');
if (!accessToken) {
return res.status(401).json({ error: 'No access token' });
}
const apiUrl = process.env.NEXT_PUBLIC_API_URL; // e.g. https://localhost:44350
const r = await fetch(`${apiUrl}/api/identity/my-profile`, {
headers: { Authorization: `Bearer ${accessToken}` }
});
if (!r.ok) {
return res.status(r.status).send(await r.text());
}
return res.status(200).json(await r.json());
}
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.
Do this to make it work reliably in MVC Razor Pages:
Ensure static JS proxies are enabled and generated
Use the new global namespace when calling APIs
Verify the actual namespace at runtime
Common pitfalls to check
Minimal working example // 1) After correct proxy generation and layout setup: // window.myCompany.myApp.default.bangGiaVeNgays.bangGiaVeNgays.create({ ... }) window.myCompany.myApp.default.bangGiaVeNgays.bangGiaVeNgays .getList({ skipCount: 0, maxResultCount: 10 }) .then(result => console.log(result)) .catch(err => console.error(err));
Does the latest ABP still support calling services like before?
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.
The call bypasses ABP’s interception pipeline. Checklist:
Auditing middleware and options
public override void OnApplicationInitialization(ApplicationInitializationContext context)
{
var app = context.GetApplicationBuilder();
app.UseAuditing();
}
Configure<AbpAuditingOptions>(options =>
{
options.IsEnabled = true;
// optional:
// options.IsEnabledForGetRequests = true;
// options.AlwaysLogOnException = true;
});
Ensure you didn’t use [DisableAuditing] on the service or method.
Entity change logs require UoW + change tracking selection Create/Update “action logs” should appear even without entity changes, but if you expect EntityChanges, ensure:
Configure<AbpAuditingOptions>(options =>
{
options.EntityHistorySelectors.AddAllEntities(); // or add your entity types explicitly
});
Your DbContext is not being saved outside ABP’s UoW unexpectedly.
Verify the proxy is actually used in Blazor Temporarily assert interception by forcing auditing:
[Audited]
public class SeasonAppService : CrudAppService<Season, SeasonDto, Guid, PagedAndSortedResultRequestDto, CreateSeasonDto, UpdateSeasonDto>,
ISeasonAppService
{
public override async Task<SeasonDto> CreateAsync(CreateSeasonDto input) { ... }
public override async Task<SeasonDto> UpdateAsync(Guid id, UpdateSeasonDto input) { ... }
}
If this still doesn’t create an audit log, interception is likely bypassed.
public override async Task<SeasonDto> CreateAsync(CreateSeasonDto input)
{
return await base.CreateAsync(input);
}
public override async Task<SeasonDto> UpdateAsync(Guid id, UpdateSeasonDto input)
{
return await base.UpdateAsync(id, input);
}
Do not seal the class or methods.
[Inject] protected ISeasonAppService SeasonAppService { get; set; } = default!;
Do not cast to the concrete type or call non-virtual helpers on the class.
Custom IAuditingStore If you replaced IAuditingStore, ensure SaveAsync is called and not filtering out Create/Update. Log inside your store to verify it’s invoked for Create/Update paths.
Quick diagnostic
If after all checks Delete is audited but Create/Update are not:
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.