A) Build and place Angular into Host/wwwroot
B) Ensure Host serves static files and SPA fallback In YourProjectHttpApiHostModule.cs (or Program.cs if using minimal hosting), inside OnApplicationInitialization (order matters):
public override void OnApplicationInitialization(ApplicationInitializationContext context)
{
var app = context.GetApplicationBuilder();
var env = context.GetEnvironment();
app.UseAbpRequestLocalization();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseAbpSecurityHeaders();
app.UseHttpsRedirection();
app.UseCorrelationId();
// Serve SPA static files (Angular build in wwwroot)
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAbpOpenIddictValidation();
app.UseAuthorization();
app.UseSwagger();
app.UseAbpSwaggerUI(options =>
{
options.SwaggerEndpoint("/swagger/v1/swagger.json", "API");
});
app.UseAuditing();
app.UseAbpSerilogEnrichers();
app.UseConfiguredEndpoints();
// SPA fallback: return index.html for non-file, non-API requests
app.Use(async (ctx, next) =>
{
await next();
if (ctx.Response.StatusCode == 404 &&
!System.IO.Path.HasExtension(ctx.Request.Path.Value) &&
!ctx.Request.Path.Value.StartsWith("/api", StringComparison.OrdinalIgnoreCase))
{
ctx.Request.Path = "/index.html";
ctx.Response.StatusCode = 200;
await next();
}
});
}
Alternative controller-based redirect (older practice that also works on Azure App Service):
public class HomeController : AbpController
{
public ActionResult Index()
{
return Redirect("~/index.html" + HttpContext.Request.QueryString);
}
}
C) Azure App Service specifics
app.Use(async (ctx, next) =>
{
ctx.Request.Scheme = "https";
await next();
});
D) Unified domain configuration Set host appsettings.json consistently for single-domain SPA+API:
E) Angular environment configuration environment.prod.ts:
F) IdentityServer client records In DbMigrator appsettings (or wherever you seed IdentityServer clients), ensure the SPA client:
G) Why you were seeing the API login page If index.html isn’t found or served, requests fall through to API endpoints (e.g., /account/login via MVC/Identity) and you see the API login instead of the Angular SPA. The SPA fallback middleware and correct placement of index.html fix this.
H) Multitenancy with subdomains (optional) If you run tenants on subdomains on the same app service and still serve SPA from the host:
Configure<AbpTenantResolveOptions>(options =>
{
options.AddDomainTenantResolver("{0}.your-app.azurewebsites.net");
});
Configure<AbpAccountOptions>(options =>
{
options.IsTenantMultiDomain = true;
options.GetTenantDomain = (httpContext, info) =>
Task.FromResult(string.IsNullOrWhiteSpace(info.Name)
? "https://your-app.azurewebsites.net"
: $"https://{info.Name}.your-app.azurewebsites.net");
});
Configure<AbpAccountIdentityServerOptions>(options =>
{
options.IsTenantMultiDomain = true;
options.GetTenantDomain = (httpContext, ctx, info) =>
Task.FromResult(string.IsNullOrWhiteSpace(info.Name)
? "https://your-app.azurewebsites.net"
: $"https://{info.Name}.your-app.azurewebsites.net");
});
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. Where to add Output Caching
B. Service registration (HttpApi.Host) In Program.cs (or Startup), register OutputCache policies alongside ABP’s standard pipeline:
// Program.cs (HttpApi.Host) builder.Services.AddOutputCache(options => { options.DefaultExpirationTimeSpan = TimeSpan.FromSeconds(30); options.AddPolicy("Default", policy => policy .Expire(TimeSpan.FromSeconds(120)) .SetVaryByHeader("Authorization")); });
// keep your existing ABP, authentication and CORS configurations here
C. Middleware order (HttpApi.Host) Add UseOutputCache in the correct order. For .NET 8, the typical order is:
var app = builder.Build();
// ... existing ABP middlewares app.UseRouting(); app.UseCors(); app.UseAuthentication(); app.UseAuthorization(); app.UseAbpRequestLocalization();
// IMPORTANT: enable output caching here app.UseOutputCache();
app.UseUnitOfWork(); app.UseAuditing();
// ABP maps endpoints here app.UseConfiguredEndpoints();
app.Run();
D. Use OutputCache attributes on controllers (in .HttpApi) Continue using attributes on your controllers (in the .HttpApi project). Example:
[OutputCache( PolicyName = "Default", VaryByRouteValueNames = new[] { nameof(id) })] [Authorize] // if required [ApiController] [Route("api/my-endpoint")] public class MyController : ControllerBase { [HttpGet("{id}")] public async Task<MyDto> GetAsync(Guid id) { ... } }
For query variants:
[OutputCache( PolicyName = "Default", VaryByQueryKeys = new[] { nameof(FilterInputDto.Id), nameof(FilterInputDto.SearchText), nameof(FilterInputDto.IsActive), nameof(FilterInputDto.Sorting) })]
E. Verifying headers and behavior
F. Why it didn’t work when controllers were in .HttpApi
G. Why you got 401 when moving controllers to .Web
H. Common pitfalls to double-check
I. Is Output Caching officially supported under .HttpApi?
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.
public class MyDataSeedContributor : IDataSeedContributor, ITransientDependency
{
private readonly ILocalizationTextManager _textManager;
private readonly ICurrentTenant _currentTenant;
public MyDataSeedContributor(ILocalizationTextManager textManager, ICurrentTenant currentTenant)
{
_textManager = textManager;
_currentTenant = currentTenant;
}
public async Task SeedAsync(DataSeedContext context)
{
using (_currentTenant.Change(context?.TenantId))
{
// Portuguese example
await _textManager.SetAsync("LeptonX", "FilterMenu", "pt-BR", "Filtrar menu");
// Add other languages as needed
await _textManager.SetAsync("LeptonX", "FilterMenu", "en", "Filter menu");
}
}
}
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.
Cause
Fix
public override void OnApplicationInitialization(ApplicationInitializationContext context)
{
var app = context.GetApplicationBuilder();
var env = context.GetEnvironment();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseAbpRequestLocalization();
app.UseStaticFiles(); // 1: physical wwwroot
app.UseAbpStaticFiles(); // 2: embedded static files from ABP modules
app.UseRouting();
app.UseAuthentication();
app.UseJwtTokenMiddleware();
app.UseAbpClaimsMap();
app.UseAuthorization();
app.UseAuditing();
app.UseConfiguredEndpoints();
}
public class DocsThemeGlobalContributor : BundleContributor
{
public override void ConfigureBundle(BundleConfigurationContext context)
{
var style = context.Files;
// styles
style.AddIfNotContains("/libs/@fortawesome/fontawesome-free/css/all.min.css");
var script = context.Files;
// scripts
script.AddIfNotContains("/libs/jquery/jquery.js");
script.AddIfNotContains("/libs/select2/js/select2.full.min.js");
script.AddIfNotContains("/libs/moment/moment.js");
// add any i18n files you actually use:
// script.AddIfNotContains("/libs/select2/js/i18n/en.js");
}
}
That’s Font Awesome not loading. Once /libs/@fortawesome/fontawesome-free/webfonts/... are served correctly (steps 1–3), the icons will render.
Quick checklist for your screenshots
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) Recommended: Use Releases/Tags as versions
B) If you must use Branches as versions
Additional checks
Quick apply to your screenshots
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.
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.
Yes. ABP supports showing an OTP step after a successful username/password login (Two-Factor Authentication). Users can use Email, SMS, or an Authenticator app. You can allow the user to choose Email or SMS on the OTP screen, or you can restrict to one provider.
Enable 2FA and providers:
How the built-in flow works:
Let the user choose Email or SMS:
public class MyAccountAppService : AccountAppService
{
public override async Task<ListResultDto<NameValue>> GetTwoFactorProvidersAsync(GetTwoFactorProvidersInput input)
{
var result = await base.GetTwoFactorProvidersAsync(input);
// Keep only Email and SMS
result.Items = result.Items.Where(p => p.Name == "Email" || p.Name == "SMS").ToList();
return result;
}
}
Register your derived service in your module to replace the default IAccountAppService.
If you want to force a single provider (e.g., SMS only) and remove the dropdown:
Notes and prerequisites:
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 checklist and fix:
Generate a proper OpenIddict certificate for production (not the site TLS cert)
Configure ABP OpenIddict to use the PFX in production
public override void PreConfigureServices(ServiceConfigurationContext context)
{
var hostingEnvironment = context.Services.GetHostingEnvironment();
var configuration = context.Services.GetConfiguration();
if (!hostingEnvironment.IsDevelopment())
{
PreConfigure<AbpOpenIddictAspNetCoreOptions>(options =>
{
options.AddDevelopmentEncryptionAndSigningCertificate = false;
});
PreConfigure<OpenIddictServerBuilder>(serverBuilder =>
{
// If your pfx is in content root
serverBuilder.AddProductionEncryptionAndSigningCertificate(
"openiddict.pfx",
"00000000-0000-0000-0000-000000000000"
);
// Set the public HTTPS issuer
serverBuilder.SetIssuer(new Uri(configuration["AuthServer:Authority"]!));
});
}
}
PreConfigure<OpenIddictServerBuilder>(serverBuilder =>
{
var flags = X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.EphemeralKeySet;
serverBuilder.AddProductionEncryptionAndSigningCertificate("openiddict.pfx", "00000000-0000-0000-0000-000000000000", flags);
});
Ensure the file is actually present on the server
Keep Authority and metadata HTTPS-only
"AuthServer": {
"Authority": "https://your-authserver-host",
"RequireHttpsMetadata": true,
"CertificatePassPhrase": "00000000-0000-0000-0000-000000000000"
}
app.Use(async (ctx, next) =>
{
ctx.Request.Scheme = "https";
await next();
});
If you temporarily set RequireHttpsMetadata = false, discovery will emit http URLs and SPAs will reject them; keep it true and fix scheme/forwarding instead.
About the “key rollover” message
IIS-specific 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 to change
Example const routes: Routes = [ { path: 'identity', loadChildren: () => import('@volo/abp.ng.identity').then(m => m.createRoutes()) }, { path: 'saas', loadChildren: () => import('@volo/abp.ng.saas').then(m => m.createRoutes()) }, { path: 'audit-logging', loadChildren: () => import('@volo/abp.ng.audit-logging').then(m => m.createRoutes()) }, { path: 'language-management', loadChildren: () => import('@volo/abp.ng.language-management').then(m => m.createRoutes()) }, { path: 'text-template-management', loadChildren: () => import('@volo/abp.ng.text-template-management').then(m => m.createRoutes()) }, { path: 'account', loadChildren: () => import('@volo/abp.ng.account').then(m => m.createRoutes()) }, ];
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.
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.